From f7dc3e5a325739884a152f30f1c6da5afb2f86b2 Mon Sep 17 00:00:00 2001 From: sgiannopoulos Date: Tue, 6 Feb 2024 15:03:49 +0200 Subject: [PATCH] description use storage file --- .../java/eu/eudat/audit/AuditableAction.java | 5 +- .../persist/DescriptionFieldFilePersist.java | 74 +++++++++ .../description/DescriptionService.java | 8 +- .../description/DescriptionServiceImpl.java | 146 +++++++++++++----- .../service/storage/StorageFileService.java | 4 +- .../storage/StorageFileServiceImpl.java | 59 +++++-- .../controllers/v2/DescriptionController.java | 73 +++++++-- 7 files changed, 302 insertions(+), 67 deletions(-) create mode 100644 dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionFieldFilePersist.java diff --git a/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java b/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java index f4e9e1545..fcf0cdc4e 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java +++ b/dmp-backend/core/src/main/java/eu/eudat/audit/AuditableAction.java @@ -48,7 +48,10 @@ public class AuditableAction { public static final EventId Description_PublicQuery = new EventId(6004, "Description_PublicQuery"); public static final EventId Description_PublicLookup = new EventId(6005, "Description_PublicLookup"); public static final EventId Description_PersistStatus = new EventId(6006, "Description_PersistStatus"); - + public static final EventId Description_UploadFieldFiles = new EventId(6007, "Description_UploadFieldFiles"); + public static final EventId Description_GetFieldFile = new EventId(6008, "Description_GetFieldFile"); + + public static final EventId Reference_Query = new EventId(7000, "Reference_Query"); public static final EventId Reference_Lookup = new EventId(7001, "Reference_Lookup"); public static final EventId Reference_Persist = new EventId(7002, "Reference_Persist"); diff --git a/dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionFieldFilePersist.java b/dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionFieldFilePersist.java new file mode 100644 index 000000000..332bcd7b1 --- /dev/null +++ b/dmp-backend/core/src/main/java/eu/eudat/model/persist/DescriptionFieldFilePersist.java @@ -0,0 +1,74 @@ +package eu.eudat.model.persist; + +import eu.eudat.commons.validation.BaseValidator; +import eu.eudat.convention.ConventionService; +import eu.eudat.errorcode.ErrorThesaurusProperties; +import gr.cite.tools.validation.specification.Specification; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class DescriptionFieldFilePersist { + + private UUID descriptionTemplateId; + + public static final String _descriptionTemplateId = "descriptionTemplateId"; + + private String fieldId; + + public static final String _fieldId = "fieldId"; + + public UUID getDescriptionTemplateId() { + return descriptionTemplateId; + } + + public void setDescriptionTemplateId(UUID descriptionTemplateId) { + this.descriptionTemplateId = descriptionTemplateId; + } + + public String getFieldId() { + return fieldId; + } + + public void setFieldId(String fieldId) { + this.fieldId = fieldId; + } + + @Component(PersistValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class PersistValidator extends BaseValidator { + + public static final String ValidatorName = "DescriptionFieldFilePersistValidator"; + + private final MessageSource messageSource; + + protected PersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return DescriptionFieldFilePersist.class; + } + + @Override + protected List specifications(DescriptionFieldFilePersist item) { + return Arrays.asList( + this.spec() + .must(() -> this.isValidGuid(item.getDescriptionTemplateId())) + .failOn(DescriptionFieldFilePersist._descriptionTemplateId).failWith(messageSource.getMessage("Validation_Required", new Object[]{DescriptionFieldFilePersist._descriptionTemplateId}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isEmpty(item.getFieldId())) + .failOn(DescriptionFieldFilePersist._fieldId).failWith(messageSource.getMessage("Validation_Required", new Object[]{DescriptionFieldFilePersist._fieldId}, LocaleContextHolder.getLocale())) + ); + } + } + +} diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionService.java b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionService.java index da56fa63d..e6075416f 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionService.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionService.java @@ -1,7 +1,9 @@ package eu.eudat.service.description; -import com.fasterxml.jackson.core.JsonProcessingException; +import eu.eudat.data.StorageFileEntity; import eu.eudat.model.Description; +import eu.eudat.model.StorageFile; +import eu.eudat.model.persist.DescriptionFieldFilePersist; import eu.eudat.model.persist.DescriptionPersist; import eu.eudat.model.persist.DescriptionStatusPersist; import gr.cite.tools.exception.MyApplicationException; @@ -10,6 +12,7 @@ import gr.cite.tools.exception.MyNotFoundException; import gr.cite.tools.exception.MyValidationException; import gr.cite.tools.fieldset.FieldSet; import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; import javax.management.InvalidApplicationException; import java.io.IOException; @@ -26,5 +29,6 @@ public interface DescriptionService { ResponseEntity export(UUID id, String exportType) throws InvalidApplicationException, IOException; - + StorageFile uploadFieldFile(DescriptionFieldFilePersist model, MultipartFile file, FieldSet fields) throws IOException; + StorageFileEntity getFieldFile(UUID descriptionId, UUID storageFileId); } diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java index 9329be7bd..b2157d6ac 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/description/DescriptionServiceImpl.java @@ -10,7 +10,7 @@ import eu.eudat.commons.scope.user.UserScope; import eu.eudat.commons.types.description.*; import eu.eudat.commons.types.descriptionreference.DescriptionReferenceDataEntity; import eu.eudat.commons.types.descriptiontemplate.FieldSetEntity; -import eu.eudat.commons.types.dmpreference.DmpReferenceDataEntity; +import eu.eudat.commons.types.descriptiontemplate.fielddata.UploadDataEntity; import eu.eudat.commons.types.notification.*; import eu.eudat.commons.types.reference.DefinitionEntity; import eu.eudat.configurations.notification.NotificationProperties; @@ -23,35 +23,33 @@ import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler; import eu.eudat.model.*; import eu.eudat.model.builder.DescriptionBuilder; +import eu.eudat.model.builder.StorageFileBuilder; import eu.eudat.model.deleter.DescriptionDeleter; import eu.eudat.model.deleter.DescriptionReferenceDeleter; import eu.eudat.model.deleter.DescriptionTagDeleter; import eu.eudat.model.file.FileEnvelope; -import eu.eudat.model.persist.DescriptionPersist; -import eu.eudat.model.persist.DescriptionReferencePersist; -import eu.eudat.model.persist.DescriptionStatusPersist; -import eu.eudat.model.persist.ReferencePersist; +import eu.eudat.model.persist.*; import eu.eudat.model.persist.descriptionproperties.*; import eu.eudat.model.persist.descriptionreference.DescriptionReferenceDataPersist; -import eu.eudat.model.persist.dmpreference.DmpReferenceDataPersist; import eu.eudat.model.persist.referencedefinition.DefinitionPersist; import eu.eudat.query.*; import eu.eudat.service.elastic.ElasticService; +import eu.eudat.service.storage.StorageFileProperties; +import eu.eudat.service.storage.StorageFileService; import eu.eudat.service.transformer.FileTransformerService; import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.query.Ordering; import gr.cite.tools.data.query.QueryFactory; -import gr.cite.tools.exception.MyApplicationException; -import gr.cite.tools.exception.MyForbiddenException; -import gr.cite.tools.exception.MyNotFoundException; -import gr.cite.tools.exception.MyValidationException; +import gr.cite.tools.exception.*; import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; +import gr.cite.tools.validation.ValidatorFactory; import jakarta.persistence.EntityManager; +import org.apache.commons.io.FilenameUtils; import org.jetbrains.annotations.NotNull; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -62,10 +60,14 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.util.unit.DataSize; +import org.springframework.web.multipart.MultipartFile; import javax.management.InvalidApplicationException; import java.io.IOException; +import java.net.URLConnection; import java.nio.file.Files; +import java.time.Duration; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -74,52 +76,41 @@ import java.util.stream.Collectors; public class DescriptionServiceImpl implements DescriptionService { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DescriptionServiceImpl.class)); - private final EntityManager entityManager; - private final AuthorizationService authorizationService; - private final DeleterFactory deleterFactory; - private final BuilderFactory builderFactory; - private final ConventionService conventionService; - private final ErrorThesaurusProperties errors; - private final MessageSource messageSource; - private final EventBroker eventBroker; - private final QueryFactory queryFactory; - private final JsonHandlingService jsonHandlingService; - private final UserScope userScope; private final XmlHandlingService xmlHandlingService; private final FileTransformerService fileTransformerService; - private final NotifyIntegrationEventHandler eventHandler; - private final NotificationProperties notificationProperties; - private final ElasticService elasticService; + private final ValidatorFactory validatorFactory; + private final StorageFileProperties storageFileConfig; + private final StorageFileService storageFileService; @Autowired public DescriptionServiceImpl( - EntityManager entityManager, - AuthorizationService authorizationService, - DeleterFactory deleterFactory, - BuilderFactory builderFactory, - ConventionService conventionService, - ErrorThesaurusProperties errors, - MessageSource messageSource, - EventBroker eventBroker, - QueryFactory queryFactory, - JsonHandlingService jsonHandlingService, - UserScope userScope, - XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService) { + EntityManager entityManager, + AuthorizationService authorizationService, + DeleterFactory deleterFactory, + BuilderFactory builderFactory, + ConventionService conventionService, + ErrorThesaurusProperties errors, + MessageSource messageSource, + EventBroker eventBroker, + QueryFactory queryFactory, + JsonHandlingService jsonHandlingService, + UserScope userScope, + XmlHandlingService xmlHandlingService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, FileTransformerService fileTransformerService, ElasticService elasticService, ValidatorFactory validatorFactory, StorageFileProperties storageFileConfig, StorageFileService storageFileService) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; @@ -136,6 +127,9 @@ public class DescriptionServiceImpl implements DescriptionService { this.notificationProperties = notificationProperties; this.fileTransformerService = fileTransformerService; this.elasticService = elasticService; + this.validatorFactory = validatorFactory; + this.storageFileConfig = storageFileConfig; + this.storageFileService = storageFileService; } //region Persist @@ -405,7 +399,15 @@ public class DescriptionServiceImpl implements DescriptionService { FieldEntity data = new FieldEntity(); if (persist == null) return data; - if (FieldType.isTextType(fieldType)) data.setTextValue(persist.getTextValue()); + if (FieldType.isTextType(fieldType)) { + if (FieldType.UPLOAD.equals(fieldType)){ + StorageFile storageFile = this.storageFileService.copyToStorage(UUID.fromString(persist.getTextValue()), StorageType.Main, true, new BaseFieldSet().ensure(StorageFile._id)); + this.storageFileService.updatePurgeAt(storageFile.getId(), null); + data.setTextValue(storageFile.getId().toString()); + } else { + data.setTextValue(persist.getTextValue()); + } + } else if (FieldType.isDateType(fieldType)) data.setDateValue(persist.getDateValue()); else if (FieldType.isTextListType(fieldType)) data.setTextListValue(persist.getTextListValue()); else if (FieldType.isExternalIdentifierType(fieldType) && persist.getExternalIdentifier() != null) data.setExternalIdentifier(this.buildExternalIdentifierEntity(persist.getExternalIdentifier())); @@ -702,14 +704,78 @@ public class DescriptionServiceImpl implements DescriptionService { @Override public ResponseEntity export(UUID id, String exportType) throws InvalidApplicationException, IOException { HttpHeaders headers = new HttpHeaders(); - String type = exportType; - FileEnvelope fileEnvelope = this.fileTransformerService.exportDescription(id, type); + FileEnvelope fileEnvelope = this.fileTransformerService.exportDescription(id, exportType); headers.add("Content-Disposition", "attachment;filename=" + fileEnvelope.getFilename()); byte[] data = Files.readAllBytes(fileEnvelope.getFile().toPath()); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); return new ResponseEntity<>(data, headers, HttpStatus.OK); } - //endregion + + + + @Override + public StorageFile uploadFieldFile(DescriptionFieldFilePersist model, MultipartFile file, FieldSet fields) throws IOException { + this.authorizationService.authorizeForce(Permission.EditDescription); + + DescriptionTemplateEntity descriptionTemplate = this.queryFactory.query(DescriptionTemplateQuery.class).ids(model.getDescriptionTemplateId()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).first(); + if (descriptionTemplate == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getDescriptionTemplateId(), DescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + eu.eudat.commons.types.descriptiontemplate.DefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(eu.eudat.commons.types.descriptiontemplate.DefinitionEntity.class, descriptionTemplate.getDefinition()); + if (definition == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getDescriptionTemplateId(), eu.eudat.commons.types.descriptiontemplate.DefinitionEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + eu.eudat.commons.types.descriptiontemplate.FieldEntity fieldEntity = definition.getFieldById(model.getFieldId()).stream().filter(x -> x != null && x.getData() != null && x.getData().getFieldType().equals(FieldType.UPLOAD)).findFirst().orElse(null); + if (fieldEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getFieldId(), eu.eudat.commons.types.descriptiontemplate.FieldEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + UploadDataEntity uploadDataEntity = (UploadDataEntity)fieldEntity.getData(); + if (DataSize.ofBytes(file.getSize()).equals(DataSize.ofMegabytes(uploadDataEntity.getMaxFileSizeInMB()))) { + throw new MyValidationException("The uploaded file is too large"); + } + if(!this.conventionService.isListNullOrEmpty(uploadDataEntity.getTypes())) { + boolean isContentTypeAccepted = false; + for (UploadDataEntity.UploadDataOptionEntity option: uploadDataEntity.getTypes()) { + if(Objects.equals(file.getContentType(), option.getValue())) { + isContentTypeAccepted = true; + break; + } + } + if (!isContentTypeAccepted){ + throw new MyValidationException("The uploaded file has an unaccepted type"); + } + } + StorageFilePersist storageFilePersist = new StorageFilePersist(); + storageFilePersist.setName(FilenameUtils.removeExtension(file.getName())); + storageFilePersist.setExtension(FilenameUtils.getExtension(file.getName())); + storageFilePersist.setMimeType(URLConnection.guessContentTypeFromName(file.getName())); + storageFilePersist.setOwnerId(this.userScope.getUserIdSafe()); + storageFilePersist.setStorageType(StorageType.Temp); + storageFilePersist.setLifetime(Duration.ofSeconds(this.storageFileConfig.getTempStoreLifetimeSeconds())); + this.validatorFactory.validator(StorageFilePersist.StorageFilePersistValidator.class).validateForce(storageFilePersist); + return this.storageFileService.persistBytes(storageFilePersist, file.getBytes(), BaseFieldSet.build(fields, StorageFile._id, StorageFile._name)); + } + + @Override + public StorageFileEntity getFieldFile(UUID descriptionId, UUID storageFileId) { + this.authorizationService.authorizeForce(Permission.BrowseDescription); + DescriptionEntity descriptionEntity = this.queryFactory.query(DescriptionQuery.class).isActive(IsActive.Active).ids(descriptionId).first(); + if (descriptionEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, Description.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + DmpDescriptionTemplateEntity dmpDescriptionTemplateEntity = this.queryFactory.query(DmpDescriptionTemplateQuery.class).ids(descriptionEntity.getDmpDescriptionTemplateId()).isActive(IsActive.Active).first(); + if (dmpDescriptionTemplateEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionEntity.getDmpDescriptionTemplateId(), DmpDescriptionTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + DmpEntity dmpEntity = this.queryFactory.query(DmpQuery.class).ids(dmpDescriptionTemplateEntity.getDmpId()).isActive(IsActive.Active).first(); + if (dmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{dmpDescriptionTemplateEntity.getDmpId(), Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + if (!dmpEntity.getAccessType().equals(DmpAccessType.Public)) + { + boolean isDmpUser = this.queryFactory.query(DmpUserQuery.class).dmpIds(dmpEntity.getId()).userIds(this.userScope.getUserIdSafe()).isActives(IsActive.Active).count() > 0; + if (!isDmpUser) throw new MyUnauthorizedException(); + } + + StorageFileEntity storageFile = this.queryFactory.query(StorageFileQuery.class).ids(storageFileId).first(); + if (storageFile == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{storageFileId, StorageFile.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + return storageFile; + } } diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileService.java b/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileService.java index abf5dc94e..9b250176d 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileService.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileService.java @@ -16,8 +16,8 @@ import java.util.UUID; public interface StorageFileService extends ApplicationListener { StorageFile persistBytes(StorageFilePersist model, byte[] payload, FieldSet fields) throws IOException; StorageFile persistString(StorageFilePersist model, String payload, FieldSet fields, Charset charset) throws IOException; - boolean moveToStorage(UUID fileId, StorageType storageType); - boolean copyToStorage(UUID fileId, StorageType storageType); + StorageFile moveToStorage(UUID fileId, StorageType storageType, boolean replaceDestination, FieldSet fields); + StorageFile copyToStorage(UUID fileId, StorageType storageType, boolean replaceDestination, FieldSet fields); boolean exists(UUID fileId); diff --git a/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileServiceImpl.java b/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileServiceImpl.java index 1c75e93f0..f7bbf4188 100644 --- a/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileServiceImpl.java +++ b/dmp-backend/core/src/main/java/eu/eudat/service/storage/StorageFileServiceImpl.java @@ -93,7 +93,7 @@ public class StorageFileServiceImpl implements StorageFileService { this.entityManager.persist(storageFile); this.entityManager.flush(); - return this.builderFactory.builder(StorageFileBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(BaseFieldSet.build(fields, Description._id), storageFile); + return this.builderFactory.builder(StorageFileBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(BaseFieldSet.build(fields, StorageFile._id), storageFile); } @Override @@ -104,6 +104,7 @@ public class StorageFileServiceImpl implements StorageFileService { private StorageFileEntity buildDataEntry(StorageFilePersist model) { StorageFileEntity data = new StorageFileEntity(); + data.setId(UUID.randomUUID()); data.setFileRef(UUID.randomUUID().toString().replaceAll("-", "").toLowerCase(Locale.ROOT)); data.setName(model.getName()); data.setOwnerId(model.getOwnerId()); @@ -117,42 +118,74 @@ public class StorageFileServiceImpl implements StorageFileService { } @Override - public boolean moveToStorage(UUID fileId, StorageType storageType) { + public StorageFile moveToStorage(UUID fileId, StorageType storageType, boolean replaceDestination, FieldSet fields) { try { StorageFileEntity storageFile = this.entityManager.find(StorageFileEntity.class, fileId); - if (storageFile == null) return false; + if (storageFile == null) return null; + this.authorizeForce(storageFile, StorageFilePermission.Read); File file = new File(this.filePath(storageFile.getFileRef(), storageFile.getStorageType())); - if (!file.exists()) return false; + if (!file.exists()) return null; File destinationFile = new File(this.filePath(storageFile.getFileRef(), storageFile.getStorageType())); + if (file.exists() && !replaceDestination) return null; + boolean fileCopied = FileCopyUtils.copy(file, destinationFile) > 0; - if (!fileCopied) return false; - return file.delete(); + if (!fileCopied) return null; + + storageFile.setStorageType(storageType); + + this.entityManager.merge(storageFile); + + file.delete(); + + this.entityManager.merge(storageFile); + return this.builderFactory.builder(StorageFileBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(BaseFieldSet.build(fields, StorageFile._id), storageFile); } catch (Exception ex) { logger.warn("problem reading byte content of storage file " + fileId, ex); - return false; + return null; } } @Override - public boolean copyToStorage(UUID fileId, StorageType storageType) { + public StorageFile copyToStorage(UUID fileId, StorageType storageType, boolean replaceDestination, FieldSet fields) { try { StorageFileEntity storageFile = this.entityManager.find(StorageFileEntity.class, fileId); - if (storageFile == null) return false; + if (storageFile == null) return null; + this.authorizeForce(storageFile, StorageFilePermission.Read); File file = new File(this.filePath(storageFile.getFileRef(), storageFile.getStorageType())); - if (!file.exists()) return false; + if (!file.exists()) return null; - File destinationFile = new File(this.filePath(storageFile.getFileRef(), storageFile.getStorageType())); - return FileCopyUtils.copy(file, destinationFile) > 0; + File destinationFile = new File(this.filePath(storageFile.getFileRef(), storageType)); + if (file.exists() && !replaceDestination) return null; + + boolean fileCopied = FileCopyUtils.copy(file, destinationFile) > 0; + if (!fileCopied) return null; + + StorageFileEntity data = new StorageFileEntity(); + data.setId(UUID.randomUUID()); + data.setFileRef(storageFile.getFileRef()); + data.setName(storageFile.getName()); + data.setOwnerId(storageFile.getOwnerId()); + data.setExtension(storageFile.getExtension()); + data.setStorageType(storageFile.getStorageType()); + data.setMimeType(storageFile.getMimeType()); + data.setCreatedAt(Instant.now()); + data.setPurgeAt(storageFile.getPurgeAt()); + + this.entityManager.persist(data); + + this.entityManager.merge(storageFile); + return this.builderFactory.builder(StorageFileBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermissionOrPublic).build(BaseFieldSet.build(fields, StorageFile._id), storageFile); + } catch (Exception ex) { logger.warn("problem reading byte content of storage file " + fileId, ex); - return false; + return null; } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/DescriptionController.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/DescriptionController.java index 2bb2e4309..6d25f54cc 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/DescriptionController.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/v2/DescriptionController.java @@ -1,11 +1,19 @@ package eu.eudat.controllers.v2; -import com.fasterxml.jackson.core.JsonProcessingException; import eu.eudat.audit.AuditableAction; import eu.eudat.authorization.AuthorizationFlags; +import eu.eudat.authorization.Permission; import eu.eudat.commons.enums.DmpAccessType; import eu.eudat.commons.enums.DmpStatus; import eu.eudat.commons.enums.IsActive; +import eu.eudat.commons.enums.StorageType; +import eu.eudat.convention.ConventionService; +import eu.eudat.data.StorageFileEntity; +import eu.eudat.model.StorageFile; +import eu.eudat.model.persist.DescriptionFieldFilePersist; +import eu.eudat.model.persist.StorageFilePersist; +import eu.eudat.service.storage.StorageFileService; +import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.validation.ValidationFilterAnnotation; import eu.eudat.model.Description; import eu.eudat.model.Dmp; @@ -21,7 +29,6 @@ import eu.eudat.query.DmpQuery; import eu.eudat.query.lookup.DescriptionLookup; import eu.eudat.service.description.DescriptionService; import eu.eudat.service.elastic.ElasticQueryHelperService; -import eu.eudat.service.transformer.FileTransformerService; import gr.cite.tools.auditing.AuditService; import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.censor.CensorFactory; @@ -32,15 +39,22 @@ import gr.cite.tools.exception.MyNotFoundException; import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.MapLogEntry; +import org.apache.commons.io.FilenameUtils; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.management.InvalidApplicationException; import java.io.IOException; +import java.net.URLConnection; +import java.time.Duration; import java.util.*; import static eu.eudat.authorization.AuthorizationFlags.Public; @@ -63,14 +77,16 @@ public class DescriptionController { private final MessageSource messageSource; private final ElasticQueryHelperService elasticQueryHelperService; + private final StorageFileService storageFileService; + private final ConventionService conventionService; public DescriptionController( - BuilderFactory builderFactory, - AuditService auditService, - DescriptionService descriptionService, - CensorFactory censorFactory, - QueryFactory queryFactory, - MessageSource messageSource, - ElasticQueryHelperService elasticQueryHelperService) { + BuilderFactory builderFactory, + AuditService auditService, + DescriptionService descriptionService, + CensorFactory censorFactory, + QueryFactory queryFactory, + MessageSource messageSource, + ElasticQueryHelperService elasticQueryHelperService, StorageFileService storageFileService, ConventionService conventionService) { this.builderFactory = builderFactory; this.auditService = auditService; this.descriptionService = descriptionService; @@ -78,6 +94,8 @@ public class DescriptionController { this.queryFactory = queryFactory; this.messageSource = messageSource; this.elasticQueryHelperService = elasticQueryHelperService; + this.storageFileService = storageFileService; + this.conventionService = conventionService; } @PostMapping("public/query") @@ -192,4 +210,41 @@ public class DescriptionController { return this.descriptionService.export(id, exportType); } + + @PostMapping("field-file/upload") + @Transactional + @ValidationFilterAnnotation(validator = DescriptionFieldFilePersist.PersistValidator.ValidatorName, argumentName = "model") + public StorageFile uploadFieldFiles(@RequestParam("file") MultipartFile file, @RequestParam("model") DescriptionFieldFilePersist model, FieldSet fieldSet) throws IOException { + logger.debug(new MapLogEntry("uploadFieldFiles" + Description.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); + StorageFile persisted = this.descriptionService.uploadFieldFile(model, file, fieldSet); + + this.auditService.track(AuditableAction.Description_UploadFieldFiles, Map.ofEntries( + new AbstractMap.SimpleEntry("model", model), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + + return persisted; + } + + @GetMapping("{id}/field-file/{fileId}") + public ResponseEntity getFieldFile(@PathVariable("id") UUID id, @PathVariable("fileId") UUID fileId) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + logger.debug(new MapLogEntry("get Field File" + Description.class.getSimpleName()).And("id", id).And("fileId", fileId)); + + StorageFileEntity storageFile = this.descriptionService.getFieldFile(id, fileId); + + byte[] file = this.storageFileService.readAsBytesSafe(id); + if (file == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, StorageFile.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.auditService.track(AuditableAction.Description_GetFieldFile, Map.ofEntries( + new AbstractMap.SimpleEntry("id", id) + )); + + String contentType = storageFile.getMimeType(); + if (this.conventionService.isNullOrEmpty(contentType)) contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; + + return ResponseEntity.ok() + .contentType(MediaType.valueOf(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + storageFile.getName() + (storageFile.getExtension().startsWith(".") ? "" : ".") + storageFile.getExtension() + "\"") + .body(new ByteArrayResource(file)); + } }