From 1ac8257fba0c812829f50dc4be1fae6849d7b933 Mon Sep 17 00:00:00 2001 From: amentis Date: Fri, 19 Jul 2024 17:24:07 +0300 Subject: [PATCH] add accounting client and accounting entry changes --- .../accounting/AccountingAggregateType.java | 33 ++++ .../accounting/AccountingDataRangeType.java | 33 ++++ .../accounting/AccountingSourceEntity.java | 59 +++++++ ...ccountingEntryCreatedIntegrationEvent.java | 4 + ...ngEntryCreatedIntegrationEventHandler.java | 2 +- ...tryCreatedIntegrationEventHandlerImpl.java | 10 +- .../model/AccountingAggregateResultItem.java | 44 ++++++ .../accounting/AccountingInfoLookup.java | 145 ++++++++++++++++++ .../service/accounting/AccountingClient.java | 10 ++ .../accounting/AccountingClientImpl.java | 26 ++++ .../accounting/AccountingProperties.java | 13 ++ .../accounting/AccountingServiceImpl.java | 139 ++++++++++++++++- .../maintenance/MaintenanceServiceImpl.java | 58 +++++-- .../resources/config/accounting-devel.yml | 8 + 14 files changed, 556 insertions(+), 28 deletions(-) create mode 100644 backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingAggregateType.java create mode 100644 backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingDataRangeType.java create mode 100644 backend/core/src/main/java/org/opencdmp/commons/types/accounting/AccountingSourceEntity.java create mode 100644 backend/core/src/main/java/org/opencdmp/model/AccountingAggregateResultItem.java create mode 100644 backend/core/src/main/java/org/opencdmp/query/lookup/accounting/AccountingInfoLookup.java create mode 100644 backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClient.java create mode 100644 backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClientImpl.java create mode 100644 backend/web/src/main/resources/config/accounting-devel.yml diff --git a/backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingAggregateType.java b/backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingAggregateType.java new file mode 100644 index 000000000..3c461350d --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingAggregateType.java @@ -0,0 +1,33 @@ +package org.opencdmp.commons.enums.accounting; + +import com.fasterxml.jackson.annotation.JsonValue; +import org.opencdmp.commons.enums.EnumUtils; +import org.opencdmp.data.converters.enums.DatabaseEnum; + +import java.util.Map; + +public enum AccountingAggregateType implements DatabaseEnum { + + Sum((short) 0), + Average((short) 1), + Max((short) 2), + Min((short) 3); + + private final Short value; + + AccountingAggregateType(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(AccountingAggregateType.class); + + public static AccountingAggregateType of(Short i) { + return map.get(i); + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingDataRangeType.java b/backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingDataRangeType.java new file mode 100644 index 000000000..d5b76a989 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/enums/accounting/AccountingDataRangeType.java @@ -0,0 +1,33 @@ +package org.opencdmp.commons.enums.accounting; + +import com.fasterxml.jackson.annotation.JsonValue; +import org.opencdmp.commons.enums.EnumUtils; +import org.opencdmp.data.converters.enums.DatabaseEnum; + +import java.util.Map; + +public enum AccountingDataRangeType implements DatabaseEnum { + + Custom((short) 0), + Today((short) 1), + ThisMonth((short) 2), + ThisYear((short) 3); + + private final Short value; + + AccountingDataRangeType(Short value) { + this.value = value; + } + + @JsonValue + public Short getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(AccountingDataRangeType.class); + + public static AccountingDataRangeType of(Short i) { + return map.get(i); + } + +} diff --git a/backend/core/src/main/java/org/opencdmp/commons/types/accounting/AccountingSourceEntity.java b/backend/core/src/main/java/org/opencdmp/commons/types/accounting/AccountingSourceEntity.java new file mode 100644 index 000000000..ffb5dde4d --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/commons/types/accounting/AccountingSourceEntity.java @@ -0,0 +1,59 @@ +package org.opencdmp.commons.types.accounting; + +public class AccountingSourceEntity { + + private String repositoryId; + private String url; + private String issuerUrl; + private String clientId; + private String clientSecret; + private String scope; + + public String getRepositoryId() { + return this.repositoryId; + } + + public void setRepositoryId(String repositoryId) { + this.repositoryId = repositoryId; + } + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getIssuerUrl() { + return this.issuerUrl; + } + + public void setIssuerUrl(String issuerUrl) { + this.issuerUrl = issuerUrl; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return this.clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getScope() { + return this.scope; + } + + public void setScope(String scope) { + this.scope = scope; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEvent.java b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEvent.java index 4eaacd7d5..7f6301ea4 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEvent.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEvent.java @@ -12,12 +12,16 @@ public class AccountingEntryCreatedIntegrationEvent extends TrackedEvent { private Instant timeStamp; private String serviceId; + public static final String _serviceId = "service"; private String resource; + public static final String _resource = "resource"; private String action; + public static final String _action = "action"; private String userId; + public static final String _userId = "user"; private AccountingMeasureType measure; diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandler.java b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandler.java index 6797d33b7..e875d1f4d 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandler.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandler.java @@ -7,5 +7,5 @@ import java.util.UUID; public interface AccountingEntryCreatedIntegrationEventHandler { - void handleAccountingEntry(String metric, AccountingValueType valueType, String subjectId, UUID tenantId) throws InvalidApplicationException; + void handleAccountingEntry(String metric, AccountingValueType valueType, String subjectId, UUID tenantId, String tenantCode) throws InvalidApplicationException; } diff --git a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandlerImpl.java b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandlerImpl.java index a7c5ac0bc..48435b85b 100644 --- a/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandlerImpl.java +++ b/backend/core/src/main/java/org/opencdmp/integrationevent/outbox/accountingentrycreated/AccountingEntryCreatedIntegrationEventHandlerImpl.java @@ -39,18 +39,18 @@ public class AccountingEntryCreatedIntegrationEventHandlerImpl implements Accoun } @Override - public void handleAccountingEntry(String metric, AccountingValueType valueType, String subjectId, UUID tenantId) throws InvalidApplicationException { - AccountingEntryCreatedIntegrationEvent event = new AccountingEntryCreatedIntegrationEvent(); + public void handleAccountingEntry(String metric, AccountingValueType valueType, String subjectId, UUID tenantId, String tenantCode) throws InvalidApplicationException { + AccountingEntryCreatedIntegrationEvent event = new AccountingEntryCreatedIntegrationEvent(); event.setTimeStamp(Instant.now()); event.setServiceId(accountingProperties.getServiceId()); - event.setAction(accountingProperties.getAction()); + event.setAction(metric); event.setMeasure(AccountingMeasureType.Unit); event.setType(valueType); - event.setResource(metric); + event.setResource(tenantCode); event.setUserId(subjectId); event.setValue(1.0); event.setTenant(tenantId); - + this.handle(event); } diff --git a/backend/core/src/main/java/org/opencdmp/model/AccountingAggregateResultItem.java b/backend/core/src/main/java/org/opencdmp/model/AccountingAggregateResultItem.java new file mode 100644 index 000000000..bb4cc7f19 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/model/AccountingAggregateResultItem.java @@ -0,0 +1,44 @@ +package org.opencdmp.model; + +public class AccountingAggregateResultItem { + + private Double sum; + + private Double average; + + private Double min; + + private Double max; + + public Double getSum() { + return sum; + } + + public void setSum(Double sum) { + this.sum = sum; + } + + public Double getAverage() { + return average; + } + + public void setAverage(Double average) { + this.average = average; + } + + public Double getMin() { + return min; + } + + public void setMin(Double min) { + this.min = min; + } + + public Double getMax() { + return max; + } + + public void setMax(Double max) { + this.max = max; + } +} diff --git a/backend/core/src/main/java/org/opencdmp/query/lookup/accounting/AccountingInfoLookup.java b/backend/core/src/main/java/org/opencdmp/query/lookup/accounting/AccountingInfoLookup.java new file mode 100644 index 000000000..78b15a33b --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/query/lookup/accounting/AccountingInfoLookup.java @@ -0,0 +1,145 @@ +package org.opencdmp.query.lookup.accounting; + +import gr.cite.tools.validation.specification.Specification; +import org.opencdmp.commons.enums.accounting.AccountingAggregateType; +import org.opencdmp.commons.enums.accounting.AccountingDataRangeType; +import org.opencdmp.commons.enums.accounting.AccountingMeasureType; +import org.opencdmp.commons.enums.accounting.AccountingValueType; +import org.opencdmp.commons.validation.BaseValidator; +import org.opencdmp.convention.ConventionService; +import org.opencdmp.errorcode.ErrorThesaurusProperties; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Scope; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public class AccountingInfoLookup { + + private Instant from; + public static final String _from = "from"; + + private Instant to; + public static final String _to = "to"; + + private List types; + public static final String _types = "types"; + + private List aggregateTypes; + public static final String _aggregateTypes = "aggregateTypes"; + + private AccountingDataRangeType dateRangeType; + public static final String _dateRangeType = "dateRangeType"; + + private AccountingMeasureType measure; + public static final String _measure = "measure"; + + private Set groupingFields; + public static final String _groupingFields = "groupingFields"; + + public Instant getFrom() { + return from; + } + + public void setFrom(Instant from) { + this.from = from; + } + + public Instant getTo() { + return to; + } + + public void setTo(Instant to) { + this.to = to; + } + + public List getTypes() { + return types; + } + + public void setTypes(List types) { + this.types = types; + } + + public List getAggregateTypes() { + return aggregateTypes; + } + + public void setAggregateTypes(List aggregateTypes) { + this.aggregateTypes = aggregateTypes; + } + + public AccountingDataRangeType getDateRangeType() { + return dateRangeType; + } + + public void setDateRangeType(AccountingDataRangeType dateRangeType) { + this.dateRangeType = dateRangeType; + } + + public AccountingMeasureType getMeasure() { + return measure; + } + + public void setMeasure(AccountingMeasureType measure) { + this.measure = measure; + } + + public Set getGroupingFields() { + return groupingFields; + } + + public void setGroupingFields(Set groupingFields) { + this.groupingFields = groupingFields; + } + + @Component(AccountingInfoLookup.AccountingInfoLookupValidator.ValidatorName) + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public static class AccountingInfoLookupValidator extends BaseValidator { + + public static final String ValidatorName = "AccountingInfoLookupValidator"; + + private final MessageSource messageSource; + + protected AccountingInfoLookupValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { + super(conventionService, errors); + this.messageSource = messageSource; + } + + @Override + protected Class modelClass() { + return AccountingInfoLookup.class; + } + + @Override + protected List specifications(AccountingInfoLookup item) { + return Arrays.asList( + this.spec() + .must(() -> !this.isNull(item.getMeasure())) + .failOn(AccountingInfoLookup._measure).failWith(messageSource.getMessage("Validation_Required", new Object[]{AccountingInfoLookup._measure}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getDateRangeType())) + .failOn(AccountingInfoLookup._dateRangeType).failWith(messageSource.getMessage("Validation_Required", new Object[]{AccountingInfoLookup._dateRangeType}, LocaleContextHolder.getLocale())), + this.spec() + .iff(()-> !this.isNull(item.getDateRangeType()) && item.getDateRangeType().equals(AccountingDataRangeType.Custom)) + .must(() -> !this.isNull(item.getFrom())) + .failOn(AccountingInfoLookup._from).failWith(messageSource.getMessage("Validation_Required", new Object[]{AccountingInfoLookup._from}, LocaleContextHolder.getLocale())), + this.spec() + .iff(()-> !this.isNull(item.getDateRangeType()) && item.getDateRangeType().equals(AccountingDataRangeType.Custom)) + .must(() -> !this.isNull(item.getTo())) + .failOn(AccountingInfoLookup._to).failWith(messageSource.getMessage("Validation_Required", new Object[]{AccountingInfoLookup._to}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isListNullOrEmpty(item.getAggregateTypes())) + .failOn(AccountingInfoLookup._aggregateTypes).failWith(messageSource.getMessage("Validation_Required", new Object[]{AccountingInfoLookup._aggregateTypes}, LocaleContextHolder.getLocale())), + this.spec() + .must(() -> !this.isNull(item.getGroupingFields())) + .failOn(AccountingInfoLookup._groupingFields).failWith(messageSource.getMessage("Validation_Required", new Object[]{AccountingInfoLookup._groupingFields}, LocaleContextHolder.getLocale())) + ); + } + } +} diff --git a/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClient.java b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClient.java new file mode 100644 index 000000000..d0cd8a4d8 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClient.java @@ -0,0 +1,10 @@ +package org.opencdmp.service.accounting; + +import org.opencdmp.model.AccountingAggregateResultItem; +import org.opencdmp.query.lookup.accounting.AccountingInfoLookup; + +public interface AccountingClient { + + AccountingAggregateResultItem calculate(AccountingInfoLookup lookup) throws Exception; + +} diff --git a/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClientImpl.java b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClientImpl.java new file mode 100644 index 000000000..523c9d156 --- /dev/null +++ b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingClientImpl.java @@ -0,0 +1,26 @@ +package org.opencdmp.service.accounting; + +import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import org.opencdmp.model.AccountingAggregateResultItem; +import org.opencdmp.query.lookup.accounting.AccountingInfoLookup; +import org.slf4j.LoggerFactory; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +public class AccountingClientImpl implements AccountingClient { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AccountingClientImpl.class)); + + private final WebClient accountingClient; + + public AccountingClientImpl(WebClient accountingClient) { + this.accountingClient = accountingClient; + } + + @Override + public AccountingAggregateResultItem calculate(AccountingInfoLookup lookup) throws Exception { + logger.debug(new MapLogEntry("calculate").And("lookup", lookup)); + return this.accountingClient.post().uri("/accounting/calculate", uriBuilder -> uriBuilder.build()).bodyValue(lookup).exchangeToMono(mono -> mono.statusCode().isError() ? mono.createException().flatMap(Mono::error) : mono.bodyToMono(AccountingAggregateResultItem.class)).block(); + + } +} diff --git a/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingProperties.java b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingProperties.java index 71a6a627b..f8a23a1fa 100644 --- a/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingProperties.java +++ b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingProperties.java @@ -1,7 +1,10 @@ package org.opencdmp.service.accounting; +import org.opencdmp.commons.types.accounting.AccountingSourceEntity; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.List; + @ConfigurationProperties(prefix = "accounting") public class AccountingProperties { private String serviceId; @@ -10,6 +13,8 @@ public class AccountingProperties { private String subjectId; + private List sources; + public String getServiceId() { return serviceId; } @@ -33,4 +38,12 @@ public class AccountingProperties { public void setSubjectId(String subjectId) { this.subjectId = subjectId; } + + public List getSources() { + return sources; + } + + public void setSources(List sources) { + this.sources = sources; + } } diff --git a/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingServiceImpl.java index dbecdde1a..c827cf945 100644 --- a/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/accounting/AccountingServiceImpl.java @@ -1,19 +1,49 @@ package org.opencdmp.service.accounting; +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.query.QueryFactory; +import gr.cite.tools.exception.MyNotFoundException; import gr.cite.tools.logging.LoggerService; +import gr.cite.tools.logging.MapLogEntry; +import gr.cite.tools.validation.ValidatorFactory; import org.opencdmp.commons.enums.UsageLimitTargetMetric; +import org.opencdmp.commons.enums.accounting.AccountingAggregateType; +import org.opencdmp.commons.enums.accounting.AccountingDataRangeType; +import org.opencdmp.commons.enums.accounting.AccountingMeasureType; import org.opencdmp.commons.enums.accounting.AccountingValueType; import org.opencdmp.commons.scope.tenant.TenantScope; import org.opencdmp.commons.scope.user.UserScope; +import org.opencdmp.commons.types.accounting.AccountingSourceEntity; +import org.opencdmp.convention.ConventionService; import org.opencdmp.data.UserCredentialEntity; +import org.opencdmp.integrationevent.outbox.accountingentrycreated.AccountingEntryCreatedIntegrationEvent; import org.opencdmp.integrationevent.outbox.accountingentrycreated.AccountingEntryCreatedIntegrationEventHandler; +import org.opencdmp.model.AccountingAggregateResultItem; import org.opencdmp.query.UserCredentialQuery; +import org.opencdmp.query.lookup.accounting.AccountingInfoLookup; 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 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.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; @Service public class AccountingServiceImpl implements AccountingService { @@ -23,20 +53,117 @@ public class AccountingServiceImpl implements AccountingService { private final AccountingEntryCreatedIntegrationEventHandler accountingEntryCreatedIntegrationEventHandler; private final UserScope userScope; private final TenantScope tenantScope; + private final MessageSource messageSource; + private final ConventionService conventionService; + private final Map clients; + private final TokenExchangeCacheService tokenExchangeCacheService; + private final AccountingProperties accountingProperties; + private final ValidatorFactory validatorFactory; @Autowired public AccountingServiceImpl( - QueryFactory queryFactory, AccountingEntryCreatedIntegrationEventHandler accountingEntryCreatedIntegrationEventHandler, UserScope userScope, TenantScope tenantScope) { + QueryFactory queryFactory, AccountingEntryCreatedIntegrationEventHandler accountingEntryCreatedIntegrationEventHandler, UserScope userScope, TenantScope tenantScope, MessageSource messageSource, ConventionService conventionService, TokenExchangeCacheService tokenExchangeCacheService, AccountingProperties accountingProperties, ValidatorFactory validatorFactory) { this.queryFactory = queryFactory; this.accountingEntryCreatedIntegrationEventHandler = accountingEntryCreatedIntegrationEventHandler; this.userScope = userScope; this.tenantScope = tenantScope; + this.messageSource = messageSource; + this.conventionService = conventionService; + this.tokenExchangeCacheService = tokenExchangeCacheService; + this.accountingProperties = accountingProperties; + this.validatorFactory = validatorFactory; + this.clients = new HashMap<>(); + } + + private AccountingClient getAccountingClient(String repositoryId) throws InvalidApplicationException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { + if (this.clients.containsKey(repositoryId)) return this.clients.get(repositoryId); + + //GK: It's register time + AccountingSourceEntity source = accountingProperties.getSources().stream().filter(x -> x.getRepositoryId().equals(repositoryId)).findFirst().orElse(null); + if (source != null) { + TokenExchangeModel tokenExchangeModel = new TokenExchangeModel("accounting:" + source, source.getIssuerUrl(), source.getClientId(), source.getClientSecret(), source.getScope()); + TokenExchangeFilterFunction apiKeyExchangeFilterFunction = new TokenExchangeFilterFunction(this.tokenExchangeCacheService, tokenExchangeModel); + WebClient webClient = WebClient.builder().baseUrl(source.getUrl() + "/api/accounting-service") + .filters(exchangeFilterFunctions -> { + exchangeFilterFunctions.add(apiKeyExchangeFilterFunction); + exchangeFilterFunctions.add(logRequest()); + exchangeFilterFunctions.add(logResponse()); + }).codecs(codecs -> codecs + .defaultCodecs() + ).build(); + AccountingClientImpl accounting = new AccountingClientImpl(webClient); + this.clients.put(repositoryId, accounting); + return accounting; + } + return null; + } + + private static ExchangeFilterFunction logRequest() { + return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { + logger.debug(new MapLogEntry("Request").And("method", clientRequest.method().toString()).And("url", clientRequest.url())); + return Mono.just(clientRequest); + }); + } + + private static ExchangeFilterFunction logResponse() { + return ExchangeFilterFunction.ofResponseProcessor(response -> { + if (response.statusCode().isError()) { + return response.mutate().build().bodyToMono(String.class) + .flatMap(body -> { + logger.error(new MapLogEntry("Response").And("method", response.request().getMethod().toString()).And("url", response.request().getURI()).And("status", response.statusCode().toString()).And("body", body)); + return Mono.just(response); + }); + } + return Mono.just(response); + + }); } public Integer getCurrentMetricValue(UsageLimitTargetMetric metric) { - //TODO - //Get/Calculate current metric value from accountingService - return 10; + AccountingClient accountingClient = null; + try { + accountingClient = this.getAccountingClient(this.accountingProperties.getSources().getFirst().getRepositoryId()); + } catch (InvalidApplicationException e) { + throw new RuntimeException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } catch (NoSuchPaddingException e) { + throw new RuntimeException(e); + } catch (IllegalBlockSizeException e) { + throw new RuntimeException(e); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (BadPaddingException e) { + throw new RuntimeException(e); + } catch (InvalidKeyException e) { + throw new RuntimeException(e); + } + if (accountingClient == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{this.accountingProperties.getSources().getFirst().getRepositoryId(), AccountingClient.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + AccountingInfoLookup lookup = new AccountingInfoLookup(); + lookup.setTo(Instant.now()); + lookup.setDateRangeType(AccountingDataRangeType.ThisYear); + lookup.setMeasure(AccountingMeasureType.Unit); + lookup.setAggregateTypes(new ArrayList<>()); + lookup.getAggregateTypes().add(AccountingAggregateType.Sum); + + lookup.setGroupingFields(Set.of( + AccountingEntryCreatedIntegrationEvent._serviceId, + AccountingEntryCreatedIntegrationEvent._action, + AccountingEntryCreatedIntegrationEvent._resource, + AccountingEntryCreatedIntegrationEvent._userId + )); + + this.validatorFactory.validator(AccountingInfoLookup.AccountingInfoLookupValidator.class).validateForce(lookup); + + AccountingAggregateResultItem accountingAggregateResultItem = null; + try { + accountingAggregateResultItem = accountingClient.calculate(lookup); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return accountingAggregateResultItem.getSum().intValue(); } public void set(String metric) { @@ -50,7 +177,7 @@ public class AccountingServiceImpl implements AccountingService { if (userCredential != null) subjectId = userCredential.getExternalId(); else subjectId = this.userScope.getUserId().toString(); - this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(metric, AccountingValueType.Plus, subjectId, this.tenantScope.getTenant()); + this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(metric, AccountingValueType.Plus, subjectId, this.tenantScope.getTenant(), this.tenantScope.getTenantCode() != null ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode()); } public void decrease(String metric) throws InvalidApplicationException { @@ -60,7 +187,7 @@ public class AccountingServiceImpl implements AccountingService { if (userCredential != null) subjectId = userCredential.getExternalId(); else subjectId = this.userScope.getUserId().toString(); - this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(metric, AccountingValueType.Minus, subjectId, this.tenantScope.getTenant()); + this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(metric, AccountingValueType.Minus, subjectId, this.tenantScope.getTenant(), this.tenantScope.getTenantCode() != null ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode()); } } diff --git a/backend/core/src/main/java/org/opencdmp/service/maintenance/MaintenanceServiceImpl.java b/backend/core/src/main/java/org/opencdmp/service/maintenance/MaintenanceServiceImpl.java index 43b07d0e1..29063f934 100644 --- a/backend/core/src/main/java/org/opencdmp/service/maintenance/MaintenanceServiceImpl.java +++ b/backend/core/src/main/java/org/opencdmp/service/maintenance/MaintenanceServiceImpl.java @@ -215,6 +215,7 @@ public class MaintenanceServiceImpl implements MaintenanceService { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(PlanQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Plan._id).ensure(Plan._isActive).ensure(Plan._creator)); List userCredentials = this.queryFactory.query(UserCredentialQuery.class).disableTracking().userIds(items.stream().map(x -> x.getCreatorId()).distinct().collect(Collectors.toList())).collect(); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (PlanEntity item : items) { String subjectId; @@ -225,8 +226,9 @@ public class MaintenanceServiceImpl implements MaintenanceService { } else { subjectId = item.getCreatorId().toString(); } - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PLAN_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PLAN_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PLAN_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PLAN_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); @@ -241,11 +243,13 @@ public class MaintenanceServiceImpl implements MaintenanceService { try { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(PlanBlueprintQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(PlanBlueprint._id).ensure(PlanBlueprint._isActive)); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (PlanBlueprintEntity item : items) { String subjectId = accountingProperties.getSubjectId(); - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.BLUEPRINT_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.BLUEPRINT_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.BLUEPRINT_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.BLUEPRINT_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); @@ -261,6 +265,7 @@ public class MaintenanceServiceImpl implements MaintenanceService { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(DescriptionQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Description._id).ensure(Description._isActive).ensure(Description._createdBy)); List userCredentials = this.queryFactory.query(UserCredentialQuery.class).disableTracking().userIds(items.stream().map(x -> x.getCreatedById()).distinct().collect(Collectors.toList())).collect(); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (DescriptionEntity item : items) { String subjectId; @@ -271,8 +276,9 @@ public class MaintenanceServiceImpl implements MaintenanceService { } else { subjectId = item.getCreatedById().toString(); } - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); @@ -287,11 +293,13 @@ public class MaintenanceServiceImpl implements MaintenanceService { try { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(DescriptionTemplateQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(DescriptionTemplate._id).ensure(DescriptionTemplate._isActive)); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (DescriptionTemplateEntity item : items) { String subjectId = accountingProperties.getSubjectId(); - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); @@ -306,11 +314,13 @@ public class MaintenanceServiceImpl implements MaintenanceService { try { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(DescriptionTemplateTypeQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(DescriptionTemplateType._id).ensure(DescriptionTemplateType._isActive)); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (DescriptionTemplateTypeEntity item : items) { String subjectId = accountingProperties.getSubjectId(); - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_TYPE_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_TYPE_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_TYPE_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.DESCRIPTION_TEMPLATE_TYPE_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); @@ -325,11 +335,13 @@ public class MaintenanceServiceImpl implements MaintenanceService { try { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(PrefillingSourceQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(PrefillingSource._id).ensure(PrefillingSource._isActive)); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (PrefillingSourceEntity item : items) { String subjectId = accountingProperties.getSubjectId(); - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PREFILLING_SOURCES_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PREFILLING_SOURCES_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PREFILLING_SOURCES_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.PREFILLING_SOURCES_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); @@ -344,11 +356,13 @@ public class MaintenanceServiceImpl implements MaintenanceService { try { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(ReferenceTypeQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(ReferenceType._id).ensure(ReferenceType._isActive)); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (ReferenceTypeEntity item : items) { String subjectId = accountingProperties.getSubjectId(); - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.REFERENCE_TYPE_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.REFERENCE_TYPE_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.REFERENCE_TYPE_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.REFERENCE_TYPE_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); @@ -364,6 +378,7 @@ public class MaintenanceServiceImpl implements MaintenanceService { this.tenantEntityManager.disableTenantFilters(); List items = this.queryFactory.query(TenantUserQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(TenantUserEntity._id).ensure(TenantUserEntity._userId).ensure(TenantUserEntity._isActive)); List userCredentials = this.queryFactory.query(UserCredentialQuery.class).disableTracking().userIds(items.stream().map(x -> x.getUserId()).distinct().collect(Collectors.toList())).collect(); + List tenants = this.queryFactory.query(TenantQuery.class).disableTracking().collectAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); for (TenantUserEntity item : items) { String subjectId; @@ -374,11 +389,22 @@ public class MaintenanceServiceImpl implements MaintenanceService { } else { subjectId = item.getUserId().toString(); } - if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.USER_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId()); - else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.USER_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId()); + String tenantCode = this.findTenantCode(tenants, item.getTenantId()); + if (item.getIsActive().equals(IsActive.Active)) this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.USER_COUNT.getValue(), AccountingValueType.Plus, subjectId, item.getTenantId(), tenantCode); + else this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(UsageLimitTargetMetric.USER_COUNT.getValue(), AccountingValueType.Minus, subjectId, item.getTenantId(), tenantCode); } } finally { this.tenantEntityManager.reloadTenantFilters(); } } + + private String findTenantCode(List tenants, UUID tenantId) { + if (tenants != null && !tenants.isEmpty() && tenantId != null){ + TenantEntity tenant = tenants.stream().filter(x -> x.getId().equals(tenantId)).findFirst().orElse(null); + if (tenant != null) return tenant.getCode(); + else return this.tenantScope.getDefaultTenantCode(); + } else { + return this.tenantScope.getDefaultTenantCode(); + } + } } diff --git a/backend/web/src/main/resources/config/accounting-devel.yml b/backend/web/src/main/resources/config/accounting-devel.yml new file mode 100644 index 000000000..d50f7885f --- /dev/null +++ b/backend/web/src/main/resources/config/accounting-devel.yml @@ -0,0 +1,8 @@ +accounting: + sources: + - url: http://localhost:50002 + repositoryId: accounting + issuer-url: ${IDP_ISSUER_URI_TOKEN} + client-id: ${ACCOUNTING_CLIENT_ID} + client-secret: ${ACCOUNTING_CLIENT_SECRET} + scope: ${IDP_APIKEY_SCOPE}