diff --git a/pom.xml b/pom.xml index 0c3f8a8c2b04bbf95b26ce18c1e75b9549780ac9..97d528b788f61fa50b570ece91b38ade11cfc6d9 100644 --- a/pom.xml +++ b/pom.xml @@ -251,20 +251,31 @@ <artifactId>java-jwt</artifactId> <version>3.11.0</version> </dependency> - </dependencies> - <build> - <finalName>portal-backend</finalName> - <resources> - <resource> - <directory>src/main/resources</directory> - <includes> - <include>**/*.xml</include> - <include>**/*.json</include> - <include>**/*.csv</include> - <include>**/*.sql</include> - <include>**/*.conf</include> - <include>**/*.yml</include> <!-- Only for development --> + <dependency> + <groupId>com.google.code.svenson</groupId> + <artifactId>svenson</artifactId> + <version>1.5.8</version> + </dependency> + <!--<dependency> + <groupId>eu.hbp.mip</groupId> + <artifactId>portal-backend</artifactId> + <version>4.0.0</version> + </dependency>--> + </dependencies> + + <build> + <finalName>portal-backend</finalName> + <resources> + <resource> + <directory>src/main/resources</directory> + <includes> + <include>**/*.xml</include> + <include>**/*.json</include> + <include>**/*.csv</include> + <include>**/*.sql</include> + <include>**/*.conf</include> + <!-- <include>**/*.yml</include> Only for development --> </includes> <filtering>true</filtering> diff --git a/src/main/java/eu/hbp/mip/controllers/ExperimentApi.java b/src/main/java/eu/hbp/mip/controllers/ExperimentApi.java index 322f33f4f5a8a08dff189b9e76a332e2983317bb..803c475723eb1a86bdc4f75e5112d67994de981d 100644 --- a/src/main/java/eu/hbp/mip/controllers/ExperimentApi.java +++ b/src/main/java/eu/hbp/mip/controllers/ExperimentApi.java @@ -1,13 +1,11 @@ package eu.hbp.mip.controllers; import eu.hbp.mip.model.DTOs.ExperimentDTO; -import eu.hbp.mip.model.UserInfo; import eu.hbp.mip.services.ExperimentService; import eu.hbp.mip.utils.JsonConverters; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -27,23 +25,34 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @Api(value = "/experiments") public class ExperimentApi { - @Autowired - private UserInfo userInfo; + private final ExperimentService experimentService; - @Autowired - private ExperimentService experimentService; + public ExperimentApi(ExperimentService experimentService) { + this.experimentService = experimentService; + } - @ApiOperation(value = "Get experiments", response = ExperimentDTO.class, responseContainer = "List") + @ApiOperation(value = "Get experiments", response = Map.class, responseContainer = "List") @RequestMapping(method = RequestMethod.GET) public ResponseEntity<String> getExperiments( @RequestParam(name = "name", required = false) String name, @RequestParam(name = "algorithm", required = false) String algorithm, @RequestParam(name = "shared", required = false) Boolean shared, @RequestParam(name = "viewed", required = false) Boolean viewed, + @RequestParam(name = "orderBy", required = false, defaultValue = "created") String orderBy, + @RequestParam(name = "descending", required = false, defaultValue = "true") Boolean descending, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "3") int size ) { - Map experiments = experimentService.getExperiments(name, algorithm, shared, viewed, page, size, "(GET) /experiments"); + Map experiments = experimentService.getExperiments( + name, + algorithm, + shared, + viewed, + page, + size, + orderBy, + descending, + "(GET) /experiments"); return new ResponseEntity(experiments, HttpStatus.OK); } @@ -66,7 +75,7 @@ public class ExperimentApi { @ApiOperation(value = "Create a transient experiment", response = ExperimentDTO.class) - @RequestMapping(value = "/{transient}",method = RequestMethod.POST) + @RequestMapping(value = "/transient",method = RequestMethod.POST) public ResponseEntity<String> createTransientExperiment(Authentication authentication, @RequestBody ExperimentDTO experimentDTO) { experimentDTO = experimentService.createTransientExperiment(authentication, experimentDTO, "(POST) /experiments/transient"); diff --git a/src/main/java/eu/hbp/mip/model/DAOs/ExperimentDAO.java b/src/main/java/eu/hbp/mip/model/DAOs/ExperimentDAO.java index 2ee8e0ba401191508fa72258d35f997f8fba9509..f10f887426f189917fe2dd9f632a21e95df6192d 100644 --- a/src/main/java/eu/hbp/mip/model/DAOs/ExperimentDAO.java +++ b/src/main/java/eu/hbp/mip/model/DAOs/ExperimentDAO.java @@ -7,10 +7,10 @@ import eu.hbp.mip.model.DTOs.AlgorithmDTO; import eu.hbp.mip.model.DTOs.ExperimentDTO; import eu.hbp.mip.utils.JsonConverters; import io.swagger.annotations.ApiModel; +import org.svenson.JSONParser; import javax.persistence.*; -import java.util.Date; -import java.util.UUID; +import java.util.*; /** * Created by habfast on 21/04/16. @@ -65,6 +65,10 @@ public class ExperimentDAO { @Expose @Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE") private Date created = new Date(); + + @Expose + @Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE") + private Date updated; @Expose @Column(columnDefinition = "BOOLEAN") @@ -81,7 +85,7 @@ public class ExperimentDAO { success } - public enum MimeTypes { + public enum Type { ERROR("text/plain+error"), WARNING("text/plain+warning"), USER_WARNING("text/plain+user_error"), @@ -93,15 +97,15 @@ public class ExperimentDAO { HTML("text/html"), TEXT("text/plain"); - private String types; + private String type; //Constructor to initialize the instance variable - MimeTypes(String types) { - this.types = types; + Type(String type) { + this.type = type; } - public String getTypes() { - return this.types; + public String getType() { + return this.type; } } @@ -116,15 +120,25 @@ public class ExperimentDAO { ExperimentDTO experimentDTO = new ExperimentDTO(); experimentDTO.setAlgorithmDetails(JsonConverters.convertJsonStringToObject(this.algorithmDetails, AlgorithmDTO.class)); experimentDTO.setCreated(this.created); + experimentDTO.setUpdated(this.updated); + experimentDTO.setFinished(this.finished); experimentDTO.setCreatedBy(this.createdBy.getUsername()); experimentDTO.setName(this.name); - experimentDTO.setResult(JsonConverters.convertJsonStringToObject(this.result, ExperimentDTO.ResultDTO.class)); + experimentDTO.setResult(convertJsonStringToResult(this.result)); + experimentDTO.setStatus(this.status); experimentDTO.setShared(this.shared); experimentDTO.setUuid(this.uuid); experimentDTO.setViewed(this.viewed); return experimentDTO; } + public static Map convertJsonStringToResult(String jsonResult) { + if(jsonResult == null || jsonResult.isEmpty()) + return null; + JSONParser parser = new JSONParser(); + parser.addTypeHint("Result[]", ExperimentDTO.ResultDTO.class); + return parser.parse(Map.class, jsonResult); + } public String getAlgorithmDetails() { return algorithmDetails; } @@ -180,6 +194,14 @@ public class ExperimentDAO { public void setCreated(Date created) { this.created = created; } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } public UUID getUuid() { return uuid; diff --git a/src/main/java/eu/hbp/mip/model/DTOs/ExperimentDTO.java b/src/main/java/eu/hbp/mip/model/DTOs/ExperimentDTO.java index 64ade455bc623298c173edad1ddd60919517bc1c..037ab5767cec70daa49320fa228c73cc5b1f7a8e 100644 --- a/src/main/java/eu/hbp/mip/model/DTOs/ExperimentDTO.java +++ b/src/main/java/eu/hbp/mip/model/DTOs/ExperimentDTO.java @@ -3,7 +3,7 @@ package eu.hbp.mip.model.DTOs; import eu.hbp.mip.model.DAOs.ExperimentDAO; import java.util.Date; -import java.util.List; +import java.util.Map; import java.util.UUID; public class ExperimentDTO { @@ -12,9 +12,11 @@ public class ExperimentDTO { private String name; private String createdBy; private Date created; + private Date updated; + private Date finished; private Boolean shared; private Boolean viewed; - private ExperimentDTO.ResultDTO result; + private Map result; private ExperimentDAO.Status status; private String algorithm; @@ -71,6 +73,22 @@ public class ExperimentDTO { public void setCreated(Date created) { this.created = created; } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Date getFinished() { + return finished; + } + + public void setFinished(Date finished) { + this.finished = finished; + } public Boolean getShared() { return shared; @@ -88,11 +106,11 @@ public class ExperimentDTO { this.viewed = viewed; } - public ExperimentDTO.ResultDTO getResult() { + public Map getResult() { return result; } - public void setResult(ExperimentDTO.ResultDTO result) { + public void setResult(Map result) { this.result = result; } @@ -104,37 +122,25 @@ public class ExperimentDTO { this.status = status; } - public static class OutputDTO { + public static class ResultDTO { - private String data; - private ExperimentDAO.MimeTypes mimeTypes; + private Object data; + private ExperimentDAO.Type type; - public String getData() { + public Object getData() { return this.data; } - public void setData(String data) { + public void setData(Object data) { this.data = data; } - public ExperimentDAO.MimeTypes getMimeTypes() { - return mimeTypes; - } - - public void setMimeTypes(ExperimentDAO.MimeTypes mimeTypes) { - this.mimeTypes = mimeTypes; - } - } - - public static class ResultDTO { - private List<OutputDTO> result; - - public List<OutputDTO> getResult() { - return this.result; + public ExperimentDAO.Type getType() { + return type; } - public void setResult(List<OutputDTO> result) { - this.result = result; + public void setType(ExperimentDAO.Type type) { + this.type = type; } } } diff --git a/src/main/java/eu/hbp/mip/repositories/ExperimentRepository.java b/src/main/java/eu/hbp/mip/repositories/ExperimentRepository.java index 5f2c8dc7643799b1a2f5a7a15773140376ff5756..7b30600e5d5575aa9a2b2f12909820382aec2142 100644 --- a/src/main/java/eu/hbp/mip/repositories/ExperimentRepository.java +++ b/src/main/java/eu/hbp/mip/repositories/ExperimentRepository.java @@ -13,5 +13,5 @@ import java.util.UUID; public interface ExperimentRepository extends CrudRepository<ExperimentDAO, UUID>, JpaSpecificationExecutor<ExperimentDAO> { - Optional<ExperimentDAO> findByUuid(UUID experimentUuid); + ExperimentDAO findByUuid(UUID experimentUuid); } diff --git a/src/main/java/eu/hbp/mip/services/ExperimentService.java b/src/main/java/eu/hbp/mip/services/ExperimentService.java index 2590e1edd05843edb56ef45b1d16e26eaa0f4415..12c0d126942063868c09334bc8fe456489d95238 100644 --- a/src/main/java/eu/hbp/mip/services/ExperimentService.java +++ b/src/main/java/eu/hbp/mip/services/ExperimentService.java @@ -27,7 +27,7 @@ import eu.hbp.mip.utils.JsonConverters; import eu.hbp.mip.utils.Logging; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -46,8 +46,6 @@ import static java.lang.Thread.sleep; @Service public class ExperimentService { - @Autowired - private UserInfo userInfo; @Value("#{'${services.exareme.queryExaremeUrl}'}") private String queryExaremeUrl; @@ -62,11 +60,16 @@ public class ExperimentService { @Value("#{'${hbp.authentication.enabled:1}'}") private boolean authenticationIsEnabled; - @Autowired - private ExperimentRepository experimentRepository; - private static final Gson gson = new Gson(); + private final UserInfo userInfo; + private final ExperimentRepository experimentRepository; + + public ExperimentService(@Qualifier("userInfo") UserInfo userInfo, ExperimentRepository experimentRepository) { + this.userInfo = userInfo; + this.experimentRepository = experimentRepository; + } + /** * The getExperiments will retrieve the experiments from database according to the filters. * @@ -76,23 +79,26 @@ public class ExperimentService { * @param viewed is optional, in case it is required to filter the experiments by viewed * @param page is the page that is required to be retrieve * @param size is the size of each page + * @param orderBy is the column that is required to ordered by + * @param descending is a boolean to determine if the experiments will be order by descending or ascending * @param endpoint is the endpoint that called the function * @return a list of mapped experiments */ - public Map getExperiments(String name,String algorithm, Boolean shared,Boolean viewed, int page, int size, String endpoint) { + public Map getExperiments(String name,String algorithm, Boolean shared,Boolean viewed, int page, int size, String orderBy, Boolean descending, String endpoint) { UserDAO user = userInfo.getUser(); Logging.LogUserAction(user.getUsername(), endpoint, "Listing my experiments."); if(size > 10 ) throw new BadRequestException("Invalid size input, max size is 10."); - - Specification<ExperimentDAO> spec = Specification.where(new ExperimentSpecifications.ExperimentWithName(name)) + Specification<ExperimentDAO> spec = Specification + .where(new ExperimentSpecifications.ExperimentWithName(name)) .and(new ExperimentSpecifications.ExperimentWithAlgorithm(algorithm)) .and(new ExperimentSpecifications.ExperimentWithShared(shared)) - .and(new ExperimentSpecifications.ExperimentWithViewed(viewed)); + .and(new ExperimentSpecifications.ExperimentWithViewed(viewed)) + .and(new ExperimentSpecifications.ExperimentOrderBy(orderBy, descending)); Pageable paging = PageRequest.of(page, size); - Page<ExperimentDAO> pageExperiments = experimentRepository.findAll(spec, paging); + Page<ExperimentDAO> pageExperiments = experimentRepository.findAll(spec, paging); List<ExperimentDAO> experimentDAOs = pageExperiments.getContent(); if (experimentDAOs.isEmpty()) @@ -124,7 +130,7 @@ public class ExperimentService { Logging.LogUserAction(user.getUsername(), endpoint, "Loading Experiment with uuid : " + uuid); - experimentDAO = loadExperiment(uuid).orElseThrow(() -> new ExperimentNotFoundException("Not found Experimnet with id = " + uuid)); + experimentDAO = loadExperiment(uuid, endpoint); if (!experimentDAO.isShared() && !experimentDAO.getCreatedBy().getUsername().equals(user.getUsername())) { Logging.LogUserAction(user.getUsername(), endpoint, "Accessing Experiment is unauthorized."); @@ -148,45 +154,16 @@ public class ExperimentService { UserDAO user = userInfo.getUser(); //Checking if check (POST) /experiments has proper input. - if (checkPostExperimentProperInput(experimentDTO)){ - Logging.LogUserAction(user.getUsername(), endpoint, - "Invalid input."); - throw new BadRequestException("Please provide proper input."); - } + checkPostExperimentProperInput(experimentDTO, endpoint); + // Get the type and name of algorithm String algorithmType = experimentDTO.getAlgorithmDetails().getType(); - String algorithmName = experimentDTO.getAlgorithmDetails().getName(); - StringBuilder parametersLogMessage = new StringBuilder(", Parameters:\n"); - experimentDTO.getAlgorithmDetails().getParameters().forEach( - params -> parametersLogMessage - .append(" ") - .append(params.getLabel()) - .append(" -> ") - .append(params.getValue()) - .append("\n") ); - Logging.LogUserAction(user.getUsername(), endpoint, "Executing " + algorithmName + parametersLogMessage); + algorithmParametersLogging(experimentDTO, endpoint); if (authenticationIsEnabled) { - // Getting the dataset from the experiment parameters - String experimentDatasets = null; - for (AlgorithmDTO.AlgorithmParamDTO parameter : experimentDTO.getAlgorithmDetails().getParameters()) { - if (parameter.getLabel().equals("dataset")) { - experimentDatasets = parameter.getValue(); - break; - } - } - - if (experimentDatasets == null || experimentDatasets.equals("")) { - Logging.LogUserAction(user.getUsername(), endpoint, - "A dataset should be specified to run an algorithm."); - throw new BadRequestException("Please provide at least one dataset to run the algorithm."); - } - - // --- Validating proper access rights on the datasets --- - if (!ClaimUtils.userHasDatasetsAuthorization(user.getUsername(), authentication.getAuthorities(), experimentDatasets)) { - throw new BadRequestException("You are not authorized to use these datasets."); - } + String experimentDatasets = getDatasetFromExperimentParameters(experimentDTO, endpoint); + ClaimUtils.validateAccessRightsOnDatasets(user.getUsername(), authentication.getAuthorities(), experimentDatasets); } // Run with the appropriate engine @@ -211,11 +188,7 @@ public class ExperimentService { UserDAO user = userInfo.getUser(); //Checking if check (POST) /experiments has proper input. - if (checkPostExperimentProperInput(experimentDTO)){ - Logging.LogUserAction(user.getUsername(), endpoint, - "Invalid input."); - throw new BadRequestException("Please provide proper input."); - } + checkPostExperimentProperInput(experimentDTO, endpoint); // Get the parameters List<AlgorithmDTO.AlgorithmParamDTO> algorithmParameters @@ -224,42 +197,17 @@ public class ExperimentService { // Get the type and name of algorithm String algorithmName = experimentDTO.getAlgorithmDetails().getName(); - if (!loadProperAlgorithms().contains(algorithmName)){ + if (!allowedTransientAlgorithms(algorithmName)){ Logging.LogUserAction(user.getUsername(), endpoint, "Not proper algorithm."); throw new BadRequestException("Please provide proper algorithm."); } - StringBuilder parametersLogMessage = new StringBuilder(", Parameters:\n"); - experimentDTO.getAlgorithmDetails().getParameters().forEach( - params -> parametersLogMessage - .append(" ") - .append(params.getLabel()) - .append(" -> ") - .append(params.getValue()) - .append("\n") ); - Logging.LogUserAction(user.getUsername(), endpoint, "Executing " + algorithmName + parametersLogMessage); + algorithmParametersLogging(experimentDTO, endpoint); if (authenticationIsEnabled) { - // Getting the dataset from the experiment parameters - String experimentDatasets = null; - for (AlgorithmDTO.AlgorithmParamDTO parameter : experimentDTO.getAlgorithmDetails().getParameters()) { - if (parameter.getLabel().equals("dataset")) { - experimentDatasets = parameter.getValue(); - break; - } - } - - if (experimentDatasets == null || experimentDatasets.equals("")) { - Logging.LogUserAction(user.getUsername(), endpoint, - "A dataset should be specified to run an algorithm."); - throw new BadRequestException("Please provide at least one dataset to run the algorithm."); - } - - // --- Validating proper access rights on the datasets --- - if (!ClaimUtils.userHasDatasetsAuthorization(user.getUsername(), authentication.getAuthorities(), experimentDatasets)) { - throw new BadRequestException("You are not authorized to use these datasets."); - } + String experimentDatasets = getDatasetFromExperimentParameters(experimentDTO, endpoint); + ClaimUtils.validateAccessRightsOnDatasets(user.getUsername(), authentication.getAuthorities(), experimentDatasets); } String body = gson.toJson(algorithmParameters); @@ -271,8 +219,11 @@ public class ExperimentService { // Results are stored in the experiment object ExaremeResult exaremeResult = runExaremeExperiment(url, body, experimentDTO); - experimentDTO.setResult(exaremeResult.result); - experimentDTO.setStatus((exaremeResult.code>= 400)? ExperimentDAO.Status.error: ExperimentDAO.Status.success); + + Logging.LogUserAction(user.getUsername(), endpoint, "Experiment with uuid: " + experimentDTO.getUuid() + "gave response code: " +exaremeResult.getCode() + " and result: "+ exaremeResult.getResults()); + + experimentDTO.setResult((exaremeResult.getCode()>= 400)? null: exaremeResult.getResults()); + experimentDTO.setStatus((exaremeResult.getCode()>= 400)? ExperimentDAO.Status.error: ExperimentDAO.Status.success); return experimentDTO; } @@ -289,41 +240,26 @@ public class ExperimentService { { ExperimentDAO experimentDAO; UserDAO user = userInfo.getUser(); - Logging.LogUserAction(user.getUsername(), endpoint, "Updating experiment with uuid : " + experimentDTO.getUuid() + "."); - //Checking if check (PUT) /experiments has proper input. - if (checkPutExperimentProperInput(experimentDTO)){ - Logging.LogUserAction(user.getUsername(), endpoint, - "Invalid input."); - throw new BadRequestException("Please provide proper input."); - } - - if((experimentDTO.getName() == null || experimentDTO.getName().length() == 0) - && experimentDTO.getShared() == null - && experimentDTO.getViewed() == null - && experimentDTO.getAlgorithmDetails() == null) - { - throw new BadRequestException("Input is required."); - } + Logging.LogUserAction(user.getUsername(), endpoint, "Updating experiment with uuid : " + uuid + "."); - experimentDAO = loadExperiment(uuid).orElseThrow(() -> new ExperimentNotFoundException("Not found Experimnet with id = " + uuid)); + experimentDAO = loadExperiment(uuid, endpoint); + //Verify (PATCH) /experiments non editable fields. + verifyPatchExperimentNonEditableFields(uuid, experimentDTO, experimentDAO, endpoint); + if (!experimentDAO.getCreatedBy().getUsername().equals(user.getUsername())) throw new UnauthorizedException("You don't have access to the experiment."); if(experimentDTO.getName() != null && experimentDTO.getName().length() != 0) - { experimentDAO.setName(experimentDTO.getName()); - } if(experimentDTO.getShared() != null) - { experimentDAO.setShared(experimentDTO.getShared()); - } if(experimentDTO.getViewed() != null) - { experimentDAO.setViewed(experimentDTO.getViewed()); - } + + experimentDAO.setUpdated(new Date()); try { experimentRepository.save(experimentDAO); @@ -332,7 +268,8 @@ public class ExperimentService { Logging.LogUserAction(user.getUsername(), endpoint, "Attempted to save changes to database but an error ocurred : " + e.getMessage() + "."); throw new InternalServerError(e.getMessage()); } - Logging.LogUserAction(user.getUsername(), endpoint, "Updated experiment with uuid : " + experimentDTO.getUuid() + "."); + + Logging.LogUserAction(user.getUsername(), endpoint, "Updated experiment with uuid : " + uuid + "."); experimentDTO = experimentDAO.convertToDTO(); return experimentDTO; @@ -350,42 +287,125 @@ public class ExperimentService { UserDAO user = userInfo.getUser(); Logging.LogUserAction(user.getUsername(), endpoint, "Deleting experiment with uuid : " + uuid + "."); - experimentDAO = loadExperiment(uuid).orElseThrow(() -> new ExperimentNotFoundException("Not found Experimnet with id = " + uuid)); + experimentDAO = loadExperiment(uuid, endpoint); if (!experimentDAO.getCreatedBy().getUsername().equals(user.getUsername())) throw new UnauthorizedException("You don't have access to the experiment."); - experimentRepository.delete(experimentDAO); + try { + experimentRepository.delete(experimentDAO); + } + catch (Exception e){ + Logging.LogUserAction(user.getUsername(), endpoint, "Attempted to delete an experiment to database but an error ocurred : " + e.getMessage() + "."); + throw new InternalServerError(e.getMessage()); + } Logging.LogUserAction(user.getUsername(), endpoint, "Deleted experiment with uuid : " + uuid + "."); } // /* ------------------------------- PRIVATE METHODS ----------------------------------------------------*/ - private boolean checkPostExperimentProperInput(ExperimentDTO experimentDTO) + + private void checkPostExperimentProperInput(ExperimentDTO experimentDTO, String endpoint) { - return experimentDTO.getShared() != null - || experimentDTO.getViewed() != null - || experimentDTO.getCreated() != null - || experimentDTO.getCreatedBy() != null - || experimentDTO.getResult() != null - || experimentDTO.getStatus() != null - || experimentDTO.getUuid() != null; + + boolean properInput = + experimentDTO.getShared() == null + && experimentDTO.getViewed() == null + && experimentDTO.getCreated() == null + && experimentDTO.getCreatedBy() == null + && experimentDTO.getResult() == null + && experimentDTO.getStatus() == null + && experimentDTO.getUuid() == null; + + if (!properInput){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "Invalid input."); + throw new BadRequestException("Please provide proper input."); + } } - private List<String> loadProperAlgorithms() + private boolean allowedTransientAlgorithms(String algorithmName) { List<String> properAlgorithms = new ArrayList<>(); - properAlgorithms.add("histograms"); - properAlgorithms.add("descriptive_stats"); - return properAlgorithms; + properAlgorithms.add("MULTIPLE_HISTOGRAMS"); + properAlgorithms.add("DESCRIPTIVE_STATS"); + return properAlgorithms.contains(algorithmName); } - private boolean checkPutExperimentProperInput(ExperimentDTO experimentDTO) + private void verifyPatchExperimentNonEditableFields(String uuid , ExperimentDTO experimentDTO, ExperimentDAO experimentDAO, String endpoint) { - return experimentDTO.getUuid() != null - || experimentDTO.getCreated() != null - || experimentDTO.getResult() != null - || experimentDTO.getStatus() != null; + if(experimentDTO.getUuid() != null && experimentDTO.getUuid().toString().compareTo(uuid) != 0){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "Uuid is not editable."); + throw new BadRequestException("Uuid is not editable."); + } + + if(experimentDTO.getAlgorithm() != null && experimentDTO.getAlgorithm().compareTo(experimentDAO.getAlgorithm()) != 0){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "Algorithm is not editable."); + throw new BadRequestException("Algorithm is not editable."); + } + + if(experimentDTO.getCreated() != null && experimentDTO.getCreatedBy().compareTo(experimentDAO.getCreatedBy().getUsername()) != 0){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "CreatedBy is not editable."); + throw new BadRequestException("CreatedBy is not editable."); + } + + if(experimentDTO.getAlgorithmDetails() != null && JsonConverters.convertObjectToJsonString(experimentDTO.getAlgorithmDetails()).compareTo(experimentDAO.getAlgorithmDetails()) != 0){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "AlgorithmDetails is not editable."); + throw new BadRequestException("AlgorithmDetails is not editable."); + } + + if(experimentDTO.getCreated() != null && experimentDTO.getAlgorithm().compareTo(experimentDAO.getAlgorithm()) != 0){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "Created is not editable."); + throw new BadRequestException("Created is not editable."); + } + + if(experimentDTO.getResult() != null && JsonConverters.convertObjectToJsonString(experimentDTO.getResult()).compareTo(experimentDAO.getResult()) != 0){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "Status is not editable."); + throw new BadRequestException("Status is not editable."); + } + + if(experimentDTO.getStatus() != null && experimentDTO.getStatus().compareTo(experimentDAO.getStatus()) != 0){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "Status is not editable."); + throw new BadRequestException("Status is not editable."); + } + } + + private void algorithmParametersLogging(ExperimentDTO experimentDTO, String endpoint) { + UserDAO user = userInfo.getUser() ; + String algorithmName = experimentDTO.getAlgorithm(); + StringBuilder parametersLogMessage = new StringBuilder(", Parameters:\n"); + experimentDTO.getAlgorithmDetails().getParameters().forEach( + params -> parametersLogMessage + .append(" ") + .append(params.getLabel()) + .append(" -> ") + .append(params.getValue()) + .append("\n") ); + Logging.LogUserAction(user.getUsername(), endpoint, "Executing " + algorithmName + parametersLogMessage); + } + + /** + * The getDatasetFromExperimentParameters will retrieve the dataset from the experiment parameters + * + * @param experimentDTO is the experiment information + * @param endpoint is the endpoint that called the function + * @return the dataset from the experiment + */ + private String getDatasetFromExperimentParameters(ExperimentDTO experimentDTO, String endpoint) { + + String experimentDatasets = null; + for (AlgorithmDTO.AlgorithmParamDTO parameter : experimentDTO.getAlgorithmDetails().getParameters()) { + if (parameter.getLabel().equals("dataset")) { + experimentDatasets = parameter.getValue(); + break; + } + } + + if (experimentDatasets == null || experimentDatasets.equals("")) { + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, + "A dataset should be specified to run an algorithm."); + throw new BadRequestException("Please provide at least one dataset to run the algorithm."); + } + return experimentDatasets; } /** @@ -394,14 +414,26 @@ public class ExperimentService { * @param uuid is the id of the experiment to be retrieved * @return the experiment information that was retrieved from database */ - private Optional<ExperimentDAO> loadExperiment(String uuid){ - - + private ExperimentDAO loadExperiment(String uuid, String endpoint){ UUID experimentUuid ; + ExperimentDAO experimentDAO; + + try { + experimentUuid = UUID.fromString(uuid); + } + catch (Exception e) + { + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, e.getMessage()); + throw new BadRequestException(e.getMessage()); + } - experimentUuid = Optional.of(UUID.fromString(uuid)).orElseThrow(() -> new IllegalArgumentException("Invalid input uuid:"+ uuid)); + experimentDAO = experimentRepository.findByUuid(experimentUuid); + if (experimentDAO == null){ + Logging.LogUserAction(userInfo.getUser().getUsername(), endpoint, "Experiment with uuid : " + uuid + "was not found."); + throw new ExperimentNotFoundException("Experiment with uuid : " + uuid + " was not found."); + } - return experimentRepository.findByUuid(experimentUuid); + return experimentDAO; } /** @@ -411,6 +443,7 @@ public class ExperimentService { * @return the experiment information that was inserted into the database */ private ExperimentDAO createExperimentInTheDatabase(ExperimentDTO experimentDTO, String endpoint) { + UserDAO user = userInfo.getUser(); ExperimentDAO experimentDAO = new ExperimentDAO(); @@ -420,6 +453,7 @@ public class ExperimentService { experimentDAO.setAlgorithm(experimentDTO.getAlgorithm()); experimentDAO.setName(experimentDTO.getName()); experimentDAO.setStatus(ExperimentDAO.Status.pending); + try { experimentRepository.save(experimentDAO); } @@ -456,6 +490,7 @@ public class ExperimentService { private void finishExperiment(ExperimentDAO experimentDAO, String endpoint) { experimentDAO.setFinished(new Date()); + try { experimentRepository.save(experimentDAO); } @@ -508,8 +543,10 @@ public class ExperimentService { // Results are stored in the experiment object ExaremeResult exaremeResult = runExaremeExperiment(url, body, finalExperimentDTO); - experimentDAO.setResult(JsonConverters.convertObjectToJsonString(exaremeResult.result)); - experimentDAO.setStatus((exaremeResult.code>= 400)? ExperimentDAO.Status.error: ExperimentDAO.Status.success); + Logging.LogExperimentAction(experimentDAO.getName(), experimentDAO.getUuid(), "Experiment with uuid: " + experimentDAO.getUuid() + "gave response code: " +exaremeResult.getCode() + " and result: "+ exaremeResult.getResults()); + + experimentDAO.setResult((exaremeResult.getCode()>= 400)? null : JsonConverters.convertObjectToJsonString(exaremeResult.getResults())); + experimentDAO.setStatus((exaremeResult.getCode()>= 400)? ExperimentDAO.Status.error: ExperimentDAO.Status.success); } catch (Exception e) { Logging.LogExperimentAction(experimentDAO.getName(), experimentDAO.getUuid(), "There was an exception: " + e.getMessage()); @@ -539,13 +576,13 @@ public class ExperimentService { code = HTTPUtil.sendPost(url, body, results); } catch (Exception e){ - throw new InternalServerError("Error occured : "+ e.getMessage()); + throw new InternalServerError("Error occurred : "+ e.getMessage()); } Logging.LogExperimentAction(experimentDTO.getName(), experimentDTO.getUuid(), "Algorithm finished with code: " + code); // Results are stored in the experiment object - ExperimentDTO.ResultDTO resultDTO = JsonConverters.convertJsonStringToObject(String.valueOf(results), ExperimentDTO.ResultDTO.class); - return new ExaremeResult(code, resultDTO); + Map resultsDTO = ExperimentDAO.convertJsonStringToResult(String.valueOf(results)); + return new ExaremeResult(code, resultsDTO); } @@ -952,21 +989,21 @@ public class ExperimentService { return returnError; } - final class ExaremeResult { - private final int code; - private final ExperimentDTO.ResultDTO result; + static class ExaremeResult { + private int code; + private Map results; - public ExaremeResult(int code, ExperimentDTO.ResultDTO result) { + public ExaremeResult(int code, Map results) { this.code = code; - this.result = result; + this.results = results; } public int getCode() { return code; } - public ExperimentDTO.ResultDTO getResult() { - return result; + public Map getResults() { + return results; } } } diff --git a/src/main/java/eu/hbp/mip/services/ExperimentSpecifications.java b/src/main/java/eu/hbp/mip/services/ExperimentSpecifications.java index 076249e0becac36fbb64db83eae5e758c56d4161..f59faa9ed73017bd9662f15a382fe1252925c2a4 100644 --- a/src/main/java/eu/hbp/mip/services/ExperimentSpecifications.java +++ b/src/main/java/eu/hbp/mip/services/ExperimentSpecifications.java @@ -1,12 +1,15 @@ package eu.hbp.mip.services; import eu.hbp.mip.model.DAOs.ExperimentDAO; +import eu.hbp.mip.utils.Exceptions.BadRequestException; import org.springframework.data.jpa.domain.Specification; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import java.util.ArrayList; +import java.util.List; public class ExperimentSpecifications { public static class ExperimentWithName implements Specification<ExperimentDAO> { @@ -65,7 +68,7 @@ public class ExperimentSpecifications { } } - public static class ExperimentWithShared implements org.springframework.data.jpa.domain.Specification<ExperimentDAO> { + public static class ExperimentWithShared implements Specification<ExperimentDAO> { private Boolean shared; @@ -79,7 +82,47 @@ public class ExperimentSpecifications { } return cb.equal(root.get("shared"), this.shared); } + } + + public static class ExperimentOrderBy implements Specification<ExperimentDAO> { + + private String orderBy; + private Boolean descending ; + public ExperimentOrderBy(String orderBy, Boolean descending){ + if (properColumnToBeOrderedBy(orderBy)) + this.orderBy = orderBy; + else + throw new BadRequestException("Please provide proper column to order by."); + if(descending == null) + this.descending = true; + else + this.descending = descending; + } + public Predicate toPredicate(Root<ExperimentDAO> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) { + if (descending) { + criteriaQuery.orderBy(cb.desc(root.get(orderBy))); + } else { + criteriaQuery.orderBy(cb.asc(root.get(orderBy))); + } + return cb.isTrue(cb.literal(true)); + } + + } + + public static boolean properColumnToBeOrderedBy(String column){ + { + List<String> properColumns = new ArrayList<>(); + properColumns.add("uuid"); + properColumns.add("name"); + properColumns.add("created_by_username"); + properColumns.add("algorithm"); + properColumns.add("created"); + properColumns.add("status"); + properColumns.add("shared"); + properColumns.add("viewed"); + return properColumns.contains(column); + } } } diff --git a/src/main/java/eu/hbp/mip/utils/ClaimUtils.java b/src/main/java/eu/hbp/mip/utils/ClaimUtils.java index 5872819bc883efd98a453e363a46ad0a1cb537cd..dea4cd0b5e898f385562a044749970108c592cfc 100644 --- a/src/main/java/eu/hbp/mip/utils/ClaimUtils.java +++ b/src/main/java/eu/hbp/mip/utils/ClaimUtils.java @@ -2,6 +2,7 @@ package eu.hbp.mip.utils; import com.google.gson.Gson; import eu.hbp.mip.model.DTOs.PathologyDTO; +import eu.hbp.mip.utils.Exceptions.BadRequestException; import org.springframework.security.core.GrantedAuthority; import java.util.ArrayList; @@ -22,7 +23,7 @@ public class ClaimUtils { return "dataset_" + datasetCode.toLowerCase(); } - public static boolean userHasDatasetsAuthorization(String username, Collection<? extends GrantedAuthority> authorities, + public static void validateAccessRightsOnDatasets(String username, Collection<? extends GrantedAuthority> authorities, String experimentDatasets) { List<String> userClaims = Arrays.asList(authorities.toString().toLowerCase() @@ -37,13 +38,12 @@ public class ClaimUtils { if (!userClaims.contains(datasetRole.toLowerCase())) { Logging.LogUserAction(username, "(POST) /experiments/runAlgorithm", "You are not allowed to use dataset: " + dataset); - return false; + throw new BadRequestException("You are not authorized to use these datasets."); } } Logging.LogUserAction(username, "(POST) /experiments/runAlgorithm", "User is authorized to use the datasets: " + experimentDatasets); } - return true; } public static String getAuthorizedPathologies(String username, Collection<? extends GrantedAuthority> authorities, diff --git a/src/main/java/eu/hbp/mip/utils/JsonConverters.java b/src/main/java/eu/hbp/mip/utils/JsonConverters.java index 52305124aff3c2078d6c87ba95c1b4235782da5a..74d01b851f44877fffe0e146d8f678ee3fb95dfb 100644 --- a/src/main/java/eu/hbp/mip/utils/JsonConverters.java +++ b/src/main/java/eu/hbp/mip/utils/JsonConverters.java @@ -7,7 +7,7 @@ import com.google.gson.Gson; import java.lang.reflect.Type; public class JsonConverters { - Gson gson = new Gson(); + private static final Gson gson = new Gson(); public static String convertObjectToJsonString(Object object) { ObjectMapper mapper = new ObjectMapper(); @@ -22,6 +22,6 @@ public class JsonConverters { public static <T> T convertJsonStringToObject(String jsonString, Type typeOfT) { if(jsonString == null || jsonString.isEmpty()) return null; - return new Gson().fromJson(jsonString, typeOfT); + return gson.fromJson(jsonString, typeOfT); } } diff --git a/src/main/resources/db/migration/V7_0__NewDatabaseStructure.sql b/src/main/resources/db/migration/V7_0__NewDatabaseStructure.sql index 73a15addd8f1a7cd34594b9708595f7a4447bfb8..5e1bcc6fb1e7ef9931db031241ff39097306069c 100644 --- a/src/main/resources/db/migration/V7_0__NewDatabaseStructure.sql +++ b/src/main/resources/db/migration/V7_0__NewDatabaseStructure.sql @@ -15,6 +15,9 @@ RENAME resultsviewed TO viewed; ALTER TABLE experiment RENAME workflowstatus TO status; +ALTER TABLE experiment +ADD COLUMN updated timestamp without time zone; + ALTER TABLE experiment ADD COLUMN algorithm text;