package eu.eudat.service.dmp; import com.fasterxml.jackson.core.JsonProcessingException; import eu.eudat.authorization.AuthorizationFlags; import eu.eudat.authorization.Permission; import eu.eudat.commons.JsonHandlingService; import eu.eudat.commons.XmlHandlingService; import eu.eudat.commons.enums.DmpStatus; import eu.eudat.commons.enums.DmpUserRole; import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.scope.user.UserScope; import eu.eudat.commons.types.reference.DefinitionEntity; import eu.eudat.commons.types.reference.FieldEntity; 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.model.Dmp; import eu.eudat.model.DmpReference; import eu.eudat.model.DmpUser; import eu.eudat.model.Reference; import eu.eudat.model.builder.DmpBuilder; import eu.eudat.model.deleter.DmpDeleter; import eu.eudat.model.deleter.DmpDescriptionTemplateDeleter; import eu.eudat.model.deleter.DmpReferenceDeleter; import eu.eudat.model.persist.*; import eu.eudat.model.persist.referencedefinition.DefinitionPersist; import eu.eudat.model.persist.referencedefinition.FieldPersist; import eu.eudat.query.*; import eu.eudat.service.description.DescriptionService; 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.logging.MapLogEntry; 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.stereotype.Service; import javax.management.InvalidApplicationException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; 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; @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) { 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; } public Dmp persist(DmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JsonProcessingException { logger.debug(new MapLogEntry("persisting data dmp").And("model", model).And("fields", fields)); this.authorizationService.authorizeForce(Permission.EditDmp); DmpEntity data = this.patchAndSave(model); this.patchAndSaveReferences(model.getReferences(), data.getId()); this.patchAndSaveTemplates(model.getDescriptionTemplates(), data.getId()); this.eventBroker.emit(new DmpTouchedEvent(data.getId())); return this.builderFactory.builder(DmpBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionOrMemberOrPublic).build(BaseFieldSet.build(fields, Dmp._id, Dmp._hash), data); } public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException { logger.debug("deleting dmp: {}", id); this.authorizationService.authorizeForce(Permission.DeleteDmp); this.deleterFactory.deleter(DmpDeleter.class).deleteAndSaveByIds(List.of(id)); } @Override public Dmp createNewVersion(NewVersionDmpPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException { logger.debug(new MapLogEntry("persisting data dmp (new version)").And("model", model).And("fields", fields)); this.authorizationService.authorizeForce(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()); latestVersionDmpEntityQuery.setOrder(new Ordering().addDescending(Dmp._version)); DmpEntity latestVersionDmpEntity = latestVersionDmpEntityQuery.first(); if (!latestVersionDmpEntity.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.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.setBlueprint(model.getBlueprintId()); 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.setDmp(newDmp.getId()); newUser.setUser(dmpUser.getUser()); 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.setDescriptionTemplateId(dmpDescriptionTemplate.getDescriptionTemplateId()); 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); } this.entityManager.persist(newDmp); this.entityManager.flush(); 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 { logger.debug(new MapLogEntry("cloning dmp").And("model", model).And("fields", fields)); this.authorizationService.authorizeForce(Permission.CloneDmp); DmpEntity existingDmpEntity = this.queryFactory.query(DmpQuery.class).authorize(AuthorizationFlags.OwnerOrPermissionOrMemberOrPublic).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.setDescription(model.getDescription()); newDmp.setLabel(model.getLabel()); newDmp.setLanguage(existingDmpEntity.getLanguage()); newDmp.setStatus(DmpStatus.Draft); newDmp.setProperties(existingDmpEntity.getProperties()); newDmp.setBlueprint(existingDmpEntity.getBlueprint()); 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.setDmp(newDmp.getId()); newUser.setUser(dmpUser.getUser()); 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.setDescriptionTemplateId(dmpDescriptionTemplate.getDescriptionTemplateId()); newTemplate.setSectionId(dmpDescriptionTemplate.getSectionId()); newTemplate.setCreatedAt(Instant.now()); newTemplate.setUpdatedAt(Instant.now()); newTemplate.setIsActive(IsActive.Active); this.entityManager.persist(newTemplate); } this.entityManager.flush(); DmpEntity resultingDmpEntity = this.queryFactory.query(DmpQuery.class).ids(newDmp.getId()).firstAs(fields); return this.builderFactory.builder(DmpBuilder.class).build(fields, resultingDmpEntity); } private DmpEntity patchAndSave(DmpPersist model) throws JsonProcessingException, InvalidApplicationException { Boolean isUpdate = this.conventionService.isValidGuid(model.getId()); DmpEntity data; DmpUserEntity dmpUserEntity = new DmpUserEntity(); 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()); data.setStatus(model.getStatus()); } else { data = new DmpEntity(); data.setId(UUID.randomUUID()); data.setGroupId(UUID.randomUUID()); data.setVersion((short) 1); data.setStatus(DmpStatus.Draft); data.setCreator(userScope.getUserId()); data.setBlueprint(model.getBlueprint()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); dmpUserEntity.setId(UUID.randomUUID()); dmpUserEntity.setDmp(data.getId()); dmpUserEntity.setUser(userScope.getUserId()); dmpUserEntity.setRole(DmpUserRole.Owner); dmpUserEntity.setCreatedAt(Instant.now()); dmpUserEntity.setUpdatedAt(Instant.now()); dmpUserEntity.setIsActive(IsActive.Active); } data.setLabel(model.getLabel()); data.setProperties(this.jsonHandlingService.toJson(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.persist(dmpUserEntity); } this.entityManager.flush(); return data; } private void patchAndSaveReferences(List models, UUID dmpId) throws InvalidApplicationException { if (models == null || models.isEmpty()) return; List references = this.queryFactory.query(DmpReferenceQuery.class).dmpIds(dmpId).collect(); Map> referencesLookup = this.conventionService.toDictionaryOfList(references, DmpReferenceEntity::getDmpId); List existingReferences; if (referencesLookup.containsKey(dmpId)) existingReferences = this.queryFactory.query(ReferenceQuery.class).ids(referencesLookup.get(dmpId).stream().map(DmpReferenceEntity::getId).toList()).collect(); else existingReferences = new ArrayList<>(); List updatedReferencesIds = models.stream().map(x -> x.getReference().getId()).filter(this.conventionService::isValidGuid).distinct().toList(); List toDelete = existingReferences.stream().filter(x -> !updatedReferencesIds.contains(x.getId())).toList(); List dmpReferenceRecordsToDelete = this.queryFactory.query(DmpReferenceQuery.class).referenceIds(toDelete.stream().map(ReferenceEntity::getId).toList()).collect(); this.deleterFactory.deleter(DmpReferenceDeleter.class).delete(dmpReferenceRecordsToDelete); Map existingReferencesLookup = existingReferences.stream().collect(Collectors.toMap(ReferenceEntity::getId, x -> x)); for (DmpReferencePersist model : models) { ReferencePersist referenceModel = model.getReference(); Boolean isUpdate = this.conventionService.isValidGuid(referenceModel.getId()); ReferenceEntity data; if (isUpdate) { if (!existingReferencesLookup.containsKey(referenceModel.getId())) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{referenceModel.getId(), Reference.class.getSimpleName()}, LocaleContextHolder.getLocale())); data = existingReferencesLookup.get(referenceModel.getId()); if (data == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{referenceModel.getId(), Reference.class.getSimpleName()}, LocaleContextHolder.getLocale())); if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(referenceModel.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage()); } else { data = new ReferenceEntity(); data.setId(UUID.randomUUID()); data.setIsActive(IsActive.Active); data.setCreatedAt(Instant.now()); DmpReferenceEntity dmpReference = new DmpReferenceEntity(); dmpReference.setReferenceId(data.getId()); dmpReference.setDmpId(dmpId); dmpReference.setCreatedAt(Instant.now()); dmpReference.setUpdatedAt(Instant.now()); dmpReference.setIsActive(IsActive.Active); dmpReference.setData(model.getData()); this.entityManager.persist(dmpReference); } data.setDefinition(this.xmlHandlingService.toXmlSafe(this.buildDefinitionEntity(referenceModel.getDefinition()))); data.setUpdatedAt(Instant.now()); data.setReference(referenceModel.getReference()); data.setAbbreviation(referenceModel.getAbbreviation()); data.setSource(referenceModel.getSource()); data.setSourceType(referenceModel.getSourceType()); if (isUpdate) this.entityManager.merge(data); else this.entityManager.persist(data); } this.entityManager.flush(); } private void patchAndSaveTemplates(List models, UUID dmpId) throws InvalidApplicationException { if (models == null || models.isEmpty()) return; List templates = this.queryFactory.query(DmpDescriptionTemplateQuery.class).dmpIds(dmpId).collect(); Map> templatesLookup = this.conventionService.toDictionaryOfList(templates, DmpDescriptionTemplateEntity::getDmpId); List existingTemplates; if (templatesLookup.containsKey(dmpId)) existingTemplates = this.queryFactory.query(DescriptionTemplateQuery.class).ids(templatesLookup.get(dmpId).stream().map(DmpDescriptionTemplateEntity::getId).toList()).collect(); else existingTemplates = new ArrayList<>(); List updatedTemplatesIds = models.stream().map(DmpDescriptionTemplatePersist::getDescriptionTemplate).filter(this.conventionService::isValidGuid).distinct().toList(); List toDelete = existingTemplates.stream().filter(x -> !updatedTemplatesIds.contains(x.getId())).toList(); List dmpDescriptionTemplateRecordsToDelete = this.queryFactory.query(DmpDescriptionTemplateQuery.class).descriptionTemplateIds(toDelete.stream().map(DescriptionTemplateEntity::getId).toList()).collect(); this.deleterFactory.deleter(DmpDescriptionTemplateDeleter.class).delete(dmpDescriptionTemplateRecordsToDelete); for (DmpDescriptionTemplatePersist model : models) { boolean shouldAdd = existingTemplates.stream().noneMatch(x -> x.getId().equals(model.getDescriptionTemplate())); if (shouldAdd) { DmpDescriptionTemplateEntity dmpTemplate = new DmpDescriptionTemplateEntity(); dmpTemplate.setDescriptionTemplateId(model.getDescriptionTemplate()); dmpTemplate.setDmpId(dmpId); dmpTemplate.setSectionId(model.getSectionId()); dmpTemplate.setCreatedAt(Instant.now()); dmpTemplate.setUpdatedAt(Instant.now()); dmpTemplate.setIsActive(IsActive.Active); this.entityManager.persist(dmpTemplate); } } this.entityManager.flush(); } 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.buildFieldEntity(fieldPersist)); } } return data; } private @NotNull FieldEntity buildFieldEntity(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; } }