package eu.eudat.service.dmp; import com.fasterxml.jackson.core.JsonProcessingException; import eu.eudat.authorization.AuthorizationFlags; import eu.eudat.authorization.Permission; import eu.eudat.authorization.authorizationcontentresolver.AuthorizationContentResolver; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.XmlHandlingService; import eu.eudat.commons.enums.*; import eu.eudat.commons.enums.notification.NotificationContactType; import eu.eudat.commons.scope.user.UserScope; import eu.eudat.commons.types.actionconfirmation.DmpInvitationEntity; import eu.eudat.commons.types.dmp.DmpBlueprintValueEntity; import eu.eudat.commons.types.dmp.DmpContactEntity; import eu.eudat.commons.types.dmp.DmpPropertiesEntity; import eu.eudat.commons.types.dmpblueprint.ReferenceTypeFieldEntity; import eu.eudat.commons.types.dmpreference.DmpReferenceDataEntity; import eu.eudat.commons.types.notification.*; import eu.eudat.commons.types.reference.DefinitionEntity; import eu.eudat.commons.types.reference.FieldEntity; import eu.eudat.configurations.notification.NotificationProperties; import eu.eudat.convention.ConventionService; import eu.eudat.data.*; import eu.eudat.errorcode.ErrorThesaurusProperties; import eu.eudat.event.DmpTouchedEvent; import eu.eudat.event.EventBroker; import eu.eudat.integrationevent.outbox.annotationentitytouch.AnnotationEntityTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.dmptouched.DmpTouchedIntegrationEventHandler; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent; import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler; import eu.eudat.model.*; import eu.eudat.model.builder.DmpBuilder; import eu.eudat.model.builder.DmpUserBuilder; import eu.eudat.model.deleter.*; import eu.eudat.model.file.FileEnvelope; import eu.eudat.model.persist.*; import eu.eudat.model.persist.actionconfirmation.DmpInvitationPersist; import eu.eudat.model.persist.dmpproperties.DmpBlueprintValuePersist; import eu.eudat.model.persist.dmpproperties.DmpContactPersist; import eu.eudat.model.persist.dmpproperties.DmpPropertiesPersist; import eu.eudat.model.persist.dmpreference.DmpReferenceDataPersist; import eu.eudat.model.persist.referencedefinition.DefinitionPersist; import eu.eudat.model.persist.referencedefinition.FieldPersist; import eu.eudat.query.*; import eu.eudat.service.actionconfirmation.ActionConfirmationService; import eu.eudat.service.description.DescriptionService; import eu.eudat.service.elastic.ElasticService; 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.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.FieldSet; import gr.cite.tools.logging.LoggerService; import gr.cite.tools.validation.ValidatorFactory; import jakarta.persistence.EntityManager; import jakarta.xml.bind.JAXBException; import org.jetbrains.annotations.NotNull; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import javax.management.InvalidApplicationException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import java.io.IOException; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @Service public class DmpServiceImpl implements DmpService { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DmpServiceImpl.class)); private final EntityManager entityManager; private final AuthorizationService authorizationService; private final DeleterFactory deleterFactory; private final BuilderFactory builderFactory; private final QueryFactory queryFactory; private final ConventionService conventionService; private final ErrorThesaurusProperties errors; private final MessageSource messageSource; private final XmlHandlingService xmlHandlingService; private final JsonHandlingService jsonHandlingService; private final UserScope userScope; private final EventBroker eventBroker; private final DescriptionService descriptionService; private final FileTransformerService fileTransformerService; private final NotifyIntegrationEventHandler eventHandler; private final NotificationProperties notificationProperties; private final ActionConfirmationService actionConfirmationService; private final gr.cite.tools.validation.ValidatorFactory validatorFactory; private final ElasticService elasticService; private final DmpTouchedIntegrationEventHandler dmpTouchedIntegrationEventHandler; private final AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler; private final AuthorizationContentResolver authorizationContentResolver; @Autowired public DmpServiceImpl( EntityManager entityManager, AuthorizationService authorizationService, DeleterFactory deleterFactory, BuilderFactory builderFactory, QueryFactory queryFactory, ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, XmlHandlingService xmlHandlingService, JsonHandlingService jsonHandlingService, UserScope userScope, EventBroker eventBroker, DescriptionService descriptionService, NotifyIntegrationEventHandler eventHandler, NotificationProperties notificationProperties, ActionConfirmationService actionConfirmationService, FileTransformerService fileTransformerService, ValidatorFactory validatorFactory, ElasticService elasticService, DmpTouchedIntegrationEventHandler dmpTouchedIntegrationEventHandler, AnnotationEntityTouchedIntegrationEventHandler annotationEntityTouchedIntegrationEventHandler, AuthorizationContentResolver authorizationContentResolver) { this.entityManager = entityManager; this.authorizationService = authorizationService; this.deleterFactory = deleterFactory; this.builderFactory = builderFactory; this.queryFactory = queryFactory; this.conventionService = conventionService; this.errors = errors; this.messageSource = messageSource; this.xmlHandlingService = xmlHandlingService; this.jsonHandlingService = jsonHandlingService; this.userScope = userScope; this.eventBroker = eventBroker; this.descriptionService = descriptionService; this.fileTransformerService = fileTransformerService; this.eventHandler = eventHandler; this.notificationProperties = notificationProperties; this.actionConfirmationService = actionConfirmationService; this.validatorFactory = validatorFactory; this.elasticService = elasticService; this.dmpTouchedIntegrationEventHandler = dmpTouchedIntegrationEventHandler; this.annotationEntityTouchedIntegrationEventHandler = annotationEntityTouchedIntegrationEventHandler; this.authorizationContentResolver = authorizationContentResolver; } public Dmp persist(DmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException, IOException { Boolean isUpdate = this.conventionService.isValidGuid(model.getId()); if (isUpdate) this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(model.getId())), Permission.EditDmp); else this.authorizationService.authorizeForce(Permission.NewDmp); DmpEntity data = this.patchAndSave(model); DmpBlueprintEntity blueprintEntity = this.entityManager.find(DmpBlueprintEntity.class, data.getBlueprintId()); if (blueprintEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{data.getBlueprintId(), DmpBlueprint.class.getSimpleName()}, LocaleContextHolder.getLocale())); eu.eudat.commons.types.dmpblueprint.DefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(eu.eudat.commons.types.dmpblueprint.DefinitionEntity.class, blueprintEntity.getDefinition()); if (definition == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{data.getBlueprintId(), eu.eudat.commons.types.dmpblueprint.DefinitionEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.patchAndSaveReferences(this.buildDmpReferencePersists(model.getProperties()), data.getId(), definition); this.patchAndSaveTemplates(data.getId(), model.getDescriptionTemplates()); if (!isUpdate || userScope.isSet()) { this.addOwner(data); if (model.getUsers() == null) model.setUsers(new ArrayList<>()); if (model.getUsers().stream().noneMatch(x-> x.getUser() != null && x.getUser().equals(this.userScope.getUserIdSafe()) && DmpUserRole.Owner.equals(x.getRole()))) model.getUsers().add(this.createOwnerPersist()); } this.eventBroker.emit(new DmpTouchedEvent(data.getId())); this.dmpTouchedIntegrationEventHandler.handle(DmpTouchedIntegrationEventHandler.buildEventFromPersistModel(model)); this.annotationEntityTouchedIntegrationEventHandler.handle(AnnotationEntityTouchedIntegrationEventHandler.buildEventFromPersistModel(model)); this.sendNotification(data); if (!this.conventionService.isListNullOrEmpty(model.getUsers())){ this.inviteUserOrAssignUsers(data.getId(), model.getUsers()); } this.elasticService.persistDmp(data); return this.builderFactory.builder(DmpBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Dmp._id, Dmp._hash), data); } private DmpUserPersist createOwnerPersist() { DmpUserPersist persist = new DmpUserPersist(); persist.setRole(DmpUserRole.Owner); persist.setUser(userScope.getUserIdSafe()); return persist; } private void addOwner(DmpEntity dmpEntity) throws InvalidApplicationException { DmpUserEntity data = new DmpUserEntity(); data.setId(UUID.randomUUID()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); data.setUpdatedAt(Instant.now()); data.setRole(DmpUserRole.Owner); data.setUserId(userScope.getUserId()); data.setDmpId(dmpEntity.getId()); this.entityManager.persist(data); } private void sendNotification(DmpEntity dmp) throws InvalidApplicationException { List existingUsers = this.queryFactory.query(DmpUserQuery.class) .dmpIds(dmp.getId()) .isActives(IsActive.Active) .collect(); if (existingUsers == null || existingUsers.size() <= 1){ return; } for (DmpUserEntity dmpUser : existingUsers) { if (!dmpUser.getUserId().equals(this.userScope.getUserIdSafe())){ UserEntity user = this.queryFactory.query(UserQuery.class).ids(dmpUser.getUserId()).first(); if (user != null){ this.createDmpNotificationEvent(dmp, user, NotificationContactType.EMAIL); this.createDmpNotificationEvent(dmp, user, NotificationContactType.IN_APP); } } } } private void createDmpNotificationEvent(DmpEntity dmp, UserEntity user, NotificationContactType type) throws InvalidApplicationException { NotifyIntegrationEvent event = new NotifyIntegrationEvent(); event.setUserId(this.userScope.getUserId()); UserContactInfoQuery query = this.queryFactory.query(UserContactInfoQuery.class).userIds(user.getId()); query.setOrder(new Ordering().addAscending(UserContactInfo._ordinal)); List contactPairs = new ArrayList<>(); contactPairs.add(new ContactPair(ContactInfoType.Email, query.first().getValue())); NotificationContactData contactData = new NotificationContactData(contactPairs, null, null); event.setContactHint(jsonHandlingService.toJsonSafe(contactData)); event.setContactTypeHint(type); this.applyNotificationType(dmp.getStatus(), event); NotificationFieldData data = new NotificationFieldData(); List fieldInfoList = new ArrayList<>(); fieldInfoList.add(new FieldInfo("{recipient}", DataType.String, user.getName())); fieldInfoList.add(new FieldInfo("{reasonName}", DataType.String, this.queryFactory.query(UserQuery.class).ids(this.userScope.getUserId()).first().getName())); fieldInfoList.add(new FieldInfo("{name}", DataType.String, dmp.getLabel())); fieldInfoList.add(new FieldInfo("{id}", DataType.String, dmp.getId().toString())); data.setFields(fieldInfoList); event.setData(jsonHandlingService.toJsonSafe(data)); eventHandler.handle(event); } private void applyNotificationType(DmpStatus status, NotifyIntegrationEvent event) { switch (status) { case Draft: event.setNotificationType(notificationProperties.getDmpModifiedType()); break; case Finalized: event.setNotificationType(notificationProperties.getDmpFinalisedType()); break; default: throw new MyApplicationException("Unsupported Dmp Status."); } } public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException, IOException { logger.debug("deleting dmp: {}", id); this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(id)), Permission.DeleteDmp); this.deleterFactory.deleter(DmpDeleter.class).deleteAndSaveByIds(List.of(id), false); } @Override public Dmp createNewVersion(NewVersionDmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, IOException, TransformerException { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation( model.getId())), Permission.CreateNewVersionDmp); DmpEntity oldDmpEntity = this.entityManager.find(DmpEntity.class, model.getId()); if (oldDmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (!this.conventionService.hashValue(oldDmpEntity.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); DmpQuery latestVersionDmpEntityQuery = this.queryFactory.query(DmpQuery.class).groupIds(oldDmpEntity.getGroupId()).versionStatuses(DmpVersionStatus.Current); List latestVersionDmps = latestVersionDmpEntityQuery.collect(); if (latestVersionDmps.isEmpty()) throw new MyValidationException("Previous dmp not found"); if (latestVersionDmps.size() > 1) throw new MyValidationException("Multiple previous dmps found"); if (!latestVersionDmps.getFirst().getVersion().equals(oldDmpEntity.getVersion())){ throw new MyValidationException(this.errors.getDmpNewVersionConflict().getCode(), this.errors.getDmpNewVersionConflict().getMessage()); } DmpEntity newDmp = new DmpEntity(); newDmp.setId(UUID.randomUUID()); newDmp.setIsActive(IsActive.Active); newDmp.setCreatedAt(Instant.now()); newDmp.setUpdatedAt(Instant.now()); newDmp.setGroupId(oldDmpEntity.getGroupId()); newDmp.setVersionStatus(DmpVersionStatus.Current); newDmp.setVersion((short)(oldDmpEntity.getVersion() + 1)); newDmp.setDescription(model.getDescription()); newDmp.setLabel(model.getLabel()); newDmp.setLanguage(oldDmpEntity.getLanguage()); newDmp.setStatus(DmpStatus.Draft); newDmp.setProperties(oldDmpEntity.getProperties()); newDmp.setBlueprintId(model.getBlueprintId()); newDmp.setCreatorId(this.userScope.getUserId()); this.entityManager.persist(newDmp); List dmpUsers = this.queryFactory.query(DmpUserQuery.class) .dmpIds(model.getId()) .isActives(IsActive.Active) .collect(); List dmpReferences = this.queryFactory.query(DmpReferenceQuery.class) .dmpIds(model.getId()) .isActives(IsActive.Active) .collect(); List dmpDescriptionTemplates = this.queryFactory.query(DmpDescriptionTemplateQuery.class) .dmpIds(model.getId()) .isActive(IsActive.Active) .collect(); for (DmpUserEntity dmpUser : dmpUsers) { DmpUserEntity newUser = new DmpUserEntity(); newUser.setId(UUID.randomUUID()); newUser.setDmpId(newDmp.getId()); newUser.setUserId(dmpUser.getUserId()); newUser.setRole(dmpUser.getRole()); newUser.setCreatedAt(Instant.now()); newUser.setUpdatedAt(Instant.now()); newUser.setIsActive(IsActive.Active); this.entityManager.persist(newUser); } for (DmpReferenceEntity dmpReference : dmpReferences) { DmpReferenceEntity newReference = new DmpReferenceEntity(); newReference.setId(UUID.randomUUID()); newReference.setDmpId(newDmp.getId()); newReference.setReferenceId(dmpReference.getReferenceId()); newReference.setData(dmpReference.getData()); newReference.setCreatedAt(Instant.now()); newReference.setUpdatedAt(Instant.now()); newReference.setIsActive(IsActive.Active); this.entityManager.persist(newReference); } for (DmpDescriptionTemplateEntity dmpDescriptionTemplate : dmpDescriptionTemplates) { DmpDescriptionTemplateEntity newTemplate = new DmpDescriptionTemplateEntity(); newTemplate.setId(UUID.randomUUID()); newTemplate.setDmpId(newDmp.getId()); newTemplate.setDescriptionTemplateGroupId(dmpDescriptionTemplate.getDescriptionTemplateGroupId()); newTemplate.setSectionId(dmpDescriptionTemplate.getSectionId()); newTemplate.setCreatedAt(Instant.now()); newTemplate.setUpdatedAt(Instant.now()); newTemplate.setIsActive(IsActive.Active); this.entityManager.persist(newTemplate); } for (UUID descriptionId : model.getDescriptions()) { this.descriptionService.clone(newDmp.getId(), descriptionId); } oldDmpEntity.setVersionStatus(DmpVersionStatus.Previous); this.entityManager.merge(oldDmpEntity); this.entityManager.flush(); this.elasticService.persistDmp(oldDmpEntity); this.elasticService.persistDmp(newDmp); return this.builderFactory.builder(DmpBuilder.class).build(BaseFieldSet.build(fields, Dmp._id), newDmp); } @Override public Dmp buildClone(CloneDmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, IOException, InvalidApplicationException { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation( model.getId())), Permission.CloneDmp); DmpEntity existingDmpEntity = this.queryFactory.query(DmpQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).ids(model.getId()).firstAs(fields); if (!this.conventionService.isValidGuid(model.getId()) || existingDmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); DmpEntity newDmp = new DmpEntity(); newDmp.setId(UUID.randomUUID()); newDmp.setIsActive(IsActive.Active); newDmp.setCreatedAt(Instant.now()); newDmp.setUpdatedAt(Instant.now()); newDmp.setGroupId(UUID.randomUUID()); newDmp.setVersion((short) 1); newDmp.setVersionStatus(DmpVersionStatus.Current); newDmp.setDescription(model.getDescription()); newDmp.setLabel(model.getLabel()); newDmp.setLanguage(existingDmpEntity.getLanguage()); newDmp.setStatus(DmpStatus.Draft); newDmp.setProperties(existingDmpEntity.getProperties()); newDmp.setBlueprintId(existingDmpEntity.getBlueprintId()); newDmp.setAccessType(existingDmpEntity.getAccessType()); newDmp.setCreatorId(this.userScope.getUserId()); this.entityManager.persist(newDmp); List dmpUsers = this.queryFactory.query(DmpUserQuery.class) .dmpIds(model.getId()) .isActives(IsActive.Active) .collect(); List dmpReferences = this.queryFactory.query(DmpReferenceQuery.class) .dmpIds(model.getId()) .isActives(IsActive.Active) .collect(); List dmpDescriptionTemplates = this.queryFactory.query(DmpDescriptionTemplateQuery.class) .dmpIds(model.getId()) .isActive(IsActive.Active) .collect(); for (DmpUserEntity dmpUser : dmpUsers) { DmpUserEntity newUser = new DmpUserEntity(); newUser.setId(UUID.randomUUID()); newUser.setDmpId(newDmp.getId()); newUser.setUserId(dmpUser.getUserId()); newUser.setRole(dmpUser.getRole()); newUser.setCreatedAt(Instant.now()); newUser.setUpdatedAt(Instant.now()); newUser.setIsActive(IsActive.Active); this.entityManager.persist(newUser); } for (DmpReferenceEntity dmpReference : dmpReferences) { DmpReferenceEntity newReference = new DmpReferenceEntity(); newReference.setId(UUID.randomUUID()); newReference.setDmpId(newDmp.getId()); newReference.setReferenceId(dmpReference.getReferenceId()); newReference.setData(dmpReference.getData()); newReference.setCreatedAt(Instant.now()); newReference.setUpdatedAt(Instant.now()); newReference.setIsActive(IsActive.Active); this.entityManager.persist(newReference); } for (DmpDescriptionTemplateEntity dmpDescriptionTemplate : dmpDescriptionTemplates) { DmpDescriptionTemplateEntity newTemplate = new DmpDescriptionTemplateEntity(); newTemplate.setId(UUID.randomUUID()); newTemplate.setDmpId(newDmp.getId()); newTemplate.setDescriptionTemplateGroupId(dmpDescriptionTemplate.getDescriptionTemplateGroupId()); newTemplate.setSectionId(dmpDescriptionTemplate.getSectionId()); newTemplate.setCreatedAt(Instant.now()); newTemplate.setUpdatedAt(Instant.now()); newTemplate.setIsActive(IsActive.Active); this.entityManager.persist(newTemplate); } this.entityManager.flush(); this.elasticService.persistDmp(newDmp); DmpEntity resultingDmpEntity = this.queryFactory.query(DmpQuery.class).ids(newDmp.getId()).firstAs(fields); return this.builderFactory.builder(DmpBuilder.class).build(fields, resultingDmpEntity); } @Override public List assignUsers(UUID dmpId, List model, FieldSet fieldSet, boolean disableDelete) throws InvalidApplicationException, IOException { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(dmpId)), Permission.AssignDmpUsers); DmpEntity dmpEntity = this.entityManager.find(DmpEntity.class, dmpId); if (dmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{dmpId, Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); List existingUsers = this.queryFactory.query(DmpUserQuery.class) .dmpIds(dmpId) .isActives(IsActive.Active) .collect(); List updatedCreatedIds = new ArrayList<>(); for (DmpUserPersist dmpUser : model) { DmpUserEntity dmpUserEntity = existingUsers.stream().filter(x-> x.getDmpId().equals(dmpId) && x.getUserId().equals(dmpUser.getUser()) && x.getRole().equals(dmpUser.getRole())).findFirst().orElse(null); if (dmpUserEntity == null){ dmpUserEntity = new DmpUserEntity(); dmpUserEntity.setId(UUID.randomUUID()); dmpUserEntity.setDmpId(dmpId); dmpUserEntity.setUserId(dmpUser.getUser()); dmpUserEntity.setRole(dmpUser.getRole()); dmpUserEntity.setSectionId(dmpUser.getSectionId()); dmpUserEntity.setCreatedAt(Instant.now()); dmpUserEntity.setUpdatedAt(Instant.now()); dmpUserEntity.setIsActive(IsActive.Active); this.entityManager.persist(dmpUserEntity); } updatedCreatedIds.add(dmpUserEntity.getUserId()); } List toDelete = existingUsers.stream().filter(x-> updatedCreatedIds.stream().noneMatch(y-> y.equals(x.getUserId()))).collect(Collectors.toList()); if (!toDelete.isEmpty() && !disableDelete) this.deleterFactory.deleter(DmpUserDeleter.class).delete(toDelete); this.entityManager.flush(); List persisted = this.queryFactory.query(DmpUserQuery.class) .dmpIds(dmpId) .isActives(IsActive.Active) .collect(); this.elasticService.persistDmp(dmpEntity); return this.builderFactory.builder(DmpUserBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fieldSet, DmpUser._id, DmpUser._hash), persisted); } @Override public Dmp removeUser(DmpUserRemovePersist model, FieldSet fields) throws InvalidApplicationException, IOException { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(model.getDmpId())), Permission.AssignDmpUsers); DmpEntity data = this.entityManager.find(DmpEntity.class, model.getDmpId()); if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); List existingUsers = this.queryFactory.query(DmpUserQuery.class) .dmpIds(model.getDmpId()).ids(model.getId()).userRoles(model.getRole()) .collect(); if (!existingUsers.isEmpty()) this.deleterFactory.deleter(DmpUserDeleter.class).delete(existingUsers); this.entityManager.flush(); DmpEntity dmpEntity = this.entityManager.find(DmpEntity.class, model.getDmpId()); if (dmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getDmpId(), Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); this.elasticService.persistDmp(dmpEntity); return this.builderFactory.builder(DmpBuilder.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(BaseFieldSet.build(fields, Dmp._id, Dmp._hash), data); } @Override public ResponseEntity export(UUID id, String transformerId, String exportType) throws InvalidApplicationException, IOException { HttpHeaders headers = new HttpHeaders(); FileEnvelope fileEnvelope = this.fileTransformerService.exportDmp(id, transformerId, exportType); headers.add("Content-Disposition", "attachment;filename=" + fileEnvelope.getFilename()); byte[] data = fileEnvelope.getFile(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); return new ResponseEntity<>(data, headers, HttpStatus.OK); } private DmpEntity patchAndSave(DmpPersist model) throws JsonProcessingException, InvalidApplicationException { Boolean isUpdate = this.conventionService.isValidGuid(model.getId()); DmpEntity data; if (isUpdate) { data = this.entityManager.find(DmpEntity.class, model.getId()); if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); if (model.getStatus() != null && model.getStatus() == DmpStatus.Finalized && data.getStatus() != DmpStatus.Finalized) { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(model.getId())), Permission.FinalizeDmp); data.setStatus(model.getStatus()); data.setFinalizedAt(Instant.now()); } } else { data = new DmpEntity(); data.setId(UUID.randomUUID()); data.setGroupId(UUID.randomUUID()); data.setVersion((short) 1); data.setStatus(DmpStatus.Draft); data.setVersionStatus(DmpVersionStatus.Current); data.setCreatorId(userScope.getUserId()); data.setBlueprintId(model.getBlueprint()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); } data.setLabel(model.getLabel()); data.setLanguage(model.getLanguage()); data.setProperties(this.jsonHandlingService.toJson(this.buildDmpPropertiesEntity(model.getProperties()))); data.setDescription(model.getDescription()); data.setAccessType(model.getAccessType()); data.setUpdatedAt(Instant.now()); if (isUpdate) this.entityManager.merge(data); else { this.entityManager.persist(data); } this.entityManager.flush(); return data; } private @NotNull DmpPropertiesEntity buildDmpPropertiesEntity(DmpPropertiesPersist persist){ DmpPropertiesEntity data = new DmpPropertiesEntity(); if (persist == null) return data; if (!this.conventionService.isListNullOrEmpty(persist.getContacts())){ data.setContacts(new ArrayList<>()); for (DmpContactPersist contactPersist: persist.getContacts()) { data.getContacts().add(this.buildDmpContactEntity(contactPersist)); } } if (persist.getDmpBlueprintValues() != null && !persist.getDmpBlueprintValues().isEmpty()){ data.setDmpBlueprintValues(new ArrayList<>()); for (DmpBlueprintValuePersist fieldValuePersist: persist.getDmpBlueprintValues().values()) { if (!this.conventionService.isNullOrEmpty(fieldValuePersist.getFieldValue())) data.getDmpBlueprintValues().add(this.buildDmpBlueprintValueEntity(fieldValuePersist)); } } return data; } private @NotNull DmpContactEntity buildDmpContactEntity(DmpContactPersist persist){ DmpContactEntity data = new DmpContactEntity(); if (persist == null) return data; data.setEmail(persist.getEmail()); data.setLastName(persist.getLastName()); data.setFirstName(persist.getFirstName()); data.setUserId(persist.getUserId()); return data; } private @NotNull DmpBlueprintValueEntity buildDmpBlueprintValueEntity(DmpBlueprintValuePersist persist){ DmpBlueprintValueEntity data = new DmpBlueprintValueEntity(); if (persist == null) return data; data.setValue(persist.getFieldValue()); data.setFieldId(persist.getFieldId()); return data; } private @NotNull List buildDmpReferencePersists(DmpPropertiesPersist persist){ List dmpReferencePersists = new ArrayList<>(); if (persist.getDmpBlueprintValues() != null && !persist.getDmpBlueprintValues().isEmpty()){ for (DmpBlueprintValuePersist fieldValuePersist: persist.getDmpBlueprintValues().values()) { if (this.conventionService.isNullOrEmpty(fieldValuePersist.getFieldValue()) && !this.conventionService.isListNullOrEmpty( fieldValuePersist.getReferences())) { for (ReferencePersist referencePersist : fieldValuePersist.getReferences()) { DmpReferencePersist dmpReferencePersist = new DmpReferencePersist(); dmpReferencePersist.setData(new DmpReferenceDataPersist()); dmpReferencePersist.getData().setBlueprintFieldId(fieldValuePersist.getFieldId()); dmpReferencePersist.setReference(referencePersist); dmpReferencePersists.add(dmpReferencePersist); } } } } return dmpReferencePersists; } private void patchAndSaveReferences(List models, UUID dmpId, eu.eudat.commons.types.dmpblueprint.DefinitionEntity blueprintDefinition) throws InvalidApplicationException { if (models == null) models = new ArrayList<>(); List dmpReferences = this.queryFactory.query(DmpReferenceQuery.class).dmpIds(dmpId).collect(); Map> dmpReferenceEntityByReferenceId = new HashMap<>(); for (DmpReferenceEntity dmpReferenceEntity : dmpReferences){ List dmpReferenceEntities = dmpReferenceEntityByReferenceId.getOrDefault(dmpReferenceEntity.getReferenceId(), null); if (dmpReferenceEntities == null) { dmpReferenceEntities = new ArrayList<>(); dmpReferenceEntityByReferenceId.put(dmpReferenceEntity.getReferenceId(), dmpReferenceEntities); } dmpReferenceEntities.add(dmpReferenceEntity); } Map dmpReferenceDataEntityMap = new HashMap<>(); for (DmpReferenceEntity dmpReferenceEntity : dmpReferences){ dmpReferenceDataEntityMap.put(dmpReferenceEntity.getId(), this.jsonHandlingService.fromJsonSafe(DmpReferenceDataEntity.class, dmpReferenceEntity.getData())); } List updatedCreatedIds = new ArrayList<>(); for (DmpReferencePersist model : models) { ReferencePersist referencePersist = model.getReference(); ReferenceEntity referenceEntity = null; if (this.conventionService.isValidGuid(referencePersist.getId())){ referenceEntity = this.entityManager.find(ReferenceEntity.class, referencePersist.getId()); if (referenceEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{referencePersist.getId(), Reference.class.getSimpleName()}, LocaleContextHolder.getLocale())); } else { ReferenceTypeFieldEntity fieldEntity = blueprintDefinition.getFieldById(model.getData().getBlueprintFieldId()).stream().filter(x-> x.getCategory().equals(DmpBlueprintFieldCategory.ReferenceType)).map(x-> (ReferenceTypeFieldEntity)x).findFirst().orElse(null); if (fieldEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getData().getBlueprintFieldId(), ReferenceTypeFieldEntity.class.getSimpleName()}, LocaleContextHolder.getLocale())); referenceEntity = this.queryFactory.query(ReferenceQuery.class).sourceTypes(referencePersist.getSourceType()).typeIds(fieldEntity.getReferenceTypeId()).sources(referencePersist.getSource()).isActive(IsActive.Active).references(referencePersist.getReference()).first(); if (referenceEntity == null){ referenceEntity = new ReferenceEntity(); referenceEntity.setId(UUID.randomUUID()); referenceEntity.setLabel(referencePersist.getLabel()); referenceEntity.setIsActive(IsActive.Active); referenceEntity.setCreatedAt(Instant.now()); referenceEntity.setTypeId(fieldEntity.getReferenceTypeId()); referenceEntity.setDefinition(this.xmlHandlingService.toXmlSafe(this.buildDefinitionEntity(referencePersist.getDefinition()))); referenceEntity.setUpdatedAt(Instant.now()); referenceEntity.setReference(referencePersist.getReference()); referenceEntity.setAbbreviation(referencePersist.getAbbreviation()); referenceEntity.setSource(referencePersist.getSource()); referenceEntity.setSourceType(referencePersist.getSourceType()); this.entityManager.persist(referenceEntity); } } DmpReferenceEntity data = null; List dmpReferenceEntities = dmpReferenceEntityByReferenceId.getOrDefault(referenceEntity.getId(), new ArrayList<>()); for (DmpReferenceEntity dmpReferenceEntity : dmpReferenceEntities){ DmpReferenceDataEntity dmpReferenceDataEntity = dmpReferenceDataEntityMap.getOrDefault(dmpReferenceEntity.getId(), new DmpReferenceDataEntity()); if (Objects.equals(dmpReferenceDataEntity.getBlueprintFieldId(), model.getData().getBlueprintFieldId())){ data = dmpReferenceEntity; break; } } boolean isUpdate = data != null; if (!isUpdate) { data = new DmpReferenceEntity(); data.setId(UUID.randomUUID()); data.setReferenceId(referenceEntity.getId()); data.setDmpId(dmpId); data.setCreatedAt(Instant.now()); data.setIsActive(IsActive.Active); data.setData(this.jsonHandlingService.toJsonSafe(this.buildDmpReferenceDataEntity(model.getData()))); } updatedCreatedIds.add(data.getId()); data.setUpdatedAt(Instant.now()); if (isUpdate) this.entityManager.merge(data); else this.entityManager.persist(data); } List toDelete = dmpReferences.stream().filter(x-> updatedCreatedIds.stream().noneMatch(y-> y.equals(x.getId()))).collect(Collectors.toList()); this.deleterFactory.deleter(DmpReferenceDeleter.class).delete(toDelete); this.entityManager.flush(); } private void patchAndSaveTemplates(UUID id, List models) throws InvalidApplicationException { if (models == null) models = new ArrayList<>(); List items = this.queryFactory.query(DmpDescriptionTemplateQuery.class).isActive(IsActive.Active).dmpIds(id).collect(); List updatedCreatedIds = new ArrayList<>(); for (DmpDescriptionTemplatePersist model : models) { DmpDescriptionTemplateEntity data = items.stream().filter(x -> x.getDescriptionTemplateGroupId().equals(model.getDescriptionTemplateGroupId()) && x.getSectionId().equals(model.getSectionId())).findFirst().orElse(null); if (data == null){ data = new DmpDescriptionTemplateEntity(); data.setId(UUID.randomUUID()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); data.setUpdatedAt(Instant.now()); data.setDmpId(id); data.setSectionId(model.getSectionId()); data.setDescriptionTemplateGroupId(model.getDescriptionTemplateGroupId()); this.entityManager.persist(data); } updatedCreatedIds.add(data.getId()); } List toDelete = items.stream().filter(x-> updatedCreatedIds.stream().noneMatch(y-> y.equals(x.getId()))).collect(Collectors.toList()); this.deleterFactory.deleter(DmpDescriptionTemplateDeleter.class).delete(toDelete); } private @NotNull DefinitionEntity buildDefinitionEntity(DefinitionPersist persist){ DefinitionEntity data = new DefinitionEntity(); if (persist == null) return data; if (!this.conventionService.isListNullOrEmpty(persist.getFields())){ data.setFields(new ArrayList<>()); for (FieldPersist fieldPersist: persist.getFields()) { data.getFields().add(this.buildDmpContactEntity(fieldPersist)); } } return data; } private @NotNull DmpReferenceDataEntity buildDmpReferenceDataEntity(DmpReferenceDataPersist persist){ DmpReferenceDataEntity data = new DmpReferenceDataEntity(); if (persist == null) return data; data.setBlueprintFieldId(persist.getBlueprintFieldId()); return data; } private @NotNull FieldEntity buildDmpContactEntity(FieldPersist persist){ FieldEntity data = new FieldEntity(); if (persist == null) return data; data.setCode(persist.getCode()); data.setDataType(persist.getDataType()); data.setCode(persist.getCode()); return data; } private DmpUserEntity checkUserRoleIfExists(List dmpUserEntities, UUID dmp, UUID user, DmpUserRole role) { for (DmpUserEntity dmpUser : dmpUserEntities) { if (dmpUser.getDmpId().equals(dmp) && dmpUser.getUserId().equals(user) && dmpUser.getRole() == role) { return dmpUser; }; } return null; } public void finalize(UUID id, List descriptionIds) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(id)), Permission.FinalizeDmp); DmpEntity dmp = this.queryFactory.query(DmpQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).ids(id).isActive(IsActive.Active).first(); if (dmp == null){ throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); } if (dmp.getStatus().equals(DmpStatus.Finalized)){ throw new MyApplicationException("DMP is already finalized"); } List descriptions = this.queryFactory.query(DescriptionQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).dmpIds(id).isActive(IsActive.Active).collect(); for (DescriptionEntity description: descriptions) { if (descriptionIds.contains(description.getId())){ // description to be finalized if (description.getStatus().equals(DescriptionStatus.Finalized)){ throw new MyApplicationException("Description is already finalized"); } if (this.descriptionService.validate(List.of(description.getId())).get(0).getResult().equals(DescriptionValidationOutput.Invalid)){ throw new MyApplicationException("Description is invalid"); } description.setStatus(DescriptionStatus.Finalized); description.setUpdatedAt(Instant.now()); description.setFinalizedAt(Instant.now()); this.entityManager.merge(description); } else if (description.getStatus().equals(DescriptionStatus.Draft)) { // description to be canceled description.setStatus(DescriptionStatus.Canceled); this.deleterFactory.deleter(DescriptionDeleter.class).delete(List.of(description), true); } } dmp.setStatus(DmpStatus.Finalized); dmp.setUpdatedAt(Instant.now()); dmp.setFinalizedAt(Instant.now()); this.entityManager.merge(dmp); this.entityManager.flush(); this.elasticService.persistDmp(dmp); this.sendNotification(dmp); } public void undoFinalize(UUID id, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, IOException { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(id)), Permission.UndoFinalizeDmp); DmpEntity dmp = this.queryFactory.query(DmpQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).ids(id).isActive(IsActive.Active).firstAs(fields); if (dmp == null){ throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale())); } if (dmp.getStatus().equals(DmpStatus.Draft)){ throw new MyApplicationException("DMP is already drafted"); } EntityDoiQuery entityDoiQuery = this.queryFactory.query(EntityDoiQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).types(EntityType.DMP).entityIds(dmp.getId()); if (entityDoiQuery != null && entityDoiQuery.count() > 0){ throw new MyApplicationException("DMP is deposited"); } dmp.setStatus(DmpStatus.Draft); dmp.setUpdatedAt(Instant.now()); this.entityManager.merge(dmp); this.entityManager.flush(); this.sendNotification(dmp); } // invites public void inviteUserOrAssignUsers(UUID id, List users) throws InvalidApplicationException, JAXBException, IOException { this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(id)), Permission.InviteDmpUsers); DmpEntity dmp = this.queryFactory.query(DmpQuery.class).ids(id).first(); if (dmp == null){ throw new InvalidApplicationException("Dmp does not exist!"); } List usersToAssign = new ArrayList<>(); for (DmpUserPersist user :users) { UUID userId = null; if (user.getUser() != null){ userId = user.getUser(); } else if (user.getEmail() != null) { UserContactInfoEntity contactInfoEntity = this.queryFactory.query(UserContactInfoQuery.class).values(user.getEmail()).types(ContactInfoType.Email).first(); if (contactInfoEntity != null){ userId = contactInfoEntity.getUserId(); } } if (userId != null){ user.setUser(userId); usersToAssign.add(user); if (this.userScope.getUserId() != userId){ this.sendDmpInvitationExistingUser(user.getUser(), dmp, user.getRole()); } }else if (user.getEmail() != null) { this.sendDmpInvitationExternalUser(user.getEmail(),dmp, user.getRole()); } } if(!usersToAssign.isEmpty()) this.assignUsers(id, usersToAssign, null, true); } private void sendDmpInvitationExistingUser(UUID userId, DmpEntity dmp, DmpUserRole role) throws InvalidApplicationException { UserEntity recipient = this.queryFactory.query(UserQuery.class).ids(userId).isActive(IsActive.Active).first(); String email = this.queryFactory.query(UserContactInfoQuery.class).userIds(recipient.getId()).first().getValue(); this.createDmpInvitationExistingUserEvent(recipient, dmp, role, email, NotificationContactType.EMAIL); this.createDmpInvitationExistingUserEvent(recipient, dmp, role, email, NotificationContactType.IN_APP); } private void createDmpInvitationExistingUserEvent(UserEntity recipient, DmpEntity dmp, DmpUserRole role, String email, NotificationContactType type) throws InvalidApplicationException { NotifyIntegrationEvent event = new NotifyIntegrationEvent(); event.setUserId(this.userScope.getUserIdSafe()); List contactPairs = new ArrayList<>(); contactPairs.add(new ContactPair(ContactInfoType.Email, email)); NotificationContactData contactData = new NotificationContactData(contactPairs, null, null); event.setContactHint(jsonHandlingService.toJsonSafe(contactData)); event.setContactTypeHint(type); event.setNotificationType(notificationProperties.getDmpInvitationExistingUserType()); NotificationFieldData data = new NotificationFieldData(); List fieldInfoList = new ArrayList<>(); fieldInfoList.add(new FieldInfo("{recipient}", DataType.String, recipient.getName())); fieldInfoList.add(new FieldInfo("{reasonName}", DataType.String, this.queryFactory.query(UserQuery.class).ids(this.userScope.getUserIdSafe()).first().getName())); fieldInfoList.add(new FieldInfo("{dmpname}", DataType.String, dmp.getLabel())); fieldInfoList.add(new FieldInfo("{dmprole}", DataType.String, role.toString())); fieldInfoList.add(new FieldInfo("{id}", DataType.String, dmp.getId().toString())); data.setFields(fieldInfoList); event.setData(jsonHandlingService.toJsonSafe(data)); eventHandler.handle(event); } private void sendDmpInvitationExternalUser(String email, DmpEntity dmp, DmpUserRole role) throws JAXBException, InvalidApplicationException { String token = this.createActionConfirmation(email, dmp, role); NotifyIntegrationEvent event = new NotifyIntegrationEvent(); event.setUserId(this.userScope.getUserIdSafe()); List contactPairs = new ArrayList<>(); contactPairs.add(new ContactPair(ContactInfoType.Email, email)); NotificationContactData contactData = new NotificationContactData(contactPairs, null, null); event.setContactHint(jsonHandlingService.toJsonSafe(contactData)); event.setContactTypeHint(NotificationContactType.EMAIL); event.setNotificationType(notificationProperties.getDmpInvitationExternalUserType()); NotificationFieldData data = new NotificationFieldData(); List fieldInfoList = new ArrayList<>(); fieldInfoList.add(new FieldInfo("{recipient}", DataType.String, email)); fieldInfoList.add(new FieldInfo("{confirmationToken}", DataType.String, token)); fieldInfoList.add(new FieldInfo("{dmpname}", DataType.String, dmp.getLabel())); fieldInfoList.add(new FieldInfo("{dmprole}", DataType.String, role.toString())); data.setFields(fieldInfoList); event.setData(jsonHandlingService.toJsonSafe(data)); eventHandler.handle(event); } private String createActionConfirmation(String email, DmpEntity dmp, DmpUserRole role) throws JAXBException, InvalidApplicationException { ActionConfirmationPersist persist = new ActionConfirmationPersist(); persist.setType(ActionConfirmationType.DmpInvitation); persist.setStatus(ActionConfirmationStatus.Requested); persist.setToken(UUID.randomUUID().toString()); persist.setDmpInvitation(new DmpInvitationPersist(email, dmp.getId(), role)); persist.setExpiresAt(Instant.now().plusSeconds(this.notificationProperties.getEmailExpirationTimeSeconds())); validatorFactory.validator(ActionConfirmationPersist.ActionConfirmationPersistValidator.class).validateForce(persist); this.actionConfirmationService.persist(persist, null); return persist.getToken(); } public void dmpInvitationAccept(String token) throws InvalidApplicationException, IOException { ActionConfirmationEntity action = this.queryFactory.query(ActionConfirmationQuery.class).tokens(token).types(ActionConfirmationType.DmpInvitation).isActive(IsActive.Active).first(); if (action == null){ throw new MyApplicationException("Token does not exist!"); } if (action.getStatus().equals(ActionConfirmationStatus.Accepted)){ throw new MyApplicationException("Invitation is already confirmed!"); } if (action.getExpiresAt().compareTo(Instant.now()) < 0){ throw new MyApplicationException("Token has expired!"); } DmpInvitationEntity dmpInvitation = this.xmlHandlingService.fromXmlSafe(DmpInvitationEntity.class, action.getData()); DmpUserEntity data = new DmpUserEntity(); data.setId(UUID.randomUUID()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); data.setUpdatedAt(Instant.now()); data.setRole(dmpInvitation.getRole()); data.setUserId(this.userScope.getUserIdSafe()); data.setDmpId(dmpInvitation.getDmpId()); this.entityManager.persist(data); action.setStatus(ActionConfirmationStatus.Accepted); this.entityManager.merge(action); } }