argos/dmp-backend/core/src/main/java/eu/eudat/service/deposit/DepositServiceImpl.java

378 lines
24 KiB
Java

package eu.eudat.service.deposit;
import eu.eudat.authorization.AuthorizationFlags;
import eu.eudat.authorization.Permission;
import eu.eudat.authorization.authorizationcontentresolver.AuthorizationContentResolver;
import eu.eudat.commonmodels.models.FileEnvelopeModel;
import eu.eudat.commonmodels.models.dmp.DmpModel;
import eu.eudat.commons.JsonHandlingService;
import eu.eudat.commons.enums.ContactInfoType;
import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.enums.StorageType;
import eu.eudat.commons.enums.TenantConfigurationType;
import eu.eudat.commons.notification.NotificationProperties;
import eu.eudat.commons.scope.tenant.TenantScope;
import eu.eudat.commons.scope.user.UserScope;
import eu.eudat.commons.types.deposit.DepositSourceEntity;
import eu.eudat.commons.types.notification.*;
import eu.eudat.commons.types.tenantconfiguration.DepositTenantConfigurationEntity;
import eu.eudat.convention.ConventionService;
import eu.eudat.data.DmpEntity;
import eu.eudat.data.DmpUserEntity;
import eu.eudat.data.TenantConfigurationEntity;
import eu.eudat.data.UserEntity;
import eu.eudat.depositinterface.repository.DepositClient;
import eu.eudat.depositinterface.repository.DepositConfiguration;
import eu.eudat.event.TenantConfigurationTouchedEvent;
import eu.eudat.event.UserAddedToTenantEvent;
import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEvent;
import eu.eudat.integrationevent.outbox.notification.NotifyIntegrationEventHandler;
import eu.eudat.model.EntityDoi;
import eu.eudat.model.StorageFile;
import eu.eudat.model.UserContactInfo;
import eu.eudat.model.builder.commonmodels.DepositConfigurationBuilder;
import eu.eudat.model.builder.commonmodels.dmp.DmpCommonModelBuilder;
import eu.eudat.model.persist.StorageFilePersist;
import eu.eudat.model.persist.deposit.DepositAuthenticateRequest;
import eu.eudat.model.persist.deposit.DepositRequest;
import eu.eudat.model.persist.EntityDoiPersist;
import eu.eudat.model.tenantconfiguration.TenantConfiguration;
import eu.eudat.query.*;
import eu.eudat.service.encryption.EncryptionService;
import eu.eudat.service.entitydoi.EntityDoiService;
import eu.eudat.service.storage.StorageFileProperties;
import eu.eudat.service.storage.StorageFileService;
import eu.eudat.service.filetransformer.FileTransformerService;
import eu.eudat.service.tenant.TenantProperties;
import gr.cite.commons.web.oidc.filter.webflux.TokenExchangeCacheService;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.commons.web.oidc.filter.webflux.TokenExchangeFilterFunction;
import gr.cite.commons.web.oidc.filter.webflux.TokenExchangeModel;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.query.Ordering;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.validation.ValidatorFactory;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.event.EventListener;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException;
import java.io.IOException;
import java.net.URLConnection;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class DepositServiceImpl implements DepositService {
private static final Logger logger = LoggerFactory.getLogger(DepositServiceImpl.class);
private final DepositProperties depositProperties;
private final Map<String, DepositClient> clients;
private final TokenExchangeCacheService tokenExchangeCacheService;
private final AuthorizationService authorizationService;
private final EntityDoiService doiService;
private final QueryFactory queryFactory;
private final MessageSource messageSource;
private final BuilderFactory builderFactory;
private final DepositConfigurationCacheService depositConfigurationCacheService;
private final FileTransformerService fileTransformerService;
private final StorageFileService storageFileService;
private final UserScope userScope;
private final ValidatorFactory validatorFactory;
private final StorageFileProperties storageFileProperties;
private final AuthorizationContentResolver authorizationContentResolver;
private final ConventionService conventionService;
private final JsonHandlingService jsonHandlingService;
private final NotificationProperties notificationProperties;
private final NotifyIntegrationEventHandler eventHandler;
private final TenantScope tenantScope;
private final EncryptionService encryptionService;
private final TenantProperties tenantProperties;
private final DepositSourcesCacheService depositSourcesCacheService;
@Autowired
public DepositServiceImpl(DepositProperties depositProperties,
TokenExchangeCacheService tokenExchangeCacheService,
AuthorizationService authorizationService,
EntityDoiService doiService,
QueryFactory queryFactory,
MessageSource messageSource,
BuilderFactory builderFactory, DepositConfigurationCacheService depositConfigurationCacheService, FileTransformerService fileTransformerService, StorageFileService storageFileService, UserScope userScope, ValidatorFactory validatorFactory, StorageFileProperties storageFileProperties, AuthorizationContentResolver authorizationContentResolver, ConventionService conventionService, JsonHandlingService jsonHandlingService, NotificationProperties notificationProperties, NotifyIntegrationEventHandler eventHandler, TenantScope tenantScope, EncryptionService encryptionService, TenantProperties tenantProperties, DepositSourcesCacheService depositSourcesCacheService) {
this.depositProperties = depositProperties;
this.tokenExchangeCacheService = tokenExchangeCacheService;
this.authorizationService = authorizationService;
this.doiService = doiService;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
this.builderFactory = builderFactory;
this.depositConfigurationCacheService = depositConfigurationCacheService;
this.fileTransformerService = fileTransformerService;
this.storageFileService = storageFileService;
this.userScope = userScope;
this.validatorFactory = validatorFactory;
this.storageFileProperties = storageFileProperties;
this.authorizationContentResolver = authorizationContentResolver;
this.conventionService = conventionService;
this.jsonHandlingService = jsonHandlingService;
this.notificationProperties = notificationProperties;
this.eventHandler = eventHandler;
this.tenantScope = tenantScope;
this.encryptionService = encryptionService;
this.tenantProperties = tenantProperties;
this.depositSourcesCacheService = depositSourcesCacheService;
this.clients = new HashMap<>();
}
private DepositClient getDepositClient(String repositoryId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String repositoryIdByTenant = this.getRepositoryIdByTenant(repositoryId);
if (this.clients.containsKey(repositoryIdByTenant)) return this.clients.get(repositoryIdByTenant);
//GK: It's register time
DepositSourceEntity source = this.getDepositSources().stream().filter(depositSource -> depositSource.getRepositoryId().equals(repositoryId)).findFirst().orElse(null);
if (source != null) {
TokenExchangeModel tokenExchangeModel = new TokenExchangeModel("deposit:" + repositoryIdByTenant, source.getIssuerUrl(), source.getClientId(), source.getClientSecret(), source.getScope());
TokenExchangeFilterFunction apiKeyExchangeFilterFunction = new TokenExchangeFilterFunction(this.tokenExchangeCacheService, tokenExchangeModel);
WebClient webClient = WebClient.builder().baseUrl(source.getUrl() + "/api/deposit").filters(exchangeFilterFunctions -> exchangeFilterFunctions.add(apiKeyExchangeFilterFunction)).build();
DepositClientImpl repository = new DepositClientImpl(webClient);
this.clients.put(repositoryIdByTenant, repository);
return repository;
}
return null;
}
private List<DepositSourceEntity> getDepositSources() throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
DepositSourcesCacheService.DepositSourceCacheValue cacheValue = this.depositSourcesCacheService.lookup(this.depositSourcesCacheService.buildKey(tenantCode));
if (cacheValue == null) {
List<DepositSourceEntity> depositSourceEntities = new ArrayList<>(depositProperties.getSources());
if (this.tenantScope.isSet() && this.tenantScope.isMultitenant()) {
TenantConfigurationQuery tenantConfigurationQuery = this.queryFactory.query(TenantConfigurationQuery.class).isActive(IsActive.Active).types(TenantConfigurationType.DepositPlugins);
if (this.tenantScope.isDefaultTenant()) tenantConfigurationQuery.tenantIsSet(false);
else tenantConfigurationQuery.tenantIsSet(true).tenantIds(this.tenantScope.getTenant());
TenantConfigurationEntity tenantConfiguration = tenantConfigurationQuery.firstAs(new BaseFieldSet().ensure(TenantConfiguration._depositPlugins));
if (tenantConfiguration != null && !this.conventionService.isNullOrEmpty(tenantConfiguration.getValue())) {
DepositTenantConfigurationEntity depositTenantConfiguration = this.jsonHandlingService.fromJsonSafe(DepositTenantConfigurationEntity.class, tenantConfiguration.getValue());
if (depositTenantConfiguration != null) {
if (depositTenantConfiguration.getDisableSystemSources()) depositSourceEntities = new ArrayList<>();
depositSourceEntities.addAll(this.buildDepositSourceItems(depositTenantConfiguration.getSources()));
}
}
}
cacheValue = new DepositSourcesCacheService.DepositSourceCacheValue(tenantCode, depositSourceEntities);
this.depositSourcesCacheService.put(cacheValue);
}
return cacheValue.getSources();
}
@EventListener
public void handleTenantConfigurationTouchedEvent(TenantConfigurationTouchedEvent event) {
if (!event.getType().equals(TenantConfigurationType.DepositPlugins)) return;
DepositSourcesCacheService.DepositSourceCacheValue depositSourceCacheValue = this.depositSourcesCacheService.lookup(this.depositSourcesCacheService.buildKey(event.getTenantCode()));
if (depositSourceCacheValue != null && depositSourceCacheValue.getSources() != null){
for (DepositSourceEntity source : depositSourceCacheValue.getSources()){
String repositoryIdByTenant = source.getRepositoryId() + "_" + event.getTenantCode();
this.clients.remove(repositoryIdByTenant);
this.depositConfigurationCacheService.evict(this.depositConfigurationCacheService.buildKey(source.getRepositoryId(), event.getTenantCode()));
}
}
this.depositSourcesCacheService.evict(this.depositSourcesCacheService.buildKey(event.getTenantCode()));
}
private List<DepositSourceEntity> buildDepositSourceItems(List<DepositSourceEntity> sources) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
List<DepositSourceEntity> items = new ArrayList<>();
if (this.conventionService.isListNullOrEmpty(sources)) return items;
for (DepositSourceEntity source : sources){
DepositSourceEntity item = new DepositSourceEntity();
item.setRepositoryId(source.getRepositoryId());
item.setUrl(source.getUrl());
item.setIssuerUrl(source.getIssuerUrl());
item.setClientId(source.getClientId());
if (!this.conventionService.isNullOrEmpty(source.getClientSecret())) item.setClientSecret(this.encryptionService.decryptAES(source.getClientSecret(), tenantProperties.getConfigEncryptionAesKey(), tenantProperties.getConfigEncryptionAesIv()));
item.setScope(source.getScope());
item.setRdaTransformerId(source.getRdaTransformerId());
item.setPdfTransformerId(source.getPdfTransformerId());
items.add(item);
}
return items;
}
private String getRepositoryIdByTenant(String repositoryId) throws InvalidApplicationException {
if (this.tenantScope.isSet() && this.tenantScope.isMultitenant()) {
return repositoryId + "_" + this.tenantScope.getTenantCode();
} else {
return repositoryId;
}
}
@Override
public List<eu.eudat.model.deposit.DepositConfiguration> getAvailableConfigurations(FieldSet fieldSet) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorizeForce(Permission.BrowseDeposit, Permission.DeferredAffiliation);
List<eu.eudat.model.deposit.DepositConfiguration> configurations = new ArrayList<>();
for (DepositSourceEntity depositSource : this.getDepositSources()) {
String tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
DepositConfigurationCacheService.DepositConfigurationCacheValue cacheValue = this.depositConfigurationCacheService.lookup(this.depositConfigurationCacheService.buildKey(depositSource.getRepositoryId(), tenantCode));
if (cacheValue == null){
try {
DepositClient depositClient = getDepositClient(depositSource.getRepositoryId());
if (depositClient == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{depositSource.getRepositoryId(), DepositClient.class.getSimpleName()}, LocaleContextHolder.getLocale()));
DepositConfiguration configuration = depositClient.getConfiguration();
cacheValue = new DepositConfigurationCacheService.DepositConfigurationCacheValue(depositSource.getRepositoryId(), tenantCode, configuration);
this.depositConfigurationCacheService.put(cacheValue);
}catch (Exception e){
logger.error(e.getMessage(), e);
}
}
if (cacheValue != null) {
eu.eudat.model.deposit.DepositConfiguration depositConfiguration = this.builderFactory.builder(DepositConfigurationBuilder.class).build(fieldSet, cacheValue.getConfiguration());
configurations.add(depositConfiguration);
}
}
return configurations;
}
@Override
public EntityDoi deposit(DepositRequest dmpDepositModel) throws Exception {
this.authorizationService.authorizeAtLeastOneForce(List.of(this.authorizationContentResolver.dmpAffiliation(dmpDepositModel.getDmpId())), Permission.DepositDmp);
//GK: First get the right client
DepositClient depositClient = getDepositClient(dmpDepositModel.getRepositoryId());
if (depositClient == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{dmpDepositModel.getRepositoryId(), DepositClient.class.getSimpleName()}, LocaleContextHolder.getLocale()));
//GK: Second get the Target Data Management Plan
DmpEntity dmpEntity = this.queryFactory.query(DmpQuery.class).ids(dmpDepositModel.getDmpId()).first();
if (dmpEntity == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{dmpDepositModel.getDmpId(), DmpEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
//GK: Forth make the required files to be uploaded with the deposit
//TODO: Properly create required files
DepositSourceEntity source = this.getDepositSources().stream().filter(depositSource -> depositSource.getRepositoryId().equals(dmpDepositModel.getRepositoryId())).findFirst().orElse(null);
if (source == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{dmpDepositModel.getRepositoryId(), DepositSourceEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
eu.eudat.model.file.FileEnvelope pdfFile = this.fileTransformerService.exportDmp(dmpEntity.getId(), source.getPdfTransformerId(),"pdf");
eu.eudat.model.file.FileEnvelope rda = this.fileTransformerService.exportDmp(dmpEntity.getId(), source.getRdaTransformerId(),"json");
FileEnvelopeModel pdfEnvelope = new FileEnvelopeModel();
FileEnvelopeModel jsonEnvelope = new FileEnvelopeModel();
pdfEnvelope.setFilename(pdfFile.getFilename());
jsonEnvelope.setMimeType("application/pdf");
jsonEnvelope.setFilename(rda.getFilename());
jsonEnvelope.setMimeType("application/json");
if (!depositClient.getConfiguration().isUseSharedStorage()){
pdfEnvelope.setFile(pdfFile.getFile());
jsonEnvelope.setFile(rda.getFile());
} else {
pdfEnvelope.setFileRef(this.addFileToSharedStorage(pdfFile));
jsonEnvelope.setFileRef(this.addFileToSharedStorage(rda));
}
//GK: Fifth Transform them to the DepositModel
DmpModel depositModel = this.builderFactory.builder(DmpCommonModelBuilder.class).useSharedStorage(depositClient.getConfiguration().isUseSharedStorage()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission)
.setRepositoryId(dmpDepositModel.getRepositoryId()).setPdfFile(pdfEnvelope).setRdaJsonFile(jsonEnvelope).build(dmpEntity);
//GK: Sixth Perform the deposit
String doi = depositClient.deposit(depositModel, dmpDepositModel.getAccessToken());
//GK: Something has gone wrong return null
if (doi.isEmpty()) return null;
//GK: doi is fine store it in database
EntityDoiPersist doiPersist = new EntityDoiPersist();
doiPersist.setRepositoryId(dmpDepositModel.getRepositoryId());
doiPersist.setDoi(doi);
doiPersist.setEntityId(dmpEntity.getId());
this.sendNotification(dmpEntity);
return doiService.persist(doiPersist, dmpDepositModel.getProject());
}
private void sendNotification(DmpEntity dmpEntity) throws InvalidApplicationException {
List<DmpUserEntity> dmpUsers = this.queryFactory.query(DmpUserQuery.class).dmpIds(dmpEntity.getId()).isActives(IsActive.Active).collect();
if (this.conventionService.isListNullOrEmpty(dmpUsers)){
throw new MyNotFoundException("Dmp does not have Users");
}
List<UserEntity> users = this.queryFactory.query(UserQuery.class).ids(dmpUsers.stream().map(DmpUserEntity::getUserId).collect(Collectors.toList())).isActive(IsActive.Active).collect();
for (UserEntity user: users) {
if (!user.getId().equals(this.userScope.getUserIdSafe()) && !this.conventionService.isListNullOrEmpty(dmpUsers.stream().filter(x -> x.getUserId().equals(user.getId())).collect(Collectors.toList()))){
this.createDmpDepositNotificationEvent(dmpEntity, user);
}
}
}
private void createDmpDepositNotificationEvent(DmpEntity dmp, UserEntity user) throws InvalidApplicationException {
NotifyIntegrationEvent event = new NotifyIntegrationEvent();
event.setUserId(user.getId());
UserContactInfoQuery query = this.queryFactory.query(UserContactInfoQuery.class).userIds(user.getId());
query.setOrder(new Ordering().addAscending(UserContactInfo._ordinal));
List<ContactPair> 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.setNotificationType(notificationProperties.getDmpDepositType());
NotificationFieldData data = new NotificationFieldData();
List<FieldInfo> 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 String addFileToSharedStorage(eu.eudat.model.file.FileEnvelope file) throws IOException {
StorageFilePersist storageFilePersist = new StorageFilePersist();
storageFilePersist.setName(FilenameUtils.removeExtension(file.getFilename()));
storageFilePersist.setExtension(FilenameUtils.getExtension(file.getFilename()));
storageFilePersist.setMimeType(URLConnection.guessContentTypeFromName(file.getFilename()));
storageFilePersist.setOwnerId(this.userScope.getUserIdSafe());
storageFilePersist.setStorageType(StorageType.Temp);
storageFilePersist.setLifetime(Duration.ofSeconds(this.storageFileProperties.getTempStoreLifetimeSeconds())); //TODO
this.validatorFactory.validator(StorageFilePersist.StorageFilePersistValidator.class).validateForce(storageFilePersist);
StorageFile persisted = this.storageFileService.persistBytes(storageFilePersist, file.getFile(), new BaseFieldSet(StorageFile._id, StorageFile._fileRef));
return persisted.getFileRef();
}
@Override
public String getLogo(String repositoryId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorizeForce(Permission.BrowseDeposit, Permission.DeferredAffiliation);
DepositClient depositClient = getDepositClient(repositoryId);
if (depositClient == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{repositoryId, DepositClient.class.getSimpleName()}, LocaleContextHolder.getLocale()));
return depositClient.getLogo();
}
@Override
public String authenticate(DepositAuthenticateRequest model) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorizeForce(Permission.BrowseDeposit, Permission.DeferredAffiliation);
DepositClient depositClient = getDepositClient(model.getRepositoryId());
if (depositClient == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{model.getRepositoryId(), DepositClient.class.getSimpleName()}, LocaleContextHolder.getLocale()));
return depositClient.authenticate(model.getCode());
}
}