argos/backend/core/src/main/java/org/opencdmp/service/filetransformer/FileTransformerServiceImpl....

271 lines
19 KiB
Java

package org.opencdmp.service.filetransformer;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.Permission;
import org.opencdmp.commonmodels.models.FileEnvelopeModel;
import org.opencdmp.commonmodels.models.description.DescriptionModel;
import org.opencdmp.commonmodels.models.dmp.DmpModel;
import org.opencdmp.commons.JsonHandlingService;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.StorageType;
import org.opencdmp.commons.enums.TenantConfigurationType;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.filetransformer.FileTransformerSourceEntity;
import org.opencdmp.commons.types.tenantconfiguration.FileTransformerTenantConfigurationEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.TenantConfigurationEntity;
import org.opencdmp.event.TenantConfigurationTouchedEvent;
import org.opencdmp.filetransformerbase.interfaces.FileTransformerConfiguration;
import org.opencdmp.model.Description;
import org.opencdmp.model.Dmp;
import org.opencdmp.model.builder.commonmodels.description.DescriptionCommonModelBuilder;
import org.opencdmp.model.builder.commonmodels.dmp.DmpCommonModelBuilder;
import org.opencdmp.model.file.RepositoryFileFormat;
import org.opencdmp.model.tenantconfiguration.TenantConfiguration;
import org.opencdmp.query.DescriptionQuery;
import org.opencdmp.query.DmpQuery;
import org.opencdmp.query.TenantConfigurationQuery;
import org.opencdmp.service.encryption.EncryptionService;
import org.opencdmp.service.storage.StorageFileService;
import org.opencdmp.service.tenant.TenantProperties;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.commons.web.oidc.filter.webflux.TokenExchangeCacheService;
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.QueryFactory;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.fieldset.BaseFieldSet;
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.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@Service
public class FileTransformerServiceImpl implements FileTransformerService {
private static final Logger logger = LoggerFactory.getLogger(FileTransformerServiceImpl.class);
private final FileTransformerProperties fileTransformerProperties;
private final Map<String, FileTransformerRepository> clients;
private final TokenExchangeCacheService tokenExchangeCacheService;
private final FileTransformerConfigurationCacheService fileTransformerConfigurationCacheService;
private final AuthorizationService authorizationService;
private final QueryFactory queryFactory;
private final BuilderFactory builderFactory;
private final StorageFileService storageFileService;
private final MessageSource messageSource;
private final ConventionService conventionService;
private final TenantScope tenantScope;
private final EncryptionService encryptionService;
private final TenantProperties tenantProperties;
private final JsonHandlingService jsonHandlingService;
private final FileTransformerSourcesCacheService fileTransformerSourcesCacheService;
@Autowired
public FileTransformerServiceImpl(FileTransformerProperties fileTransformerProperties, TokenExchangeCacheService tokenExchangeCacheService, FileTransformerConfigurationCacheService fileTransformerConfigurationCacheService, AuthorizationService authorizationService,
QueryFactory queryFactory, BuilderFactory builderFactory, StorageFileService storageFileService, MessageSource messageSource, ConventionService conventionService, TenantScope tenantScope, EncryptionService encryptionService, TenantProperties tenantProperties, JsonHandlingService jsonHandlingService, FileTransformerSourcesCacheService fileTransformerSourcesCacheService) {
this.fileTransformerProperties = fileTransformerProperties;
this.tokenExchangeCacheService = tokenExchangeCacheService;
this.fileTransformerConfigurationCacheService = fileTransformerConfigurationCacheService;
this.authorizationService = authorizationService;
this.queryFactory = queryFactory;
this.builderFactory = builderFactory;
this.storageFileService = storageFileService;
this.messageSource = messageSource;
this.conventionService = conventionService;
this.tenantScope = tenantScope;
this.encryptionService = encryptionService;
this.tenantProperties = tenantProperties;
this.jsonHandlingService = jsonHandlingService;
this.fileTransformerSourcesCacheService = fileTransformerSourcesCacheService;
this.clients = new HashMap<>();
}
private FileTransformerRepository getRepository(String repoId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String repositoryIdByTenant = this.getRepositoryIdByTenant(repoId);
if (this.clients.containsKey(repositoryIdByTenant)) return this.clients.get(repositoryIdByTenant);
//GK: It's register time
FileTransformerSourceEntity source = this.getFileTransformerSources().stream().filter(fileTransformerSourceEntity -> fileTransformerSourceEntity.getTransformerId().equals(repoId)).findFirst().orElse(null);
if (source != null) {
TokenExchangeModel tokenExchangeModel = new TokenExchangeModel("file_transformer:" + repositoryIdByTenant, source.getIssuerUrl(), source.getClientId(), source.getClientSecret(), source.getScope());
TokenExchangeFilterFunction tokenExchangeFilterFunction = new TokenExchangeFilterFunction(this.tokenExchangeCacheService, tokenExchangeModel);
FileTransformerRepository repository = new FileTransformerRepository(WebClient.builder().baseUrl(source.getUrl() + "/api/file-transformer").filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(tokenExchangeFilterFunction);
exchangeFilterFunctions.add(logRequest());
}).build());
this.clients.put(repositoryIdByTenant, repository);
return repository;
}
return null;
}
private List<FileTransformerSourceEntity> getFileTransformerSources() throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
FileTransformerSourcesCacheService.FileTransformerSourceCacheValue cacheValue = this.fileTransformerSourcesCacheService.lookup(this.fileTransformerSourcesCacheService.buildKey(tenantCode));
if (cacheValue == null) {
List<FileTransformerSourceEntity> fileTransformerSourceEntities = new ArrayList<>(this.fileTransformerProperties.getSources());
if (this.tenantScope.isSet() && this.tenantScope.isMultitenant()) {
TenantConfigurationQuery tenantConfigurationQuery = this.queryFactory.query(TenantConfigurationQuery.class).isActive(IsActive.Active).types(TenantConfigurationType.FileTransformerPlugins);
if (this.tenantScope.isDefaultTenant()) tenantConfigurationQuery.tenantIsSet(false);
else tenantConfigurationQuery.tenantIsSet(true).tenantIds(this.tenantScope.getTenant());
TenantConfigurationEntity tenantConfiguration = tenantConfigurationQuery.firstAs(new BaseFieldSet().ensure(TenantConfiguration._fileTransformerPlugins));
if (tenantConfiguration != null && !this.conventionService.isNullOrEmpty(tenantConfiguration.getValue())) {
FileTransformerTenantConfigurationEntity fileTransformerTenantConfigurationEntity = this.jsonHandlingService.fromJsonSafe(FileTransformerTenantConfigurationEntity.class, tenantConfiguration.getValue());
if (fileTransformerTenantConfigurationEntity != null) {
if (fileTransformerTenantConfigurationEntity.getDisableSystemSources()) fileTransformerSourceEntities = new ArrayList<>();
fileTransformerSourceEntities.addAll(this.buildFileTransformerSourceItems(fileTransformerTenantConfigurationEntity.getSources()));
}
}
}
cacheValue = new FileTransformerSourcesCacheService.FileTransformerSourceCacheValue(tenantCode, fileTransformerSourceEntities);
this.fileTransformerSourcesCacheService.put(cacheValue);
}
return cacheValue.getSources();
}
@EventListener
public void handleTenantConfigurationTouchedEvent(TenantConfigurationTouchedEvent event) {
if (!event.getType().equals(TenantConfigurationType.FileTransformerPlugins)) return;
FileTransformerSourcesCacheService.FileTransformerSourceCacheValue fileTransformerSourceCacheValue = this.fileTransformerSourcesCacheService.lookup(this.fileTransformerSourcesCacheService.buildKey(event.getTenantCode()));
if (fileTransformerSourceCacheValue != null && fileTransformerSourceCacheValue.getSources() != null){
for (FileTransformerSourceEntity source : fileTransformerSourceCacheValue.getSources()){
String repositoryIdByTenant = source.getTransformerId() + "_" + event.getTenantCode();
this.clients.remove(repositoryIdByTenant);
this.fileTransformerConfigurationCacheService.evict(this.fileTransformerConfigurationCacheService.buildKey(source.getTransformerId(), event.getTenantCode()));
}
}
this.fileTransformerSourcesCacheService.evict(this.fileTransformerSourcesCacheService.buildKey(event.getTenantCode()));
}
private List<FileTransformerSourceEntity> buildFileTransformerSourceItems(List<FileTransformerSourceEntity> sources) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
List<FileTransformerSourceEntity> items = new ArrayList<>();
if (this.conventionService.isListNullOrEmpty(sources)) return items;
for (FileTransformerSourceEntity source : sources){
FileTransformerSourceEntity item = new FileTransformerSourceEntity();
item.setTransformerId(source.getTransformerId());
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(), this.tenantProperties.getConfigEncryptionAesKey(), this.tenantProperties.getConfigEncryptionAesIv()));
item.setScope(source.getScope());
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<RepositoryFileFormat> getAvailableExportFileFormats() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
List<RepositoryFileFormat> formats = new ArrayList<>();
List<FileTransformerConfiguration> configurations = this.getAvailableConfigurations();
for (FileTransformerConfiguration configuration : configurations){
if (configuration != null && configuration.getExportVariants() != null) formats.addAll(configuration.getExportVariants().stream().map(x-> new RepositoryFileFormat(configuration.getFileTransformerId(), x, configuration.getExportEntityTypes())).toList());
}
return formats;
}
private List<FileTransformerConfiguration> getAvailableConfigurations() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
List<FileTransformerConfiguration> configurations = new ArrayList<>();
for (FileTransformerSourceEntity transformerSource : this.getFileTransformerSources()) {
String tenantCode = this.tenantScope.isSet() && this.tenantScope.isMultitenant() ? this.tenantScope.getTenantCode() : "";
FileTransformerConfigurationCacheService.FileTransformerConfigurationCacheValue cacheValue = this.fileTransformerConfigurationCacheService.lookup(this.fileTransformerConfigurationCacheService.buildKey(transformerSource.getTransformerId(), tenantCode));
if (cacheValue == null){
try {
FileTransformerRepository fileTransformerRepository = this.getRepository(transformerSource.getTransformerId());
if (fileTransformerRepository == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{transformerSource.getTransformerId(), FileTransformerRepository.class.getSimpleName()}, LocaleContextHolder.getLocale()));
FileTransformerConfiguration configuration = fileTransformerRepository.getConfiguration();
cacheValue = new FileTransformerConfigurationCacheService.FileTransformerConfigurationCacheValue(transformerSource.getTransformerId(), tenantCode, configuration);
this.fileTransformerConfigurationCacheService.put(cacheValue);
}catch (Exception e){
logger.error(e.getMessage(), e);
}
}
if (cacheValue != null) {
configurations.add(cacheValue.getConfiguration());
}
}
return configurations;
}
@Override
public org.opencdmp.model.file.FileEnvelope exportDmp(UUID dmpId, String repositoryId, String format) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorize(Permission.EditDmp);
//GK: First get the right client
FileTransformerRepository repository = this.getRepository(repositoryId);
if (repository == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{format, FileTransformerRepository.class.getSimpleName()}, LocaleContextHolder.getLocale()));
//GK: Second get the Target Data Management Plan
DmpQuery query = this.queryFactory.query(DmpQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).ids(dmpId);
DmpModel dmpFileTransformerModel = this.builderFactory.builder(DmpCommonModelBuilder.class).useSharedStorage(repository.getConfiguration().isUseSharedStorage()).setRepositoryId(repository.getConfiguration().getFileTransformerId()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(query.first());
if (dmpFileTransformerModel == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{dmpId, Dmp.class.getSimpleName()}, LocaleContextHolder.getLocale()));
FileEnvelopeModel fileEnvelope = repository.exportDmp(dmpFileTransformerModel, format);
org.opencdmp.model.file.FileEnvelope result = new org.opencdmp.model.file.FileEnvelope();
byte[] data = repository.getConfiguration().isUseSharedStorage() ? this.storageFileService.readByFileRefAsBytesSafe(fileEnvelope.getFileRef(), StorageType.Transformer) : fileEnvelope.getFile();
result.setFile(data);
result.setFilename(fileEnvelope.getFilename());
return result;
}
@Override
public org.opencdmp.model.file.FileEnvelope exportDescription(UUID descriptionId, String repositoryId, String format) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, InvalidApplicationException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
this.authorizationService.authorize(Permission.EditDmp);
//GK: First get the right client
FileTransformerRepository repository = this.getRepository(repositoryId);
if (repository == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{format, FileTransformerRepository.class.getSimpleName()}, LocaleContextHolder.getLocale()));
//GK: Second get the Target Data Management Plan
DescriptionQuery query = this.queryFactory.query(DescriptionQuery.class).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).ids(descriptionId);
DescriptionModel descriptionFileTransformerModel = this.builderFactory.builder(DescriptionCommonModelBuilder.class).setRepositoryId(repository.getConfiguration().getFileTransformerId()).useSharedStorage(repository.getConfiguration().isUseSharedStorage()).authorize(AuthorizationFlags.OwnerOrDmpAssociatedOrPermission).build(query.first());
if (descriptionFileTransformerModel == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{descriptionId, Description.class.getSimpleName()}, LocaleContextHolder.getLocale()));
FileEnvelopeModel fileEnvelope = repository.exportDescription(descriptionFileTransformerModel, format);
org.opencdmp.model.file.FileEnvelope result = new org.opencdmp.model.file.FileEnvelope();
byte[] data = repository.getConfiguration().isUseSharedStorage() ? this.storageFileService.readByFileRefAsBytesSafe(fileEnvelope.getFileRef(), StorageType.Transformer) : fileEnvelope.getFile(); //TODO: shared storage should be per repository
result.setFile(data);
result.setFilename(fileEnvelope.getFilename());
return result;
}
// This method returns filter function which will log request data
private static ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
}