diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index c4fe58e4..1e1be369 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -56,3 +56,9 @@ services: depends_on: - damap-fe - damap-be + +# Uncomment the following block to persist postgres data. +# volumes: +# - damap-db-data:/var/lib/postgresql/data +#volumes: +# damap-db-data: diff --git a/src/main/java/org/damap/base/conversion/AbstractTemplateExportScienceEuropeComponents.java b/src/main/java/org/damap/base/conversion/AbstractTemplateExportScienceEuropeComponents.java index ac6047b2..c5a70d8d 100644 --- a/src/main/java/org/damap/base/conversion/AbstractTemplateExportScienceEuropeComponents.java +++ b/src/main/java/org/damap/base/conversion/AbstractTemplateExportScienceEuropeComponents.java @@ -405,10 +405,19 @@ public void storageInformation() { // written in section 5 if (!distVar.toString().isEmpty()) { String storageDescription = ""; - storageDescription = - internalStorageTranslationRepo - .getInternalStorageById(((Storage) host).getInternalStorageId().id, "eng") - .getDescription(); + InternalStorageTranslation storageTranslation = + internalStorageTranslationRepo.getInternalStorageById( + ((Storage) host).getInternalStorageId().id, "eng"); + + if (storageTranslation == null) { + storageTranslation = + internalStorageTranslationRepo + .getAllInternalStorageTranslationsByStorageId( + ((Storage) host).getInternalStorageId().id) + .get(0); + } + + storageDescription = storageTranslation.getDescription(); storageVar = storageVar.concat( distVar diff --git a/src/main/java/org/damap/base/domain/InternalStorage.java b/src/main/java/org/damap/base/domain/InternalStorage.java index 2ad7957e..f5544ea2 100644 --- a/src/main/java/org/damap/base/domain/InternalStorage.java +++ b/src/main/java/org/damap/base/domain/InternalStorage.java @@ -29,4 +29,7 @@ public class InternalStorage extends PanacheEntity { @Column(name = "backup_location") private String backupLocation; + + @Column(name = "active") + private boolean active; } diff --git a/src/main/java/org/damap/base/exception/ConstraintViolationExceptionMapper.java b/src/main/java/org/damap/base/exception/ConstraintViolationExceptionMapper.java new file mode 100644 index 00000000..4700d882 --- /dev/null +++ b/src/main/java/org/damap/base/exception/ConstraintViolationExceptionMapper.java @@ -0,0 +1,38 @@ +package org.damap.base.exception; + +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import java.util.List; +import java.util.Map; + +@Provider +public class ConstraintViolationExceptionMapper + implements ExceptionMapper { + + @Override + public Response toResponse(ConstraintViolationException exception) { + // Extract the constraint violations and format them into a response-friendly structure + List> violations = + exception.getConstraintViolations().stream() + .map( + violation -> + Map.of( + "path", violation.getPropertyPath().toString(), + "message", violation.getMessage(), + "invalidValue", String.valueOf(violation.getInvalidValue()))) + .toList(); + + // Create the response body with the violations + Map responseBody = + Map.of("exception", "ConstraintViolationException", "violations", violations); + + // Build and return the response + return Response.status(Response.Status.BAD_REQUEST) + .entity(responseBody) + .type(MediaType.APPLICATION_JSON) + .build(); + } +} diff --git a/src/main/java/org/damap/base/repo/InternalStorageRepo.java b/src/main/java/org/damap/base/repo/InternalStorageRepo.java index 43064fc5..4ce340e3 100644 --- a/src/main/java/org/damap/base/repo/InternalStorageRepo.java +++ b/src/main/java/org/damap/base/repo/InternalStorageRepo.java @@ -1,13 +1,13 @@ package org.damap.base.repo; -import io.quarkus.hibernate.orm.panache.PanacheRepository; import jakarta.enterprise.context.ApplicationScoped; import java.util.List; import org.damap.base.domain.InternalStorage; +import org.damap.base.repo.base.RepoSearch; /** InternalStorageRepo class. */ @ApplicationScoped -public class InternalStorageRepo implements PanacheRepository { +public class InternalStorageRepo implements RepoSearch { /** * getAll. diff --git a/src/main/java/org/damap/base/repo/InternalStorageTranslationRepo.java b/src/main/java/org/damap/base/repo/InternalStorageTranslationRepo.java index 87c07177..1115d98d 100644 --- a/src/main/java/org/damap/base/repo/InternalStorageTranslationRepo.java +++ b/src/main/java/org/damap/base/repo/InternalStorageTranslationRepo.java @@ -39,4 +39,30 @@ public InternalStorageTranslation getInternalStorageById(Long storageId, String Parameters.with("storageId", storageId).and("languageCode", languageCode)) .firstResult(); } + + public List getAllInternalStorageTranslationsByStorageId( + Long storageId) { + return list( + "select storage from InternalStorageTranslation storage" + + " where storage.internalStorageId.id = :storageId ", + Parameters.with("storageId", storageId)); + } + + public boolean existsTranslationForStorageIdAndLanguageCode(Long storageId, String languageCode) { + return count("internalStorageId.id = ?1 and languageCode = ?2", storageId, languageCode) > 0; + } + + public boolean existsTranslationForStorageIdAndLanguageCodeExceptId( + Long storageId, String languageCode, Long id) { + return count( + "internalStorageId.id = ?1 and languageCode = ?2 and id != ?3", + storageId, + languageCode, + id) + > 0; + } + + public void deleteAllTranslationsForInternalStorage(Long storageId) { + delete("internalStorageId.id", storageId); + } } diff --git a/src/main/java/org/damap/base/repo/StorageRepo.java b/src/main/java/org/damap/base/repo/StorageRepo.java new file mode 100644 index 00000000..3533e4d8 --- /dev/null +++ b/src/main/java/org/damap/base/repo/StorageRepo.java @@ -0,0 +1,16 @@ +package org.damap.base.repo; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; +import org.damap.base.domain.InternalStorage; +import org.damap.base.domain.Storage; + +/** InternalStorageRepo class. */ +@ApplicationScoped +public class StorageRepo implements PanacheRepository { + + public List findByInternalStorageId(InternalStorage internalStorage) { + return list("internalStorageId", internalStorage); + } +} diff --git a/src/main/java/org/damap/base/repo/base/RepoSearch.java b/src/main/java/org/damap/base/repo/base/RepoSearch.java new file mode 100644 index 00000000..5e3c465d --- /dev/null +++ b/src/main/java/org/damap/base/repo/base/RepoSearch.java @@ -0,0 +1,55 @@ +package org.damap.base.repo.base; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; +import jakarta.ws.rs.core.MultivaluedMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface RepoSearch extends PanacheRepository { + + // TODO: Add pagination + /** + * Search by parameters. Automatically generates a query based on the parameters and values in the + * query parameters. + * + * @param queryParams a {@link jakarta.ws.rs.core.MultivaluedMap} object, containing the attribute + * as key and the values as values in an array + * @return a {@link java.util.List} object, containing the results + */ + default List searchByParameters(MultivaluedMap queryParams) { + + StringBuilder query = new StringBuilder(); + + Map params = new HashMap<>(); + + int counter = 0; + + for (Map.Entry> entry : queryParams.entrySet()) { + String key = entry.getKey(); + List values = entry.getValue(); + + if (values.isEmpty()) { + continue; + } + + if (counter != 0) { + query.append(" AND "); + } + + query.append(" ( "); + + query.append(key).append(" = :").append(key).append("0"); + params.put(key + "0", values.get(0)); + for (int i = 1; i < values.size(); i++) { + query.append(" OR %s = :%s".formatted(key, key + i)); + params.put(key + i, values.get(i)); + } + query.append(" )"); + + counter++; + } + + return list(query.toString(), params); + } +} diff --git a/src/main/java/org/damap/base/rest/InternalStorageResource.java b/src/main/java/org/damap/base/rest/InternalStorageResource.java index 704395ff..ea1369fe 100644 --- a/src/main/java/org/damap/base/rest/InternalStorageResource.java +++ b/src/main/java/org/damap/base/rest/InternalStorageResource.java @@ -1,36 +1,134 @@ package org.damap.base.rest; import io.quarkus.security.Authenticated; +import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import java.util.List; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; import lombok.extern.jbosslog.JBossLog; +import org.damap.base.rest.base.ResultList; +import org.damap.base.rest.base.resource.ResourceCreate; +import org.damap.base.rest.base.resource.ResourceDelete; +import org.damap.base.rest.base.resource.ResourceRead; +import org.damap.base.rest.base.resource.ResourceSearch; +import org.damap.base.rest.base.resource.ResourceUpdate; import org.damap.base.rest.storage.InternalStorageDO; import org.damap.base.rest.storage.InternalStorageService; -import org.jboss.resteasy.reactive.RestPath; /** InternalStorageResource class. */ @Path("/api/storages") @Authenticated @Produces(MediaType.APPLICATION_JSON) @JBossLog -public class InternalStorageResource { +public class InternalStorageResource + implements ResourceRead, + ResourceCreate, + ResourceUpdate, + ResourceDelete, + ResourceSearch { @Inject InternalStorageService internalStorageService; /** - * getAllByLanguage. + * create a new internal storage option. * - * @param languageCode a {@link java.lang.String} object - * @return a {@link java.util.List} object + * @param data a {@link org.damap.base.rest.storage.InternalStorageDO} object + * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object, with the ID set */ + @Override + @POST + @Path("") + @Consumes(MediaType.APPLICATION_JSON) + @RolesAllowed("Damap Admin") + public InternalStorageDO create(@Valid InternalStorageDO data) { + log.debug("Create internal storage option"); + log.debug(data); + + try { + return internalStorageService.create(data); + } catch (ClientErrorException e) { + throw new ClientErrorException( + Response.status(Response.Status.BAD_REQUEST) + .entity("{\"message\":\"" + e.getMessage() + "\"}") + .type(MediaType.APPLICATION_JSON) + .build()); + } + } + + /** + * read a specific internal storage option. + * + * @param id a {@link java.lang.String} object + * @param uriInfo a {@link jakarta.ws.rs.core.UriInfo} object + * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object + */ + @Override + @GET + @Path("/{id}") + public InternalStorageDO read(@PathParam("id") String id, @Context UriInfo uriInfo) { + log.debug("Read internal storage option with id " + id); + + try { + return internalStorageService.read(id, uriInfo.getQueryParameters()); + } catch (NumberFormatException e) { + log.error("Invalid internal storage ID: " + id); + throw new ClientErrorException( + Response.status(Response.Status.BAD_REQUEST) + .entity( + "{\"message\":\"Invalid internal storage ID: " + id + " - must be a number\"}") + .type(MediaType.APPLICATION_JSON) + .build()); + } + } + + /** + * update an existing internal storage option. + * + * @param id a {@link java.lang.String} object representing the ID of the internal storage option + * to update + * @param data a {@link org.damap.base.rest.storage.InternalStorageDO} object representing the new + * data + * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object, with the updated data + */ + @Override + @PUT + @Path("/{id}") + @Consumes(MediaType.APPLICATION_JSON) + @RolesAllowed("Damap Admin") + public InternalStorageDO update(@PathParam("id") String id, @Valid InternalStorageDO data) { + log.debug("Update internal storage option with id " + id); + log.debug(data); + return internalStorageService.update(id, data); + } + + /** + * delete an existing internal storage option. + * + * @param id a {@link java.lang.String} object, representing the ID of the internal storage option + * to delete + */ + @Override + @DELETE + @RolesAllowed("Damap Admin") + @Path("/{id}") + public void delete(@PathParam("id") String id) { + log.debug("Delete internal storage option with id " + id); + internalStorageService.delete(id); + } + + /** + * search for internal storage options. + * + * @param uriInfo a {@link jakarta.ws.rs.core.UriInfo} object, representing the query parameters + * @return a ResultList of {@link org.damap.base.rest.storage.InternalStorageDO} objects, that + * match the criteria + */ + @Override @GET - @Path("/{languageCode}") - public List getAllByLanguage(@RestPath String languageCode) { - log.debug("Return all internal storage options for language " + languageCode); - return internalStorageService.getAllByLanguage(languageCode); + @Consumes(MediaType.APPLICATION_JSON) + public ResultList search(@Context UriInfo uriInfo) { + log.debug("Search internal storage options"); + return internalStorageService.search(uriInfo.getQueryParameters()); } } diff --git a/src/main/java/org/damap/base/rest/InternalStorageTranslationResource.java b/src/main/java/org/damap/base/rest/InternalStorageTranslationResource.java new file mode 100644 index 00000000..09670e3f --- /dev/null +++ b/src/main/java/org/damap/base/rest/InternalStorageTranslationResource.java @@ -0,0 +1,103 @@ +package org.damap.base.rest; + +import io.quarkus.security.Authenticated; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.extern.jbosslog.JBossLog; +import org.damap.base.rest.base.resource.*; +import org.damap.base.rest.storage.InternalStorageTranslationDO; +import org.damap.base.rest.storage.InternalStorageTranslationService; + +@Path("/api/storages/{storageId}/translations") +@Authenticated +@Produces(MediaType.APPLICATION_JSON) +@JBossLog +public class InternalStorageTranslationResource + implements ResourceRead, + ResourceCreate, + ResourceUpdate, + ResourceDelete { + + @Inject InternalStorageTranslationService internalStorageTranslationService; + + private static final String MESSAGE_START = "{\"message\":\""; + + @Override + @RolesAllowed("Damap Admin") + public InternalStorageTranslationDO create(@Valid InternalStorageTranslationDO data) { + log.debug("Create internal storage translation"); + log.debug(data); + + try { + return internalStorageTranslationService.create(data); + } catch (ClientErrorException e) { + throw new ClientErrorException( + Response.status(Response.Status.BAD_REQUEST) + .entity(MESSAGE_START + e.getMessage() + "\"}") + .type(MediaType.APPLICATION_JSON) + .build()); + } + } + + @Override + @RolesAllowed("Damap Admin") + public void delete(String id) { + log.info("Delete internal storage translation with id " + id); + try { + internalStorageTranslationService.delete(id); + } catch (NotFoundException e) { + throw e; + } catch (ClientErrorException e) { + throw new ClientErrorException( + Response.status(Response.Status.BAD_REQUEST) + .entity(MESSAGE_START + e.getMessage() + "\"}") + .type(MediaType.APPLICATION_JSON) + .build()); + } + } + + @Override + public InternalStorageTranslationDO read(String id, UriInfo uriInfo) { + log.info("Read internal storage translation with id " + id); + + try { + return internalStorageTranslationService.read(id, uriInfo.getQueryParameters()); + } catch (NumberFormatException e) { + log.error("Invalid internal storage translation ID: " + id); + throw new ClientErrorException( + "Invalid internal storage translation ID: " + id + " - must be a number", + Response.Status.BAD_REQUEST); + } + } + + @Override + @RolesAllowed("Damap Admin") + public InternalStorageTranslationDO update(String id, @Valid InternalStorageTranslationDO data) { + log.info("Update internal storage translation with id " + id); + log.info(data); + try { + return internalStorageTranslationService.update(id, data); + } catch (NotFoundException e) { + throw e; + } catch (ClientErrorException e) { + throw new ClientErrorException( + Response.status(Response.Status.BAD_REQUEST) + .entity(MESSAGE_START + e.getMessage() + "\"}") + .type(MediaType.APPLICATION_JSON) + .build()); + } + } + + @GET + public List getAllByStorageId(@PathParam("storageId") String id) { + log.info("getAll: " + id); + + return internalStorageTranslationService.getAllByStorageId(id); + } +} diff --git a/src/main/java/org/damap/base/rest/base/resource/ResourceCreate.java b/src/main/java/org/damap/base/rest/base/resource/ResourceCreate.java index ca3da59d..470b7b5b 100644 --- a/src/main/java/org/damap/base/rest/base/resource/ResourceCreate.java +++ b/src/main/java/org/damap/base/rest/base/resource/ResourceCreate.java @@ -1,5 +1,6 @@ package org.damap.base.rest.base.resource; +import jakarta.validation.Valid; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -17,5 +18,5 @@ public interface ResourceCreate { @POST @Path("") @Consumes(MediaType.APPLICATION_JSON) - E create(S data); + E create(@Valid S data); } diff --git a/src/main/java/org/damap/base/rest/base/resource/ResourceUpdate.java b/src/main/java/org/damap/base/rest/base/resource/ResourceUpdate.java index 2d64e51a..85c65848 100644 --- a/src/main/java/org/damap/base/rest/base/resource/ResourceUpdate.java +++ b/src/main/java/org/damap/base/rest/base/resource/ResourceUpdate.java @@ -1,5 +1,6 @@ package org.damap.base.rest.base.resource; +import jakarta.validation.Valid; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; @@ -19,5 +20,5 @@ public interface ResourceUpdate { @PUT @Path("/{id}") @Consumes(MediaType.APPLICATION_JSON) - E update(@PathParam("id") String id, S data); + E update(@PathParam("id") String id, @Valid S data); } diff --git a/src/main/java/org/damap/base/rest/base/service/ServiceSearch.java b/src/main/java/org/damap/base/rest/base/service/ServiceSearch.java index 46338bbd..122c3f6f 100644 --- a/src/main/java/org/damap/base/rest/base/service/ServiceSearch.java +++ b/src/main/java/org/damap/base/rest/base/service/ServiceSearch.java @@ -1,5 +1,6 @@ package org.damap.base.rest.base.service; +import jakarta.ws.rs.core.MultivaluedHashMap; import jakarta.ws.rs.core.MultivaluedMap; import org.damap.base.rest.base.ResultList; @@ -12,4 +13,20 @@ public interface ServiceSearch { * @return a {@link org.damap.base.rest.base.ResultList} object */ ResultList search(MultivaluedMap queryParams); + + default Object convertValue(Class type, String value) { + if (type == Long.class) { + return Long.parseLong(value); + } else if (type == Boolean.class) { + return Boolean.parseBoolean(value); + } else if (type == String.class) { + return value; + } else { + throw new IllegalArgumentException("Unsupported type: " + type); + } + } + + default MultivaluedMap getEntityFields() { + return new MultivaluedHashMap<>(); + } } diff --git a/src/main/java/org/damap/base/rest/dmp/mapper/DmpDOMapper.java b/src/main/java/org/damap/base/rest/dmp/mapper/DmpDOMapper.java index 8cf1016f..69716b28 100644 --- a/src/main/java/org/damap/base/rest/dmp/mapper/DmpDOMapper.java +++ b/src/main/java/org/damap/base/rest/dmp/mapper/DmpDOMapper.java @@ -175,6 +175,7 @@ public DmpDO mapEntityToDO(Dmp dmp, DmpDO dmpDO) { * @return a {@link org.damap.base.domain.Dmp} object */ public Dmp mapDOtoEntity(DmpDO dmpDO, Dmp dmp, MapperService mapperService) { + if (dmpDO.getId() != null) dmp.id = dmpDO.getId(); dmp.setTitle(dmpDO.getTitle()); dmp.setDescription(dmpDO.getDescription()); diff --git a/src/main/java/org/damap/base/rest/dmp/mapper/MapperService.java b/src/main/java/org/damap/base/rest/dmp/mapper/MapperService.java index 3e0e5605..1cf4a936 100644 --- a/src/main/java/org/damap/base/rest/dmp/mapper/MapperService.java +++ b/src/main/java/org/damap/base/rest/dmp/mapper/MapperService.java @@ -2,6 +2,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import java.util.List; import lombok.extern.jbosslog.JBossLog; import org.damap.base.domain.*; import org.damap.base.r3data.RepositoriesService; @@ -67,12 +68,17 @@ public InternalStorage getInternalStorageById(Long id) { * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object */ public InternalStorageDO getInternalStorageDOById(Long id, String languageCode) { - InternalStorageTranslation internalStorageTranslation = - internalStorageTranslationRepo.getInternalStorageById(id, languageCode); - if (internalStorageTranslation != null) - return InternalStorageDOMapper.mapEntityToDO( - internalStorageTranslation, new InternalStorageDO()); - return null; + + InternalStorage internalStorage = internalStorageRepo.findById(id); + if (internalStorage == null) { + return null; + } + + List translations = + internalStorageTranslationRepo.getAllInternalStorageTranslationsByStorageId(id); + + return InternalStorageDOMapper.mapEntityToDO( + internalStorage, new InternalStorageDO(), translations); } /** diff --git a/src/main/java/org/damap/base/rest/dmp/service/DmpConsistencyUtility.java b/src/main/java/org/damap/base/rest/dmp/service/DmpConsistencyUtility.java index a2a1fab4..31d6b902 100644 --- a/src/main/java/org/damap/base/rest/dmp/service/DmpConsistencyUtility.java +++ b/src/main/java/org/damap/base/rest/dmp/service/DmpConsistencyUtility.java @@ -1,20 +1,29 @@ package org.damap.base.rest.dmp.service; +import jakarta.validation.ValidationException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; import lombok.experimental.UtilityClass; +import lombok.extern.jbosslog.JBossLog; +import org.damap.base.domain.*; import org.damap.base.enums.*; import org.damap.base.rest.dmp.domain.DatasetDO; import org.damap.base.rest.dmp.domain.DmpDO; import org.damap.base.rest.dmp.domain.HostDO; +import org.damap.base.rest.dmp.domain.StorageDO; +import org.damap.base.rest.storage.InternalStorageService; /** DmpConsistencyUtility class. */ @UtilityClass +@JBossLog public class DmpConsistencyUtility { + private static final String STORAGE_NOT_ACTIVE = "Storage is not active"; + /** * Adapts a given DMP so that it's information is consistent. * @@ -45,6 +54,56 @@ public void enforceDmpConsistency(DmpDO dmpDO) { enforceDatasetConsistency(dmpDO); } + /** + * Validates that all internal storage used in the new DMP are active. If there is no old DMP, it + * retrieves the storage IDs from the new DMP and checks their active status using the {@link + * InternalStorageService}. If comparing with an old DMP, the method checks any new storage IDs + * added in the new DMP and ensures they are active. If any storage is inactive, a {@link + * ValidationException} is thrown. A storage that is already in the old DMP is not checked for + * active status. + * + * @param newDmp the new DMP + * @param oldDmp the old DMP + * @param internalStorageService the internal storage service + * @throws ValidationException if at least one storage is not active + */ + public void enforceActiveStorage( + DmpDO newDmp, Dmp oldDmp, InternalStorageService internalStorageService) + throws ValidationException { + + // New DMP used storage IDs + List newDmpStorageIds = new ArrayList<>(); + for (StorageDO storageDO : newDmp.getStorage()) { + newDmpStorageIds.add(storageDO.getInternalStorageId()); + } + + // Old DMP used storage IDs or empty list if no old DMP + List oldDmpStorageIds = new ArrayList<>(); + if (oldDmp != null) { + for (Host host : oldDmp.getHostList()) { + if (host instanceof Storage storage) { + InternalStorage internalStorage = storage.getInternalStorageId(); + if (internalStorage != null) { + oldDmpStorageIds.add(internalStorage.id); + } + } + } + } + + // Difference between new and old DMP storage IDs + newDmpStorageIds.removeAll(oldDmpStorageIds); + + newDmpStorageIds.removeIf(Objects::isNull); + + for (Long id : newDmpStorageIds) { + InternalStorage storage = InternalStorage.findById(id); + if (storage.isActive()) { + continue; + } + throw new ValidationException(STORAGE_NOT_ACTIVE); + } + } + /** * Removes all datasets from {@code dmpDO} that are of type {@code source}. * diff --git a/src/main/java/org/damap/base/rest/dmp/service/DmpService.java b/src/main/java/org/damap/base/rest/dmp/service/DmpService.java index 3d015667..c3e4c40a 100644 --- a/src/main/java/org/damap/base/rest/dmp/service/DmpService.java +++ b/src/main/java/org/damap/base/rest/dmp/service/DmpService.java @@ -19,10 +19,7 @@ import org.damap.base.enums.EIdentifierType; import org.damap.base.repo.AccessRepo; import org.damap.base.repo.DmpRepo; -import org.damap.base.rest.dmp.domain.ContributorDO; -import org.damap.base.rest.dmp.domain.DmpDO; -import org.damap.base.rest.dmp.domain.DmpListItemDO; -import org.damap.base.rest.dmp.domain.ProjectDO; +import org.damap.base.rest.dmp.domain.*; import org.damap.base.rest.dmp.mapper.ContributorDOMapper; import org.damap.base.rest.dmp.mapper.DmpDOMapper; import org.damap.base.rest.dmp.mapper.DmpListItemDOMapper; @@ -31,6 +28,7 @@ import org.damap.base.rest.persons.orcid.ORCIDPersonServiceImpl; import org.damap.base.rest.projects.ProjectService; import org.damap.base.rest.projects.ProjectSupplementDO; +import org.damap.base.rest.storage.InternalStorageService; import org.damap.base.rest.version.VersionDO; import org.damap.base.rest.version.VersionService; import org.hibernate.envers.AuditReader; @@ -53,6 +51,8 @@ public class DmpService { @Inject ORCIDPersonServiceImpl orcidPersonService; + @Inject InternalStorageService internalStorageService; + /** * getAll. * @@ -126,6 +126,9 @@ public List getDmpDOListByPersonId(String personId) { public DmpDO create(@Valid DmpDO dmpDO, String editedBy) { log.info("Creating new DMP"); DmpConsistencyUtility.enforceDmpConsistency(dmpDO); + + DmpConsistencyUtility.enforceActiveStorage(dmpDO, null, internalStorageService); + Dmp dmp = DmpDOMapper.mapDOtoEntity(dmpDO, new Dmp(), mapperService); dmp.setCreated(new Date()); fetchORCIDContributorInfo(dmp); @@ -147,6 +150,9 @@ public DmpDO update(@Valid DmpDO dmpDO) { log.info("Updating DMP with id " + dmpDO.getId()); DmpConsistencyUtility.enforceDmpConsistency(dmpDO); Dmp dmp = dmpRepo.findById(dmpDO.getId()); + + DmpConsistencyUtility.enforceActiveStorage(dmpDO, dmp, internalStorageService); + boolean projectSelectionChanged = projectSelectionChanged(dmp, dmpDO); DmpDOMapper.mapDOtoEntity(dmpDO, dmp, mapperService); dmp.setModified(new Date()); diff --git a/src/main/java/org/damap/base/rest/madmp/mapper/MaDmpMapper.java b/src/main/java/org/damap/base/rest/madmp/mapper/MaDmpMapper.java index 95bcd996..01af01c3 100644 --- a/src/main/java/org/damap/base/rest/madmp/mapper/MaDmpMapper.java +++ b/src/main/java/org/damap/base/rest/madmp/mapper/MaDmpMapper.java @@ -13,6 +13,7 @@ import org.damap.base.rest.dmp.mapper.MapperService; import org.damap.base.rest.madmp.dto.*; import org.damap.base.rest.storage.InternalStorageDO; +import org.damap.base.rest.storage.InternalStorageTranslationDO; import org.re3data.schema._2_2.Certificates; import org.re3data.schema._2_2.PidSystems; import org.re3data.schema._2_2.Re3Data; @@ -481,15 +482,32 @@ public Host.SupportVersioning getSupportVersioning(Yesno versioning) { public Host mapToMaDmpFromInternalStorage(InternalStorageDO internalStorageDO, Host host) { host.setAvailability(null); - host.setBackupFrequency(internalStorageDO.getBackupFrequency()); host.setBackupType(null); host.setCertifiedWith(null); - host.setDescription(internalStorageDO.getDescription()); + host.setGeoLocation(null); host.setPidSystem(null); host.setStorageType(null); host.setSupportVersioning(null); - host.setTitle(internalStorageDO.getTitle()); + + List translations = internalStorageDO.getTranslations(); + InternalStorageTranslationDO internalStorageTranslationDO = null; + + if (!translations.isEmpty()) { + internalStorageTranslationDO = translations.get(0); + } + + for (InternalStorageTranslationDO translationDO : translations) { + if (translationDO.getLanguageCode().equals(DEFAULT_LANGUAGE_CODE)) { + internalStorageTranslationDO = translationDO; + break; + } + } + + host.setTitle(internalStorageTranslationDO.getTitle()); + host.setDescription(internalStorageTranslationDO.getDescription()); + host.setBackupFrequency(internalStorageTranslationDO.getBackupFrequency()); + if (internalStorageDO.getUrl() != null) host.setUrl(URI.create(internalStorageDO.getUrl())); return host; } diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageDO.java b/src/main/java/org/damap/base/rest/storage/InternalStorageDO.java index 42d7cbbc..68d49ee2 100644 --- a/src/main/java/org/damap/base/rest/storage/InternalStorageDO.java +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageDO.java @@ -1,6 +1,9 @@ package org.damap.base.rest.storage; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; @@ -10,12 +13,17 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class InternalStorageDO { private long id; - private String url; - private String backupFrequency; - private String storageLocation; + + @NotBlank(message = "url cannot be blank") + @NotNull(message = "url cannot be null") private String url; + + @NotBlank(message = "storage location cannot be blank") + @NotNull(message = "storage location cannot be null") private String storageLocation; + private String backupLocation; + + @NotNull(message = "activation status cannot be null") private Boolean active; + // the following information comes from the translation table - private String languageCode; - private String title; - private String description; + List translations; } diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageDOMapper.java b/src/main/java/org/damap/base/rest/storage/InternalStorageDOMapper.java index 01fd8151..576303e1 100644 --- a/src/main/java/org/damap/base/rest/storage/InternalStorageDOMapper.java +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageDOMapper.java @@ -1,6 +1,9 @@ package org.damap.base.rest.storage; +import java.util.ArrayList; +import java.util.List; import lombok.experimental.UtilityClass; +import org.damap.base.domain.InternalStorage; import org.damap.base.domain.InternalStorageTranslation; /** InternalStorageDOMapper class. */ @@ -15,17 +18,41 @@ public class InternalStorageDOMapper { * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object */ public InternalStorageDO mapEntityToDO( - InternalStorageTranslation storage, InternalStorageDO storageDO) { - storageDO.setId(storage.getInternalStorageId().id); - storageDO.setUrl(storage.getInternalStorageId().getUrl()); - storageDO.setBackupFrequency(storage.getBackupFrequency()); - storageDO.setStorageLocation(storage.getInternalStorageId().getStorageLocation()); - storageDO.setBackupLocation(storage.getInternalStorageId().getBackupLocation()); + InternalStorage storage, + InternalStorageDO storageDO, + List translations) { - storageDO.setLanguageCode(storage.getLanguageCode()); - storageDO.setTitle(storage.getTitle()); - storageDO.setDescription(storage.getDescription()); + storageDO.setId(storage.id); + storageDO.setUrl(storage.getUrl()); + storageDO.setStorageLocation(storage.getStorageLocation()); + storageDO.setBackupLocation(storage.getBackupLocation()); + storageDO.setActive(storage.isActive()); + + List translationDOs = new ArrayList<>(); + for (InternalStorageTranslation translation : translations) { + translationDOs.add( + InternalStorageTranslationDOMapper.mapEntityToDO( + translation, new InternalStorageTranslationDO())); + } + + storageDO.setTranslations(translationDOs); return storageDO; } + + /** + * mapDOToEntity. + * + * @param storageDO a {@link org.damap.base.rest.storage.InternalStorageDO} object + * @param internalStorage a {@link org.damap.base.domain.InternalStorage} object + * @return a {@link org.damap.base.domain.InternalStorage} object + */ + public InternalStorage mapDOToEntity( + InternalStorageDO storageDO, InternalStorage internalStorage) { + internalStorage.setUrl(storageDO.getUrl()); + internalStorage.setStorageLocation(storageDO.getStorageLocation()); + internalStorage.setBackupLocation(storageDO.getBackupLocation()); + internalStorage.setActive(storageDO.getActive()); + return internalStorage; + } } diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageService.java b/src/main/java/org/damap/base/rest/storage/InternalStorageService.java index 7286c345..9d22c242 100644 --- a/src/main/java/org/damap/base/rest/storage/InternalStorageService.java +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageService.java @@ -2,31 +2,197 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import lombok.extern.jbosslog.JBossLog; +import org.damap.base.domain.InternalStorage; +import org.damap.base.domain.InternalStorageTranslation; +import org.damap.base.domain.Storage; +import org.damap.base.repo.InternalStorageRepo; import org.damap.base.repo.InternalStorageTranslationRepo; +import org.damap.base.repo.StorageRepo; +import org.damap.base.rest.base.ResultList; +import org.damap.base.rest.base.Search; +import org.damap.base.rest.base.service.ServiceCreate; +import org.damap.base.rest.base.service.ServiceDelete; +import org.damap.base.rest.base.service.ServiceRead; +import org.damap.base.rest.base.service.ServiceSearch; +import org.damap.base.rest.base.service.ServiceUpdate; /** InternalStorageService class. */ @ApplicationScoped -public class InternalStorageService { +@JBossLog +public class InternalStorageService + implements ServiceCreate, + ServiceDelete, + ServiceRead, + ServiceSearch, + ServiceUpdate { @Inject InternalStorageTranslationRepo internalStorageTranslationRepo; + @Inject InternalStorageRepo internalStorageRepo; + + @Inject StorageRepo storageRepo; + + /** + * create a new internal storage option. + * + * @param data a {@link org.damap.base.rest.storage.InternalStorageDO} object + * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object + */ + @Override + @Transactional + public InternalStorageDO create(InternalStorageDO data) { + InternalStorageValidator.validateForCreation(data); + + InternalStorage internalStorage = + InternalStorageDOMapper.mapDOToEntity(data, new InternalStorage()); + internalStorage.persist(); + + for (InternalStorageTranslationDO translationDO : data.getTranslations()) { + translationDO.setStorageId(internalStorage.id); + InternalStorageTranslationValidator.validateForCreation( + translationDO, internalStorageRepo, internalStorageTranslationRepo); + InternalStorageTranslation translation = + InternalStorageTranslationDOMapper.mapDOToTranslationEntityForCreation( + translationDO, internalStorage); + translation.setInternalStorageId(internalStorage); + translation.persist(); + } + + return read(String.valueOf(internalStorage.id)); + } + + /** + * read a specific internal storage option. + * + * @param id a {@link java.lang.String} object + * @param queryParams a {@link jakarta.ws.rs.core.MultivaluedMap} object + * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object + */ + @Override + public InternalStorageDO read(String id, MultivaluedMap queryParams) { + + InternalStorage internalStorage = + internalStorageRepo + .findByIdOptional(Long.parseLong(id)) + .orElseThrow( + () -> new NotFoundException("No internal storage with ID " + id + " found")); + + List translations = + internalStorageTranslationRepo.getAllInternalStorageTranslationsByStorageId( + Long.parseLong(id)); + + return InternalStorageDOMapper.mapEntityToDO( + internalStorage, new InternalStorageDO(), translations); + } + /** - * getAllByLanguage. + * update a specific internal storage option. * - * @param languageCode a {@link java.lang.String} object - * @return a {@link java.util.List} object + * @param id a {@link java.lang.String} object + * @param data a {@link org.damap.base.rest.storage.InternalStorageDO} object + * @return a {@link org.damap.base.rest.storage.InternalStorageDO} object, updated + *

Note: The internalStorageTranslation has to be updated through a separate endpoint */ - public List getAllByLanguage(String languageCode) { + @Override + @Transactional + public InternalStorageDO update(String id, InternalStorageDO data) { + InternalStorageValidator.validateForUpdate(id, internalStorageRepo); + + InternalStorage internalStorage = internalStorageRepo.findById(Long.parseLong(id)); + InternalStorageDOMapper.mapDOToEntity(data, internalStorage); + internalStorage.persistAndFlush(); + + return read(String.valueOf(internalStorage.id)); + } + + /** + * delete a specific internal storage option. + * + * @param id a {@link java.lang.String} object + */ + @Override + @Transactional + public void delete(String id) { + InternalStorage internalStorage = + internalStorageRepo + .findByIdOptional(Long.parseLong(id)) + .orElseThrow( + () -> new NotFoundException("No internal storage with ID " + id + " found")); + + List storages = storageRepo.findByInternalStorageId(internalStorage); + + if (!storages.isEmpty()) { + throw new ClientErrorException( + "Internal storage with ID " + id + " is still in use by " + storages.size() + " storages", + Response.Status.CONFLICT); + } + + internalStorageTranslationRepo.deleteAllTranslationsForInternalStorage(internalStorage.id); + + internalStorageRepo.delete(internalStorage); + } + + /** + * search for internal storage options. + * + * @param queryParams a {@link jakarta.ws.rs.core.MultivaluedMap} object + * @return a {@link org.damap.base.rest.base.ResultList} object, list of all matching internal + * storage options + */ + @Override + public ResultList search(MultivaluedMap queryParams) { + queryParams = InternalStorageValidator.validateSearchParameters(queryParams); + + MultivaluedMap fields = getEntityFields(); + + MultivaluedMap searchParams = new MultivaluedHashMap<>(); + for (Map.Entry> entry : queryParams.entrySet()) { + String key = entry.getKey(); + List values = entry.getValue(); + if (fields.containsKey(key)) { + searchParams.addAll( + key, values.stream().map(v -> convertValue(fields.getFirst(key), v)).toList()); + } + } + + List internalStorageList = + internalStorageRepo.searchByParameters(searchParams); + List internalStorageDOList = new ArrayList<>(); - internalStorageTranslationRepo - .getAllInternalStorageByLanguage(languageCode) - .forEach( - storageTranslation -> - internalStorageDOList.add( - InternalStorageDOMapper.mapEntityToDO( - storageTranslation, new InternalStorageDO()))); - return internalStorageDOList; + + for (InternalStorage internalStorage : internalStorageList) { + List translations = + internalStorageTranslationRepo.getAllInternalStorageTranslationsByStorageId( + internalStorage.id); + internalStorageDOList.add( + InternalStorageDOMapper.mapEntityToDO( + internalStorage, new InternalStorageDO(), translations)); + } + + Search search = Search.fromMap(queryParams); + + return ResultList.fromItemsAndSearch(internalStorageDOList, search); + } + + @Override + public MultivaluedMap getEntityFields() { + MultivaluedMap fields = new MultivaluedHashMap<>(); + fields.add("id", Long.class); + fields.add("version", Long.class); + fields.add("url", String.class); + fields.add("storageLocation", String.class); + fields.add("backupLocation", String.class); + fields.add("active", Boolean.class); + return fields; } } diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationDO.java b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationDO.java new file mode 100644 index 00000000..943c1fb5 --- /dev/null +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationDO.java @@ -0,0 +1,25 @@ +package org.damap.base.rest.storage; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class InternalStorageTranslationDO { + private long id; + private long storageId; + + @NotBlank(message = "language code cannot be blank") + @NotNull(message = "language code cannot be null") private String languageCode; + + @NotBlank(message = "title cannot be blank") + @NotNull(message = "title cannot be null") private String title; + + private String description; + + private String backupFrequency; +} diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationDOMapper.java b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationDOMapper.java new file mode 100644 index 00000000..5006be36 --- /dev/null +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationDOMapper.java @@ -0,0 +1,44 @@ +package org.damap.base.rest.storage; + +import lombok.experimental.UtilityClass; +import org.damap.base.domain.InternalStorage; +import org.damap.base.domain.InternalStorageTranslation; + +@UtilityClass +public class InternalStorageTranslationDOMapper { + + public InternalStorageTranslationDO mapEntityToDO( + InternalStorageTranslation internalStorageTranslation, + InternalStorageTranslationDO internalStorageTranslationDO) { + + internalStorageTranslationDO.setId(internalStorageTranslation.id); + internalStorageTranslationDO.setStorageId(internalStorageTranslation.getInternalStorageId().id); + internalStorageTranslationDO.setLanguageCode(internalStorageTranslation.getLanguageCode()); + internalStorageTranslationDO.setTitle(internalStorageTranslation.getTitle()); + internalStorageTranslationDO.setDescription(internalStorageTranslation.getDescription()); + internalStorageTranslationDO.setBackupFrequency( + internalStorageTranslation.getBackupFrequency()); + + return internalStorageTranslationDO; + } + + public InternalStorageTranslation mapDOToTranslationEntityForCreation( + InternalStorageTranslationDO translationDO, InternalStorage internalStorage) { + InternalStorageTranslation translation = new InternalStorageTranslation(); + translation.setLanguageCode(translationDO.getLanguageCode()); + translation.setTitle(translationDO.getTitle()); + translation.setDescription(translationDO.getDescription()); + translation.setBackupFrequency(translationDO.getBackupFrequency()); + translation.setInternalStorageId(internalStorage); + return translation; + } + + public InternalStorageTranslation mapDOToEntity( + InternalStorageTranslationDO translationDO, InternalStorageTranslation translation) { + translation.setLanguageCode(translationDO.getLanguageCode()); + translation.setTitle(translationDO.getTitle()); + translation.setDescription(translationDO.getDescription()); + translation.setBackupFrequency(translationDO.getBackupFrequency()); + return translation; + } +} diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationService.java b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationService.java new file mode 100644 index 00000000..2fe042cf --- /dev/null +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationService.java @@ -0,0 +1,142 @@ +package org.damap.base.rest.storage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.MultivaluedMap; +import java.util.List; +import lombok.extern.jbosslog.JBossLog; +import org.damap.base.domain.InternalStorage; +import org.damap.base.domain.InternalStorageTranslation; +import org.damap.base.repo.InternalStorageRepo; +import org.damap.base.repo.InternalStorageTranslationRepo; +import org.damap.base.rest.base.ResultList; +import org.damap.base.rest.base.service.ServiceCreate; +import org.damap.base.rest.base.service.ServiceDelete; +import org.damap.base.rest.base.service.ServiceRead; +import org.damap.base.rest.base.service.ServiceSearch; +import org.damap.base.rest.base.service.ServiceUpdate; + +@ApplicationScoped +@JBossLog +public class InternalStorageTranslationService + implements ServiceCreate, + ServiceDelete, + ServiceRead, + ServiceSearch, + ServiceUpdate { + + @Inject InternalStorageTranslationRepo internalStorageTranslationRepo; + + @Inject InternalStorageRepo internalStorageRepo; + + @Override + @Transactional + public InternalStorageTranslationDO create(InternalStorageTranslationDO data) + throws ClientErrorException { + InternalStorageTranslationValidator.validateForCreation( + data, internalStorageRepo, internalStorageTranslationRepo); + + InternalStorage internalStorage = internalStorageRepo.findById(data.getStorageId()); + + InternalStorageTranslation internalStorageTranslation = + InternalStorageTranslationDOMapper.mapDOToTranslationEntityForCreation( + data, internalStorage); + internalStorageTranslation.persistAndFlush(); + + return getInternalStorageTranslationById(internalStorageTranslation.id); + } + + @Override + public InternalStorageTranslationDO read(String id, MultivaluedMap queryParams) + throws NumberFormatException { + + InternalStorageTranslation internalStorageTranslation = + internalStorageTranslationRepo.findById(Long.parseLong(id)); + + if (internalStorageTranslation == null) { + throw new NotFoundException("No internal storage translation found for id " + id); + } + + return InternalStorageTranslationDOMapper.mapEntityToDO( + internalStorageTranslation, new InternalStorageTranslationDO()); + } + + @Override + public InternalStorageTranslationDO read(String id) { + return this.read(id, null); + } + + @Override + @Transactional + public InternalStorageTranslationDO update(String id, InternalStorageTranslationDO data) { + + InternalStorageTranslationValidator.validateForUpdate( + id, data, internalStorageTranslationRepo, internalStorageRepo); + + InternalStorageTranslation internalStorageTranslation = + internalStorageTranslationRepo.findById(Long.parseLong(id)); + internalStorageTranslation = + InternalStorageTranslationDOMapper.mapDOToEntity(data, internalStorageTranslation); + internalStorageTranslation.persistAndFlush(); + + return getInternalStorageTranslationById(internalStorageTranslation.id); + } + + @Override + @Transactional + public void delete(String id) { + + InternalStorageTranslation internalStorageTranslation = + internalStorageTranslationRepo.findById(Long.parseLong(id)); + + if (internalStorageTranslation == null) { + throw new NotFoundException("No internal storage translation found for id " + id); + } + + if (internalStorageTranslationRepo + .getAllInternalStorageTranslationsByStorageId( + internalStorageTranslation.getInternalStorageId().id) + .size() + == 1) { + throw new ClientErrorException( + "Cannot delete the last translation for an internal storage", 400); + } + + internalStorageTranslationRepo.delete(internalStorageTranslation); + } + + @Override + public ResultList search( + MultivaluedMap queryParams) { + return null; + } + + public List getAllByStorageId(String storageId) { + + if (!InternalStorageValidator.storageIdExists(Long.valueOf(storageId))) { + throw new NotFoundException("No internal storage found for id " + storageId); + } + + List translations = + internalStorageTranslationRepo.getAllInternalStorageTranslationsByStorageId( + Long.valueOf(storageId)); + + return translations.stream() + .map( + translation -> + InternalStorageTranslationDOMapper.mapEntityToDO( + translation, new InternalStorageTranslationDO())) + .toList(); + } + + @Transactional + public InternalStorageTranslationDO getInternalStorageTranslationById( + long internalStorageTranslationId) { + return InternalStorageTranslationDOMapper.mapEntityToDO( + internalStorageTranslationRepo.findById(internalStorageTranslationId), + new InternalStorageTranslationDO()); + } +} diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationValidator.java b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationValidator.java new file mode 100644 index 00000000..2c1de8b7 --- /dev/null +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageTranslationValidator.java @@ -0,0 +1,90 @@ +package org.damap.base.rest.storage; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Response; +import lombok.experimental.UtilityClass; +import lombok.extern.jbosslog.JBossLog; +import org.damap.base.domain.InternalStorage; +import org.damap.base.domain.InternalStorageTranslation; +import org.damap.base.repo.InternalStorageRepo; +import org.damap.base.repo.InternalStorageTranslationRepo; + +@UtilityClass +@JBossLog +public class InternalStorageTranslationValidator { + + public void validateForCreation( + InternalStorageTranslationDO internalStorageTranslationDO, + InternalStorageRepo internalStorageRepo, + InternalStorageTranslationRepo internalStorageTranslationRepo) + throws ClientErrorException { + log.info("Validating internal storage translation for creation"); + + // Check if no translation for the same language exists + if (internalStorageTranslationRepo.existsTranslationForStorageIdAndLanguageCode( + internalStorageTranslationDO.getStorageId(), + internalStorageTranslationDO.getLanguageCode())) { + throw new ClientErrorException( + "Translation for language code " + + internalStorageTranslationDO.getLanguageCode() + + " already exists", + Response.Status.BAD_REQUEST); + } + + InternalStorageTranslationValidator.validationCommon( + internalStorageTranslationDO, internalStorageRepo); + } + + public void validateForUpdate( + String id, + InternalStorageTranslationDO internalStorageTranslationDO, + InternalStorageTranslationRepo internalStorageTranslationRepo, + InternalStorageRepo internalStorageRepo) + throws ClientErrorException { + log.info("Validating internal storage translation for update"); + + InternalStorageTranslation internalStorageTranslation = + internalStorageTranslationRepo.findById(Long.parseLong(id)); + + if (internalStorageTranslation == null) { + throw new NotFoundException("No internal storage translation with ID " + id + " found"); + } + + if (internalStorageTranslationRepo.existsTranslationForStorageIdAndLanguageCodeExceptId( + internalStorageTranslationDO.getStorageId(), + internalStorageTranslationDO.getLanguageCode(), + Long.parseLong(id))) { + throw new ClientErrorException( + "Translation for language code " + + internalStorageTranslationDO.getLanguageCode() + + " already exists", + Response.Status.BAD_REQUEST); + } + + InternalStorageTranslationValidator.validationCommon( + internalStorageTranslationDO, internalStorageRepo); + } + + public void validationCommon( + InternalStorageTranslationDO internalStorageTranslationDO, + InternalStorageRepo internalStorageRepo) + throws ClientErrorException { + log.info("Validating common internal storage translation"); + + InternalStorage internalStorage = + internalStorageRepo.findById(internalStorageTranslationDO.getStorageId()); + + if (internalStorage == null) { + throw new NotFoundException( + "No internal storage found for id " + internalStorageTranslationDO.getStorageId()); + } + + // Check if language code is 'deu' or 'eng' + if (!internalStorageTranslationDO.getLanguageCode().equals("deu") + && !internalStorageTranslationDO.getLanguageCode().equals("eng")) { + throw new ClientErrorException( + "Language code must be 'deu' or 'eng'", Response.Status.BAD_REQUEST); + } + } +} diff --git a/src/main/java/org/damap/base/rest/storage/InternalStorageValidator.java b/src/main/java/org/damap/base/rest/storage/InternalStorageValidator.java new file mode 100644 index 00000000..07ce07b4 --- /dev/null +++ b/src/main/java/org/damap/base/rest/storage/InternalStorageValidator.java @@ -0,0 +1,56 @@ +package org.damap.base.rest.storage; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.Response; +import java.util.Arrays; +import java.util.List; +import lombok.experimental.UtilityClass; +import lombok.extern.jbosslog.JBossLog; +import org.damap.base.domain.InternalStorage; +import org.damap.base.repo.InternalStorageRepo; + +@UtilityClass +@JBossLog +public class InternalStorageValidator { + + public void validateForCreation(InternalStorageDO internalStorageDO) { + + if (internalStorageDO.getTranslations() == null + || internalStorageDO.getTranslations().isEmpty()) { + throw new ClientErrorException( + "Translations list cannot be null or empty, at least one translation needed", + Response.Status.BAD_REQUEST); + } + } + + public void validateForUpdate(String id, InternalStorageRepo internalStorageRepo) + throws NotFoundException { + InternalStorage internalStorage = internalStorageRepo.findById(Long.parseLong(id)); + + if (internalStorage == null) { + throw new NotFoundException("No internal storage with ID " + id + " found"); + } + } + + public boolean storageIdExists(Long storageId) { + return InternalStorage.findById(storageId) != null; + } + + public MultivaluedMap validateSearchParameters( + MultivaluedMap queryParams) { + List allowedParams = + Arrays.asList("id", "url", "active", "storageLocation", "backupLocation"); + + MultivaluedMap queryParamsChecked = new MultivaluedHashMap<>(); + + queryParamsChecked.putAll(queryParams); + queryParamsChecked.keySet().removeIf(key -> !allowedParams.contains(key)); + + queryParamsChecked.values().removeIf(List::isEmpty); + + return queryParamsChecked; + } +} diff --git a/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.0.3_1.yaml b/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.0.3_1.yaml index 6d8e4902..8da68dbc 100644 --- a/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.0.3_1.yaml +++ b/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.0.3_1.yaml @@ -17,4 +17,4 @@ databaseChangeLog: DROP PROCEDURE IF EXISTS damap_upsert_storage; DROP PROCEDURE IF EXISTS damap_insert_storage_translation; DROP PROCEDURE IF EXISTS damap_update_storage_translation; - DROP PROCEDURE IF EXISTS damap_upsert_storage_translation; + DROP PROCEDURE IF EXISTS damap_upsert_storage_translation; \ No newline at end of file diff --git a/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.3.0_1.yaml b/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.3.0_1.yaml new file mode 100644 index 00000000..db4cc4e3 --- /dev/null +++ b/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.3.0_1.yaml @@ -0,0 +1,34 @@ +databaseChangeLog: + - changeSet: + id: 15 + author: Geoffrey Karnbach + changes: + - addColumn: + tableName: internal_storage + columns: + - column: + name: active + type: boolean + defaultValueBoolean: true + - addColumn: + tableName: internal_storage_aud + columns: + - column: + name: active + type: boolean + defaultValueBoolean: true + - sqlFile: + dbms: "!h2" + comment: Update storage procedures + splitStatements: false + stripComments: false + path: org/damap/base/db/changeLog-4.x/storage_procedures2.sql + + rollback: + - dropColumn: + tableName: internal_storage + columnName: active + - dropColumn: + tableName: internal_storage_aud + columnName: active + rollback: \ No newline at end of file diff --git a/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.x.yaml b/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.x.yaml index 37b77de3..4bb65156 100644 --- a/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.x.yaml +++ b/src/main/resources/org/damap/base/db/changeLog-4.x/changeLog-4.x.yaml @@ -9,3 +9,5 @@ databaseChangeLog: file: org/damap/base/db/changeLog-4.x/changeLog-4.0.3_2.yaml - include: file: org/damap/base/db/changeLog-4.x/changeLog-4.0.3_3.yaml + - include: + file: org/damap/base/db/changeLog-4.x/changeLog-4.3.0_1.yaml \ No newline at end of file diff --git a/src/main/resources/org/damap/base/db/changeLog-4.x/storage_procedures.sql b/src/main/resources/org/damap/base/db/changeLog-4.x/storage_procedures.sql index 13cada01..09d19e44 100644 --- a/src/main/resources/org/damap/base/db/changeLog-4.x/storage_procedures.sql +++ b/src/main/resources/org/damap/base/db/changeLog-4.x/storage_procedures.sql @@ -6,10 +6,10 @@ CREATE OR REPLACE PROCEDURE damap_insert_storage(url_ varchar, version_ integer, LANGUAGE SQL AS $$ INSERT INTO - internal_storage ( id, version, url, storage_location, backup_location ) + internal_storage ( id, version, url, storage_location, backup_location ) values ( - NEXTVAL ('internal_storage_seq'), version_, url_, location_, backup_location_ + NEXTVAL ('internal_storage_seq'), version_, url_, location_, backup_location_ ) $$; @@ -17,37 +17,37 @@ $$; CREATE OR REPLACE PROCEDURE damap_update_storage(old_url_ varchar, url_ varchar, version_ integer, location_ varchar, backup_location_ varchar) LANGUAGE SQL AS $$ - UPDATE - internal_storage - SET - version = version_, - url = url_, - storage_location = location_, - backup_location = backup_location_ - WHERE - url = old_url_; +UPDATE + internal_storage +SET + version = version_, + url = url_, + storage_location = location_, + backup_location = backup_location_ +WHERE + url = old_url_; $$; CREATE OR REPLACE PROCEDURE damap_upsert_storage(old_url_ varchar, url_ varchar, version_ integer, location_ varchar, backup_location_ varchar) LANGUAGE plpgsql AS $$ - BEGIN +BEGIN IF NOT EXISTS ( SELECT - 1 + 1 FROM - internal_storage + internal_storage WHERE - url = old_url_ + url = old_url_ ) THEN CALL damap_insert_storage(url_, version_, location_, backup_location_); - ELSE +ELSE CALL damap_update_storage(old_url_, url_, version_, location_, backup_location_); - END IF; - END +END IF; +END $$; @@ -56,7 +56,7 @@ CREATE OR REPLACE PROCEDURE damap_insert_storage_translation(url_ varchar, versi LANGUAGE SQL AS $$ INSERT INTO - inter_storage_translation ( id, version, internal_storage_id, language_code, title, description ) + inter_storage_translation ( id, version, internal_storage_id, language_code, title, description ) values ( NEXTVAL ('inter_storage_translation_seq'), @@ -79,52 +79,52 @@ $$; CREATE OR REPLACE PROCEDURE damap_update_storage_translation(url_ varchar, version_ integer, language_code_ varchar, title_ varchar, description_ varchar) LANGUAGE SQL AS $$ - UPDATE - inter_storage_translation - SET - version = version_, - title = title_, - description = description_ - WHERE - language_code = language_code_ - AND internal_storage_id = - ( - SELECT - id - from - internal_storage - where - url = url_ - ); +UPDATE + inter_storage_translation +SET + version = version_, + title = title_, + description = description_ +WHERE + language_code = language_code_ + AND internal_storage_id = + ( + SELECT + id + from + internal_storage + where + url = url_ + ); $$; CREATE OR REPLACE PROCEDURE damap_upsert_storage_translation(url_ varchar, version_ integer, language_code_ varchar, title_ varchar, description_ varchar) LANGUAGE plpgsql AS $$ - BEGIN +BEGIN IF NOT EXISTS ( SELECT - 1 + 1 FROM - inter_storage_translation + inter_storage_translation WHERE - language_code = language_code_ - AND internal_storage_id = + language_code = language_code_ + AND internal_storage_id = ( SELECT - id + id from - internal_storage + internal_storage where url = url_ ) ) THEN CALL damap_insert_storage_translation(url_, version_, language_code_, title_, description_); - ELSE +ELSE CALL damap_update_storage_translation(url_, version_, language_code_, title_, description_); - END IF; - END -$$; +END IF; +END +$$; \ No newline at end of file diff --git a/src/main/resources/org/damap/base/db/changeLog-4.x/storage_procedures2.sql b/src/main/resources/org/damap/base/db/changeLog-4.x/storage_procedures2.sql new file mode 100644 index 00000000..cdf9c09c --- /dev/null +++ b/src/main/resources/org/damap/base/db/changeLog-4.x/storage_procedures2.sql @@ -0,0 +1,131 @@ +-- Keep available procedures up to date. These should be created with `CREATE OR REPLACE` + + +-- Storage +CREATE OR REPLACE PROCEDURE damap_insert_storage(url_ varchar, version_ integer, location_ varchar, backup_location_ varchar, active_ boolean) +LANGUAGE SQL +AS $$ + INSERT INTO + internal_storage ( id, version, url, storage_location, backup_location, active ) + values + ( + NEXTVAL ('internal_storage_seq'), version_, url_, location_, backup_location_, active_ + ) +$$; + + +CREATE OR REPLACE PROCEDURE damap_update_storage(old_url_ varchar, url_ varchar, version_ integer, location_ varchar, backup_location_ varchar, active_ boolean) +LANGUAGE SQL +AS $$ +UPDATE + internal_storage +SET + version = version_, + url = url_, + storage_location = location_, + backup_location = backup_location_, + active = active_ +WHERE + url = old_url_; +$$; + + +CREATE OR REPLACE PROCEDURE damap_upsert_storage(old_url_ varchar, url_ varchar, version_ integer, location_ varchar, backup_location_ varchar, active_ boolean) +LANGUAGE plpgsql +AS $$ +BEGIN + IF NOT EXISTS + ( + SELECT + 1 + FROM + internal_storage + WHERE + url = old_url_ + ) + THEN + CALL damap_insert_storage(url_, version_, location_, backup_location_, active_); +ELSE + CALL damap_update_storage(old_url_, url_, version_, location_, backup_location_, active_); +END IF; +END +$$; + + +-- Storage Translations +CREATE OR REPLACE PROCEDURE damap_insert_storage_translation(url_ varchar, version_ integer, language_code_ varchar, title_ varchar, description_ varchar) +LANGUAGE SQL +AS $$ + INSERT INTO + inter_storage_translation ( id, version, internal_storage_id, language_code, title, description ) + values + ( + NEXTVAL ('inter_storage_translation_seq'), + version_, + ( + SELECT + id + from + internal_storage + where + url = url_ + ), + language_code_, + title_, + description_ + ); +$$; + + +CREATE OR REPLACE PROCEDURE damap_update_storage_translation(url_ varchar, version_ integer, language_code_ varchar, title_ varchar, description_ varchar) +LANGUAGE SQL +AS $$ + UPDATE + inter_storage_translation + SET + version = version_, + title = title_, + description = description_ + WHERE + language_code = language_code_ + AND internal_storage_id = + ( + SELECT + id + from + internal_storage + where + url = url_ + ); +$$; + + +CREATE OR REPLACE PROCEDURE damap_upsert_storage_translation(url_ varchar, version_ integer, language_code_ varchar, title_ varchar, description_ varchar) +LANGUAGE plpgsql +AS $$ + BEGIN + IF NOT EXISTS + ( + SELECT + 1 + FROM + inter_storage_translation + WHERE + language_code = language_code_ + AND internal_storage_id = + ( + SELECT + id + from + internal_storage + where + url = url_ + ) + ) + THEN + CALL damap_insert_storage_translation(url_, version_, language_code_, title_, description_); + ELSE + CALL damap_update_storage_translation(url_, version_, language_code_, title_, description_); + END IF; + END +$$; diff --git a/src/main/resources/org/damap/base/db/changeLog-root.yaml b/src/main/resources/org/damap/base/db/changeLog-root.yaml index 399df0df..b71939fc 100644 --- a/src/main/resources/org/damap/base/db/changeLog-root.yaml +++ b/src/main/resources/org/damap/base/db/changeLog-root.yaml @@ -23,4 +23,4 @@ databaseChangeLog: - include: file: org/damap/base/db/changeLog-3.x/changeLog-3.0.0_1.yaml - include: - file: org/damap/base/db/changeLog-4.x/changeLog-4.x.yaml + file: org/damap/base/db/changeLog-4.x/changeLog-4.x.yaml \ No newline at end of file diff --git a/src/test/java/org/damap/base/rest/InternalStorageResourceTest.java b/src/test/java/org/damap/base/rest/InternalStorageResourceTest.java index b2df5845..6b140a37 100644 --- a/src/test/java/org/damap/base/rest/InternalStorageResourceTest.java +++ b/src/test/java/org/damap/base/rest/InternalStorageResourceTest.java @@ -1,12 +1,23 @@ package org.damap.base.rest; import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.security.TestSecurity; +import io.restassured.RestAssured; +import io.restassured.parsing.Parser; +import io.restassured.response.ValidatableResponse; import jakarta.inject.Inject; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; +import java.util.List; +import org.damap.base.rest.storage.InternalStorageDO; +import org.damap.base.rest.storage.InternalStorageTranslationDO; import org.damap.base.util.TestDOFactory; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @QuarkusTest @@ -15,15 +26,274 @@ class InternalStorageResourceTest { @Inject TestDOFactory testDOFactory; + @BeforeAll + public static void setup() { + RestAssured.defaultParser = Parser.JSON; + } + + private InternalStorageDO getValidInternalStorageDO() { + InternalStorageDO validData = new InternalStorageDO(); + validData.setUrl("test.url.com"); + validData.setStorageLocation("testStorageLocation"); + validData.setBackupLocation("testBackupLocation"); + validData.setActive(true); + + InternalStorageTranslationDO translation = new InternalStorageTranslationDO(); + translation.setLanguageCode("eng"); + translation.setTitle("testStorageName"); + translation.setDescription("testStorageDescription"); + translation.setBackupFrequency("testBackupFrequency"); + + validData.setTranslations(List.of(translation)); + + return validData; + } + @Test void testGetAllByLanguageEndpoint_Invalid() { - given().when().get("/eng").then().statusCode(401); + given().when().get("languageCode=eng").then().statusCode(401); } @Test @TestSecurity(user = "userJwt", roles = "user") void testGetAllByLanguageEndpoint_Valid() { testDOFactory.prepareInternalStorageOption(); - given().when().get("/eng").then().statusCode(200); + given().queryParam("languageCode", "eng").when().get().then().statusCode(200); + } + + // Authorization tests + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testPostEndpointAsUser_Unauthorized() { + given().contentType("application/json").when().post().then().statusCode(403); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testPutEndpointAsUser_Unauthorized() { + given().contentType("application/json").when().put("/1").then().statusCode(403); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testDeleteEndpointAsUser_Unauthorized() { + given().when().delete("/1").then().statusCode(403); + } + + // GET tests + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testReadEndpointAsUser_NotFound() { + given().when().get("/-1").then().statusCode(404); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testReadEndpointAsUser_BadRequest() { + ValidatableResponse response = given().when().get("/abc").then().statusCode(400); + response.body("message", startsWith("Invalid internal storage ID")); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testReadEndpointAsUser_Valid() { + Long id = testDOFactory.prepareInternalStorageOption(); + + InternalStorageDO response = + given().when().get("/" + id).then().statusCode(200).extract().as(InternalStorageDO.class); + + assertEquals(id, response.getId()); + assertEquals("test.url.com", response.getUrl()); + } + + // POST tests + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testCreateEndpointAsAdminWithMissingTranslation_BadRequest() { + InternalStorageDO invalidData = this.getValidInternalStorageDO(); + invalidData.setTranslations(List.of()); + + ValidatableResponse response = + given() + .contentType("application/json") + .body(invalidData) + .when() + .post() + .then() + .statusCode(400); + response.body("message", startsWith("Translations list cannot be null or empty")); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testCreateEndpointAsAdminWithEmptyURL_BadRequest() { + InternalStorageDO invalidData = this.getValidInternalStorageDO(); + invalidData.setUrl(""); + + ValidatableResponse response = + given() + .contentType("application/json") + .body(invalidData) + .when() + .post() + .then() + .statusCode(400); + + response.body("exception", equalTo("ConstraintViolationException")); + response.body("violations.size()", is(1)); + response.body("violations[0].message", startsWith("url cannot be blank")); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testCreateEndpointAsAdminWithValidData_Created() { + InternalStorageDO validData = this.getValidInternalStorageDO(); + + InternalStorageDO response = + given() + .contentType("application/json") + .body(validData) + .when() + .post() + .then() + .statusCode(200) + .extract() + .as(InternalStorageDO.class); + + Long id = response.getId(); + assertEquals("test.url.com", response.getUrl()); + + InternalStorageDO readResponse = + given().when().get("/" + id).then().statusCode(200).extract().as(InternalStorageDO.class); + assertEquals(id, readResponse.getId()); + assertEquals("test.url.com", readResponse.getUrl()); + assertEquals("testStorageLocation", readResponse.getStorageLocation()); + assertEquals("testBackupLocation", readResponse.getBackupLocation()); + assertEquals(true, readResponse.getActive()); + } + + // PUT tests + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testPutEndpointAsAdminWithNonExistingId_NotFound() { + InternalStorageDO validData = this.getValidInternalStorageDO(); + given() + .contentType("application/json") + .body(validData) + .when() + .put("/-1") + .then() + .statusCode(404); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testPutEndpointAsAdminValid_Updated() { + Long id = testDOFactory.prepareInternalStorageOption(); + + InternalStorageDO currentData = + given().when().get("/" + id).then().statusCode(200).extract().as(InternalStorageDO.class); + String currentUrl = currentData.getUrl(); + + InternalStorageDO validData = this.getValidInternalStorageDO(); + validData.setUrl("new.url.com"); + + InternalStorageDO response = + given() + .contentType("application/json") + .body(validData) + .when() + .put("/" + id) + .then() + .statusCode(200) + .extract() + .as(InternalStorageDO.class); + + assertEquals(id, response.getId()); + assertEquals("new.url.com", response.getUrl()); + assertNotEquals(currentUrl, response.getUrl()); + } + + // DELETE tests + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testDeleteEndpointAsAdminWithNonExistingId_NotFound() { + given().when().delete("/-1").then().statusCode(404); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testDeleteEndpointAsAdminValidId_Deleted() { + Long id = testDOFactory.prepareInternalStorageOption(); + given().when().delete("/" + id).then().statusCode(204); + + given().when().get("/" + id).then().statusCode(404); + } + + // Search tests + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testSearchEndpointAsUserInvalidParams_IgnoredAndFiltered() { + testDOFactory.prepareSpecialInternalStorageOption("1"); + + given() + .queryParams("invalidParam", "1", "url", "special.url.com1") + .when() + .get() + .then() + .statusCode(200) + .body("items.size()", equalTo(1)); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testSearchEndpointAsUserMultipleOptions_Filtered() { + testDOFactory.prepareSpecialInternalStorageOption("2"); + testDOFactory.prepareSpecialInternalStorageOption("3"); + + MultivaluedMap queryParams = new MultivaluedHashMap<>(); + queryParams.put("url", List.of("special.url.com2", "special.url.com3")); + queryParams.put("active", List.of("true")); + + given() + .queryParams(queryParams) + .when() + .get() + .then() + .statusCode(200) + .body("items.size()", equalTo(2)); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testSearchEndpointAsUserMultipleOptions_FilteredNoResult() { + testDOFactory.prepareSpecialInternalStorageOption("4"); + testDOFactory.prepareSpecialInternalStorageOption("5"); + + MultivaluedMap queryParams = new MultivaluedHashMap<>(); + queryParams.put("url", List.of("special.url.com4", "special.url.com5")); + queryParams.put("active", List.of("false")); + + given() + .queryParams(queryParams) + .when() + .get() + .then() + .statusCode(200) + .body("items.size()", equalTo(0)); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testSearchEndpointAsUserValidParams_Filtered() { + testDOFactory.prepareSpecialInternalStorageOption(""); + + given() + .queryParam("url", "special.url.com") + .when() + .get() + .then() + .statusCode(200) + .body("items.size()", equalTo(1)); } } diff --git a/src/test/java/org/damap/base/rest/InternalStorageTranslationResourceTest.java b/src/test/java/org/damap/base/rest/InternalStorageTranslationResourceTest.java new file mode 100644 index 00000000..4626421b --- /dev/null +++ b/src/test/java/org/damap/base/rest/InternalStorageTranslationResourceTest.java @@ -0,0 +1,294 @@ +package org.damap.base.rest; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.response.ValidatableResponse; +import jakarta.inject.Inject; +import java.util.List; +import org.damap.base.rest.storage.InternalStorageTranslationDO; +import org.damap.base.util.TestDOFactory; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@TestHTTPEndpoint(InternalStorageTranslationResource.class) +class InternalStorageTranslationResourceTest { + + @Inject TestDOFactory testDOFactory; + + // Authorization tests + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testPostEndpointAsUser_Unauthorized() { + given() + .pathParam("storageId", 1) + .contentType("application/json") + .when() + .post() + .then() + .statusCode(403); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testPutEndpointAsUser_Unauthorized() { + given() + .pathParam("storageId", 1) + .contentType("application/json") + .when() + .put("/1") + .then() + .statusCode(403); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testDeleteEndpointAsUser_Unauthorized() { + given().pathParam("storageId", 1).when().delete("/1").then().statusCode(403); + } + + // Get (by ID and all) tests + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testGetByIdEndpointInvalidID_NotFound() { + testDOFactory.prepareInternalStorageTranslationOption(false); + + given().pathParam("storageId", -1).when().get("/-1").then().statusCode(404); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testGetByIdEndpointValidID_Valid() { + List ids = testDOFactory.prepareInternalStorageTranslationOption(false); + Long storageId = ids.get(0); + Long id = ids.get(1); + + InternalStorageTranslationDO response = + given() + .when() + .pathParam("storageId", storageId) + .get("/" + id) + .then() + .statusCode(200) + .extract() + .as(InternalStorageTranslationDO.class); + assertEquals("Test Storage Title ENG", response.getTitle()); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testGetAllByStorageEndpointInvalidID_NotFound() { + testDOFactory.prepareInternalStorageTranslationOption(false); + + given().pathParam("storageId", -1).when().get().then().statusCode(404); + } + + @Test + @TestSecurity(user = "userJwt", roles = "user") + void testGetAllByStorageEndpointValidID_Valid() { + Long id = testDOFactory.prepareInternalStorageTranslationOption(false).get(0); + + List response = + given() + .pathParam("storageId", id) + .when() + .get() + .then() + .statusCode(200) + .extract() + .as(List.class); + assertEquals(1, response.size()); + } + + // Create tests + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testCreateTranslationInvalidStorageID_NotFound() { + InternalStorageTranslationDO data = new InternalStorageTranslationDO(); + data.setStorageId(-1); + data.setTitle("Test Storage Title ENG"); + data.setDescription("Test Storage Description ENG"); + data.setLanguageCode("eng"); + data.setBackupFrequency("Test Storage Backup Frequency ENG"); + + given() + .pathParam("storageId", -1) + .contentType("application/json") + .body(data) + .when() + .post() + .then() + .statusCode(400); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testCreateTranslationDuplicateLanguageCode_BadRequest() { + + Long id = testDOFactory.prepareInternalStorageTranslationOption(false).get(0); + + InternalStorageTranslationDO data = new InternalStorageTranslationDO(); + data.setStorageId(id); + data.setTitle("Title"); + data.setBackupFrequency("Test Storage Backup Frequency ENG"); + data.setLanguageCode("eng"); + data.setDescription("Test Storage Description ENG"); + + ValidatableResponse response = + given() + .pathParam("storageId", id) + .contentType("application/json") + .body(data) + .when() + .post() + .then() + .statusCode(400); + response.body("message", startsWith("Translation for language code eng already exists")); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testCreateEndpointValidData_Created() { + Long id = testDOFactory.prepareInternalStorageOption(); + + InternalStorageTranslationDO data = new InternalStorageTranslationDO(); + data.setStorageId(id); + data.setTitle("Test Storage Title DEU"); + data.setDescription("Test Storage Description DEU"); + data.setLanguageCode("deu"); + data.setBackupFrequency("Test Storage Backup Frequency DEU"); + + // Correct the URL construction + InternalStorageTranslationDO response = + given() + .pathParam("storageId", id) + .contentType("application/json") + .body(data) + .when() + .post() + .then() + .statusCode(200) + .extract() + .as(InternalStorageTranslationDO.class); + + assertEquals("Test Storage Title DEU", response.getTitle()); + + List response2 = + given() + .pathParam("storageId", id) + .when() + .get() + .then() + .statusCode(200) + .extract() + .as(List.class); + assertEquals(2, response2.size()); + } + + // Update tests + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testUpdateEndpointInvalidID_NotFound() { + InternalStorageTranslationDO data = new InternalStorageTranslationDO(); + data.setTitle("Test Storage Title ENG"); + data.setDescription("Test Storage Description ENG"); + data.setLanguageCode("eng"); + data.setBackupFrequency("Test Storage Backup Frequency ENG"); + + given() + .pathParam("storageId", -1) + .contentType("application/json") + .body(data) + .when() + .put("/-1") + .then() + .statusCode(404); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testUpdateEndpointValidData_Updated() { + List ids = testDOFactory.prepareInternalStorageTranslationOption(false); + + InternalStorageTranslationDO data = new InternalStorageTranslationDO(); + data.setId(ids.get(1)); + data.setTitle("Test Storage Title DEU"); + data.setDescription("Test Storage Description DEU"); + data.setLanguageCode("deu"); + data.setBackupFrequency("Test Storage Backup Frequency DEU"); + data.setStorageId(ids.get(0)); + + InternalStorageTranslationDO response = + given() + .pathParam("storageId", ids.get(0)) + .contentType("application/json") + .body(data) + .when() + .put("/" + ids.get(1)) + .then() + .statusCode(200) + .extract() + .as(InternalStorageTranslationDO.class); + assertEquals("Test Storage Title DEU", response.getTitle()); + + InternalStorageTranslationDO response2 = + given() + .pathParam("storageId", ids.get(0)) + .when() + .get("/" + ids.get(1)) + .then() + .statusCode(200) + .extract() + .as(InternalStorageTranslationDO.class); + assertEquals("Test Storage Title DEU", response2.getTitle()); + } + + // Delete tests + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testDeleteEndpointInvalidID_NotFound() { + given().pathParam("storageId", -1).when().delete("/-1").then().statusCode(404); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testDeleteEndpointLastTranslation_BadRequest() { + List ids = testDOFactory.prepareInternalStorageTranslationOption(false); + + ValidatableResponse response = + given() + .pathParam("storageId", ids.get(0)) + .when() + .delete("/" + ids.get(1)) + .then() + .statusCode(400); + response.body( + "message", startsWith("Cannot delete the last translation for an internal storage")); + } + + @Test + @TestSecurity(user = "adminJwt", roles = "Damap Admin") + void testDeleteEndpointValidID_Deleted() { + List id = testDOFactory.prepareInternalStorageTranslationOption(true); + Long translationId = id.get(2); + Long storageId = id.get(0); + + given() + .pathParam("storageId", storageId) + .when() + .delete("/" + translationId) + .then() + .statusCode(204); + + given() + .pathParam("storageId", storageId) + .when() + .get("/" + translationId) + .then() + .statusCode(404); + } +} diff --git a/src/test/java/org/damap/base/util/TestDOFactory.java b/src/test/java/org/damap/base/util/TestDOFactory.java index 407fbb1e..7e858c34 100644 --- a/src/test/java/org/damap/base/util/TestDOFactory.java +++ b/src/test/java/org/damap/base/util/TestDOFactory.java @@ -4,6 +4,7 @@ import com.google.common.io.Resources; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import jakarta.validation.ValidationException; import java.io.InputStream; @@ -33,6 +34,7 @@ import org.damap.base.enums.ESecurityMeasure; import org.damap.base.repo.DmpRepo; import org.damap.base.repo.DmpVersionRepo; +import org.damap.base.repo.InternalStorageRepo; import org.damap.base.repo.InternalStorageTranslationRepo; import org.damap.base.rest.dmp.domain.ContributorDO; import org.damap.base.rest.dmp.domain.CostDO; @@ -59,12 +61,16 @@ public class TestDOFactory { @Inject InternalStorageTranslationRepo internalStorageTranslationRepo; + @Inject InternalStorageRepo internalStorageRepo; + @Inject MockDmpService dmpService; @Inject VersionService versionService; @Inject DmpVersionRepo dmpVersionRepo; + @Inject EntityManager entityManager; + private final String editorId = "012345"; /** @@ -361,14 +367,48 @@ public DmpDO getOrCreateTestDmpDOEmpty() { /** prepareInternalStorageOption. */ @Transactional - public void prepareInternalStorageOption() { - - if (internalStorageTranslationRepo.listAll().size() > 0) return; + public Long prepareInternalStorageOption() { InternalStorage internalStorage = new InternalStorage(); internalStorage.setUrl("test.url.com"); internalStorage.setStorageLocation("AUT"); internalStorage.setBackupLocation("AUT"); + internalStorage.setActive(true); + internalStorage.persist(); + + InternalStorageTranslation internalStorageTranslation = new InternalStorageTranslation(); + internalStorageTranslation.setInternalStorageId(internalStorage); + internalStorageTranslation.setTitle("Test Storage Title"); + internalStorageTranslation.setDescription( + "Long winded yet brief description of the storage option"); + internalStorageTranslation.setLanguageCode("eng"); + internalStorageTranslation.persistAndFlush(); + + InternalStorage internalStorage2 = new InternalStorage(); + internalStorage2.setUrl("test2.url.com"); + internalStorage2.setStorageLocation("AUT"); + internalStorage2.setBackupLocation("GER"); + internalStorage2.setActive(false); + internalStorage2.persist(); + + InternalStorageTranslation internalStorageTranslation2 = new InternalStorageTranslation(); + internalStorageTranslation2.setInternalStorageId(internalStorage2); + internalStorageTranslation2.setTitle("Test Storage Title"); + internalStorageTranslation2.setDescription( + "Long winded yet brief description of the storage option"); + internalStorageTranslation2.setLanguageCode("eng"); + internalStorageTranslation2.persistAndFlush(); + + return internalStorage.id; + } + + @Transactional + public Long prepareSpecialInternalStorageOption(String urlSuffix) { + InternalStorage internalStorage = new InternalStorage(); + internalStorage.setUrl("special.url.com" + urlSuffix); + internalStorage.setStorageLocation("AUT"); + internalStorage.setBackupLocation("AUT"); + internalStorage.setActive(true); internalStorage.persist(); InternalStorageTranslation internalStorageTranslation = new InternalStorageTranslation(); @@ -378,6 +418,42 @@ public void prepareInternalStorageOption() { "Long winded yet brief description of the storage option"); internalStorageTranslation.setLanguageCode("eng"); internalStorageTranslation.persistAndFlush(); + + return internalStorage.id; + } + + @Transactional + public List prepareInternalStorageTranslationOption(boolean generateTwo) { + + InternalStorage internalStorage = new InternalStorage(); + internalStorage.setUrl("test.url.com"); + internalStorage.setStorageLocation("AUT"); + internalStorage.setBackupLocation("AUT"); + internalStorage.setActive(true); + internalStorage.persist(); + + InternalStorageTranslation internalStorageTranslation = new InternalStorageTranslation(); + internalStorageTranslation.setInternalStorageId(internalStorage); + internalStorageTranslation.setTitle("Test Storage Title ENG"); + internalStorageTranslation.setDescription( + "Long winded yet brief description of the storage option"); + internalStorageTranslation.setLanguageCode("eng"); + internalStorageTranslation.persistAndFlush(); + + if (generateTwo) { + InternalStorageTranslation internalStorageTranslation2 = new InternalStorageTranslation(); + internalStorageTranslation2.setInternalStorageId(internalStorage); + internalStorageTranslation2.setTitle("Test Storage Title DEU"); + internalStorageTranslation2.setDescription( + "Long winded yet brief description of the storage option"); + internalStorageTranslation2.setLanguageCode("deu"); + internalStorageTranslation2.persistAndFlush(); + + return List.of( + internalStorage.id, internalStorageTranslation.id, internalStorageTranslation2.id); + } + + return List.of(internalStorage.id, internalStorageTranslation.id); } /**