add Usage Limit periodicity

This commit is contained in:
amentis 2024-07-24 14:10:40 +03:00
parent 25c3ae2c11
commit 48434f845e
28 changed files with 528 additions and 32 deletions

View File

@ -0,0 +1,30 @@
package org.opencdmp.commons.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import org.opencdmp.data.converters.enums.DatabaseEnum;
import java.util.Map;
public enum UsageLimitPeriodicityRange implements DatabaseEnum<Short> {
Monthly((short) 0),
Yearly((short) 1);
private final Short value;
UsageLimitPeriodicityRange(Short value) {
this.value = value;
}
@JsonValue
public Short getValue() {
return value;
}
private static final Map<Short, UsageLimitPeriodicityRange> map = EnumUtils.getEnumValueMap(UsageLimitPeriodicityRange.class);
public static UsageLimitPeriodicityRange of(Short i) {
return map.get(i);
}
}

View File

@ -10,7 +10,7 @@ public enum AccountingValueType implements DatabaseEnum<Short> {
Plus((short) 0),
Minus((short) 1),
Reset((short) 3);
Reset((short) 2);
private final Short value;

View File

@ -0,0 +1,35 @@
package org.opencdmp.commons.types.usagelimit;
import jakarta.xml.bind.annotation.*;
import org.opencdmp.commons.enums.ReferenceFieldDataType;
import org.opencdmp.commons.enums.UsageLimitPeriodicityRange;
import org.opencdmp.commons.types.reference.FieldEntity;
import java.util.List;
@XmlRootElement(name = "definition")
@XmlAccessorType(XmlAccessType.FIELD)
public class DefinitionEntity {
@XmlAttribute(name = "hasPeriodicity")
private Boolean hasPeriodicity;
@XmlAttribute(name = "periodicityRange")
private UsageLimitPeriodicityRange periodicityRange;
public Boolean getHasPeriodicity() {
return hasPeriodicity;
}
public void setHasPeriodicity(Boolean hasPeriodicity) {
this.hasPeriodicity = hasPeriodicity;
}
public UsageLimitPeriodicityRange getPeriodicityRange() {
return periodicityRange;
}
public void setPeriodicityRange(UsageLimitPeriodicityRange periodicityRange) {
this.periodicityRange = periodicityRange;
}
}

View File

@ -1,11 +1,13 @@
package org.opencdmp.data;
import jakarta.persistence.*;
import org.hibernate.annotations.Type;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitTargetMetric;
import org.opencdmp.data.converters.enums.IsActiveConverter;
import org.opencdmp.data.converters.enums.UsageLimitTargetMetricConverter;
import org.opencdmp.data.tenant.TenantScopedBaseEntity;
import org.opencdmp.data.types.SQLXMLType;
import java.time.Instant;
import java.util.UUID;
@ -39,6 +41,12 @@ public class UsageLimitEntity extends TenantScopedBaseEntity {
public static final String _value = "value";
@Type(SQLXMLType.class)
@Column(name = "definition", columnDefinition = "xml")
private String definition;
public static final String _definition = "definition";
@Column(name = "is_active", nullable = false)
@Convert(converter = IsActiveConverter.class)
private IsActive isActive;
@ -87,6 +95,14 @@ public class UsageLimitEntity extends TenantScopedBaseEntity {
this.value = value;
}
public String getDefinition() {
return definition;
}
public void setDefinition(String definition) {
this.definition = definition;
}
public IsActive getIsActive() {
return isActive;
}

View File

@ -49,7 +49,8 @@ public class AccountingEntryCreatedIntegrationEventHandlerImpl implements Accoun
event.setType(valueType);
event.setResource(tenantCode);
event.setUserId(subjectId);
event.setValue(1.0);
if (valueType != null && valueType.equals(AccountingValueType.Reset)) event.setValue(0.0);
else event.setValue(1.0);
event.setTenant(tenantId);
this.handle(event);

View File

@ -2,6 +2,7 @@ package org.opencdmp.model;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitTargetMetric;
import org.opencdmp.model.usagelimit.Definition;
import java.time.Instant;
import java.util.UUID;
@ -23,6 +24,9 @@ public class UsageLimit {
private IsActive isActive;
public static final String _isActive = "isActive";
private Definition definition;
public static final String _definition = "definition";
private Instant createdAt;
public static final String _createdAt = "createdAt";
@ -67,6 +71,14 @@ public class UsageLimit {
this.value = value;
}
public Definition getDefinition() {
return definition;
}
public void setDefinition(Definition definition) {
this.definition = definition;
}
public IsActive getIsActive() {
return isActive;
}

View File

@ -1,14 +1,18 @@
package org.opencdmp.model.builder;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.DataLogEntry;
import gr.cite.tools.logging.LoggerService;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.scope.tenant.TenantScope;
import org.opencdmp.commons.types.usagelimit.DefinitionEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.UsageLimitEntity;
import org.opencdmp.model.UsageLimit;
import org.opencdmp.model.builder.usagelimit.DefinitionBuilder;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@ -22,15 +26,19 @@ import java.util.*;
public class UsageLimitBuilder extends BaseBuilder<UsageLimit, UsageLimitEntity> {
private final TenantScope tenantScope;
private final XmlHandlingService xmlHandlingService;
private final BuilderFactory builderFactory;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public UsageLimitBuilder(
ConventionService conventionService,
TenantScope tenantScope) {
TenantScope tenantScope, XmlHandlingService xmlHandlingService, BuilderFactory builderFactory) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(UsageLimitBuilder.class)));
this.tenantScope = tenantScope;
this.xmlHandlingService = xmlHandlingService;
this.builderFactory = builderFactory;
}
public UsageLimitBuilder authorize(EnumSet<AuthorizationFlags> values) {
@ -45,6 +53,8 @@ public class UsageLimitBuilder extends BaseBuilder<UsageLimit, UsageLimitEntity>
if (fields == null || data == null || fields.isEmpty())
return new ArrayList<>();
FieldSet definitionFields = fields.extractPrefixed(this.asPrefix(UsageLimit._definition));
List<UsageLimit> models = new ArrayList<>();
for (UsageLimitEntity d : data) {
UsageLimit m = new UsageLimit();
@ -64,6 +74,10 @@ public class UsageLimitBuilder extends BaseBuilder<UsageLimit, UsageLimitEntity>
m.setIsActive(d.getIsActive());
if (fields.hasField(this.asIndexer(UsageLimit._hash)))
m.setHash(this.hashValue(d.getUpdatedAt()));
if (!definitionFields.isEmpty() && d.getDefinition() != null) {
DefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(DefinitionEntity.class, d.getDefinition());
m.setDefinition(this.builderFactory.builder(DefinitionBuilder.class).authorize(this.authorize).build(definitionFields, definition));
}
if (fields.hasField(this.asIndexer(UsageLimit._belongsToCurrentTenant))) m.setBelongsToCurrentTenant(this.getBelongsToCurrentTenant(d, this.tenantScope));
models.add(m);

View File

@ -0,0 +1,55 @@
package org.opencdmp.model.builder.usagelimit;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.DataLogEntry;
import gr.cite.tools.logging.LoggerService;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.commons.types.usagelimit.DefinitionEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.model.builder.BaseBuilder;
import org.opencdmp.model.usagelimit.Definition;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.*;
@Component("usagelimitdefinitionbuilder")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DefinitionBuilder extends BaseBuilder<Definition, DefinitionEntity> {
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public DefinitionBuilder(
ConventionService conventionService) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(DefinitionBuilder.class)));
}
public DefinitionBuilder authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
@Override
public List<Definition> build(FieldSet fields, List<DefinitionEntity> data) throws MyApplicationException {
this.logger.debug("building for {} items requesting {} fields", Optional.ofNullable(data).map(List::size).orElse(0), Optional.ofNullable(fields).map(FieldSet::getFields).map(Set::size).orElse(0));
this.logger.trace(new DataLogEntry("requested fields", fields));
if (fields == null || data == null || fields.isEmpty())
return new ArrayList<>();
List<Definition> models = new ArrayList<>();
for (DefinitionEntity d : data) {
Definition m = new Definition();
if (fields.hasField(this.asIndexer(Definition._periodicityRange))) m.setPeriodicityRange(d.getPeriodicityRange());
if (fields.hasField(this.asIndexer(Definition._hasPeriodicity))) m.setHasPeriodicity(d.getHasPeriodicity());
models.add(m);
}
this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0));
return models;
}
}

View File

@ -7,6 +7,8 @@ import gr.cite.tools.logging.DataLogEntry;
import gr.cite.tools.logging.LoggerService;
import org.opencdmp.authorization.Permission;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.model.UsageLimit;
import org.opencdmp.model.censorship.usagelimit.DefinitionCensor;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
@ -35,6 +37,8 @@ public class UsageLimitsCensor extends BaseCensor {
return;
this.authService.authorizeForce(Permission.BrowseUsageLimit);
FieldSet definitionFields = fields.extractPrefixed(this.asIndexerPrefix(UsageLimit._definition));
this.censorFactory.censor(DefinitionCensor.class).censor(definitionFields);
}
}

View File

@ -0,0 +1,45 @@
package org.opencdmp.model.censorship.usagelimit;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.censor.CensorFactory;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.DataLogEntry;
import gr.cite.tools.logging.LoggerService;
import org.opencdmp.authorization.Permission;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.model.censorship.BaseCensor;
import org.opencdmp.model.censorship.reference.FieldCensor;
import org.opencdmp.model.reference.Definition;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component("usagelimitdefinitioncensor")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DefinitionCensor extends BaseCensor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(DefinitionCensor.class));
protected final AuthorizationService authService;
protected final CensorFactory censorFactory;
public DefinitionCensor(ConventionService conventionService,
AuthorizationService authService,
CensorFactory censorFactory) {
super(conventionService);
this.authService = authService;
this.censorFactory = censorFactory;
}
public void censor(FieldSet fields) {
logger.debug(new DataLogEntry("censoring fields", fields));
if (fields == null || fields.isEmpty())
return;
this.authService.authorizeForce(Permission.BrowseUsageLimit);
}
}

View File

@ -1,11 +1,13 @@
package org.opencdmp.model.persist;
import gr.cite.tools.validation.ValidatorFactory;
import gr.cite.tools.validation.specification.Specification;
import org.opencdmp.commons.enums.UsageLimitTargetMetric;
import org.opencdmp.commons.validation.BaseValidator;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.UsageLimitEntity;
import org.opencdmp.errorcode.ErrorThesaurusProperties;
import org.opencdmp.model.persist.usagelimit.DefinitionPersist;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope;
@ -30,6 +32,9 @@ public class UsageLimitPersist {
private Long value;
public static final String _value = "value";
private DefinitionPersist definition;
public static final String _definition = "definition";
private String hash;
public static final String _hash = "hash";
@ -65,6 +70,14 @@ public class UsageLimitPersist {
this.value = value;
}
public DefinitionPersist getDefinition() {
return definition;
}
public void setDefinition(DefinitionPersist definition) {
this.definition = definition;
}
public String getHash() {
return this.hash;
}
@ -81,9 +94,12 @@ public class UsageLimitPersist {
private final MessageSource messageSource;
protected UsageLimitPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
private final ValidatorFactory validatorFactory;
protected UsageLimitPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) {
super(conventionService, errors);
this.messageSource = messageSource;
this.validatorFactory = validatorFactory;
}
@Override
@ -117,7 +133,12 @@ public class UsageLimitPersist {
.failOn(UsageLimitPersist._value).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._value}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> item.getValue() > 0)
.failOn(UsageLimitPersist._value).failWith(this.messageSource.getMessage("Validation_UnexpectedValue", new Object[]{UsageLimitPersist._value}, LocaleContextHolder.getLocale()))
.failOn(UsageLimitPersist._value).failWith(this.messageSource.getMessage("Validation_UnexpectedValue", new Object[]{UsageLimitPersist._value}, LocaleContextHolder.getLocale())),
this.refSpec()
.iff(() -> !this.isNull(item.getDefinition()))
.on(UsageLimitPersist._definition)
.over(item.getDefinition())
.using(() -> this.validatorFactory.validator(org.opencdmp.model.persist.usagelimit.DefinitionPersist.DefinitionPersistValidator.class))
);
}
}

View File

@ -0,0 +1,76 @@
package org.opencdmp.model.persist.usagelimit;
import gr.cite.tools.validation.specification.Specification;
import org.opencdmp.commons.enums.UsageLimitPeriodicityRange;
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.util.Arrays;
import java.util.List;
public class DefinitionPersist {
private Boolean hasPeriodicity;
public final static String _hasPeriodicity = "hasPeriodicity";
private UsageLimitPeriodicityRange periodicityRange;
public final static String _periodicityRange = "periodicityRange";
public Boolean getHasPeriodicity() {
return hasPeriodicity;
}
public void setHasPeriodicity(Boolean hasPeriodicity) {
this.hasPeriodicity = hasPeriodicity;
}
public UsageLimitPeriodicityRange getPeriodicityRange() {
return periodicityRange;
}
public void setPeriodicityRange(UsageLimitPeriodicityRange periodicityRange) {
this.periodicityRange = periodicityRange;
}
@Component(DefinitionPersistValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class DefinitionPersistValidator extends BaseValidator<DefinitionPersist> {
public static final String ValidatorName = "UsageLimit.DefinitionPersistValidator";
private final MessageSource messageSource;
protected DefinitionPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
super(conventionService, errors);
this.messageSource = messageSource;
}
@Override
protected Class<DefinitionPersist> modelClass() {
return DefinitionPersist.class;
}
@Override
protected List<Specification> specifications(DefinitionPersist item) {
return Arrays.asList(
this.spec()
.must(() -> !this.isNull(item.getHasPeriodicity()))
.failOn(DefinitionPersist._hasPeriodicity).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DefinitionPersist._hasPeriodicity}, LocaleContextHolder.getLocale())),
this.spec()
.iff(() -> !this.isNull(item.getHasPeriodicity()) && item.getHasPeriodicity())
.must(() -> !this.isNull(item.getPeriodicityRange()))
.failOn(DefinitionPersist._periodicityRange).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{DefinitionPersist._periodicityRange}, LocaleContextHolder.getLocale()))
);
}
}
}

View File

@ -0,0 +1,28 @@
package org.opencdmp.model.usagelimit;
import org.opencdmp.commons.enums.UsageLimitPeriodicityRange;
public class Definition {
private Boolean hasPeriodicity;
public final static String _hasPeriodicity = "hasPeriodicity";
private UsageLimitPeriodicityRange periodicityRange;
public final static String _periodicityRange = "periodicityRange";
public Boolean getHasPeriodicity() {
return hasPeriodicity;
}
public void setHasPeriodicity(Boolean hasPeriodicity) {
this.hasPeriodicity = hasPeriodicity;
}
public UsageLimitPeriodicityRange getPeriodicityRange() {
return periodicityRange;
}
public void setPeriodicityRange(UsageLimitPeriodicityRange periodicityRange) {
this.periodicityRange = periodicityRange;
}
}

View File

@ -194,6 +194,7 @@ public class UsageLimitQuery extends QueryBase<UsageLimitEntity> {
item.setLabel(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._label, String.class));
item.setTargetMetric(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._targetMetric, UsageLimitTargetMetric.class));
item.setValue(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._value, Long.class));
item.setDefinition(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._definition, String.class));
item.setCreatedAt(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._createdAt, Instant.class));
item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._updatedAt, Instant.class));
item.setIsActive(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._isActive, IsActive.class));
@ -210,6 +211,8 @@ public class UsageLimitQuery extends QueryBase<UsageLimitEntity> {
return UsageLimitEntity._targetMetric;
else if (item.match(UsageLimit._value))
return UsageLimitEntity._value;
else if (item.prefix(UsageLimit._definition))
return UsageLimitEntity._definition;
else if (item.match(UsageLimit._createdAt))
return UsageLimitEntity._createdAt;
else if (item.match(UsageLimit._updatedAt))

View File

@ -3,15 +3,15 @@ package org.opencdmp.service.accounting;
import org.opencdmp.commons.types.accounting.AccountingSourceEntity;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Instant;
import java.util.List;
@ConfigurationProperties(prefix = "accounting")
public class AccountingProperties {
private Boolean enabled;
private String serviceId;
private Instant fromInstant;
private String subjectId;
private List<String> projectFields;
private List<AccountingSourceEntity> sources;
@ -28,6 +28,14 @@ public class AccountingProperties {
this.enabled = enabled;
}
public Instant getFromInstant() {
return fromInstant;
}
public void setFromInstant(Instant fromInstant) {
this.fromInstant = fromInstant;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}

View File

@ -1,14 +1,16 @@
package org.opencdmp.service.accounting;
import org.opencdmp.commons.enums.UsageLimitTargetMetric;
import org.opencdmp.commons.types.usagelimit.DefinitionEntity;
import javax.management.InvalidApplicationException;
import java.util.UUID;
public interface AccountingService {
Integer getCurrentMetricValue(UsageLimitTargetMetric metric) throws InvalidApplicationException;
Integer getCurrentMetricValue(UsageLimitTargetMetric metric, DefinitionEntity definition) throws InvalidApplicationException;
void set(String metric);
void set(String metric, UUID tenantId, String tenantCode) throws InvalidApplicationException;
void increase(String metric) throws InvalidApplicationException;

View File

@ -11,6 +11,7 @@ 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.UsageLimitPeriodicityRange;
import org.opencdmp.commons.enums.UsageLimitTargetMetric;
import org.opencdmp.commons.enums.accounting.AccountingAggregateType;
import org.opencdmp.commons.enums.accounting.AccountingDataRangeType;
@ -19,6 +20,7 @@ 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.commons.types.usagelimit.DefinitionEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.UserCredentialEntity;
import org.opencdmp.integrationevent.outbox.accountingentrycreated.AccountingEntryCreatedIntegrationEvent;
@ -47,10 +49,7 @@ 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.List;
import java.util.Map;
import java.util.*;
@Service
public class AccountingServiceImpl implements AccountingService {
@ -130,7 +129,7 @@ public class AccountingServiceImpl implements AccountingService {
});
}
public Integer getCurrentMetricValue(UsageLimitTargetMetric metric) throws InvalidApplicationException {
public Integer getCurrentMetricValue(UsageLimitTargetMetric metric, DefinitionEntity definition) throws InvalidApplicationException {
if (this.isEnabled) {
AccountingClient accountingClient = null;
try {
@ -154,13 +153,20 @@ public class AccountingServiceImpl implements AccountingService {
throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{this.accountingProperties.getSources().getFirst().getRepositoryId(), AccountingClient.class.getSimpleName()}, LocaleContextHolder.getLocale()));
AccountingInfoLookup lookup = new AccountingInfoLookup();
if (definition.getHasPeriodicity()) {
if (definition.getPeriodicityRange().equals(UsageLimitPeriodicityRange.Monthly)) lookup.setDateRangeType(AccountingDataRangeType.ThisMonth);
else lookup.setDateRangeType(AccountingDataRangeType.ThisYear);
} else {
lookup.setDateRangeType(AccountingDataRangeType.Custom);
lookup.setTo(Instant.now());
lookup.setDateRangeType(AccountingDataRangeType.ThisYear);
lookup.setFrom(this.accountingProperties.getFromInstant());
}
lookup.setMeasure(AccountingMeasureType.Unit);
lookup.setServiceCodes(List.of(this.accountingProperties.getServiceId()));
lookup.setAggregateTypes(List.of(AccountingAggregateType.Sum));
lookup.setActionCodes(List.of(metric.getValue()));
lookup.setUserCodes(List.of(this.getSubjectId()));
try {
lookup.setResourceCodes(List.of(this.tenantScope.getTenantCode() != null ? this.tenantScope.getTenantCode() : this.tenantScope.getDefaultTenantCode()));
} catch (InvalidApplicationException e) {
@ -191,8 +197,12 @@ public class AccountingServiceImpl implements AccountingService {
return null;
}
public void set(String metric) {
//TODO
public void set(String metric, UUID tenantId, String tenantCode) throws InvalidApplicationException{
if (this.isEnabled) {
String subjectId = this.getSubjectId();
this.accountingEntryCreatedIntegrationEventHandler.handleAccountingEntry(metric, AccountingValueType.Reset, subjectId, tenantId, tenantCode);
}
}
public void increase(String metric) throws InvalidApplicationException {

View File

@ -5,6 +5,7 @@ import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.FieldSet;
import jakarta.xml.bind.JAXBException;
import org.opencdmp.commons.enums.UsageLimitTargetMetric;
import org.opencdmp.model.UsageLimit;
import org.opencdmp.model.persist.UsageLimitPersist;
@ -14,7 +15,7 @@ import java.util.UUID;
public interface UsageLimitService {
UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException;
UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException;
void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException;

View File

@ -11,11 +11,15 @@ 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.xml.bind.JAXBException;
import org.jetbrains.annotations.NotNull;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.Permission;
import org.opencdmp.commons.XmlHandlingService;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitTargetMetric;
import gr.cite.tools.data.query.QueryFactory;
import org.opencdmp.commons.types.usagelimit.DefinitionEntity;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.data.UsageLimitEntity;
@ -24,6 +28,7 @@ import org.opencdmp.model.UsageLimit;
import org.opencdmp.model.builder.UsageLimitBuilder;
import org.opencdmp.model.deleter.UsageLimitDeleter;
import org.opencdmp.model.persist.UsageLimitPersist;
import org.opencdmp.model.persist.usagelimit.DefinitionPersist;
import org.opencdmp.query.UsageLimitQuery;
import org.opencdmp.service.accounting.AccountingProperties;
import org.opencdmp.service.accounting.AccountingService;
@ -63,6 +68,8 @@ public class UsageLimitServiceImpl implements UsageLimitService {
private final TenantEntityManager tenantEntityManager;
private final XmlHandlingService xmlHandlingService;
private final AccountingService accountingService;
private final UsageLimitProperties usageLimitProperties;
@ -79,7 +86,7 @@ public class UsageLimitServiceImpl implements UsageLimitService {
ConventionService conventionService,
ErrorThesaurusProperties errors,
MessageSource messageSource,
QueryFactory queryFactory, TenantEntityManager tenantEntityManager, AccountingService accountingService, UsageLimitProperties usageLimitProperties, AccountingProperties accountingProperties) {
QueryFactory queryFactory, TenantEntityManager tenantEntityManager, XmlHandlingService xmlHandlingService, AccountingService accountingService, UsageLimitProperties usageLimitProperties, AccountingProperties accountingProperties) {
this.entityManager = entityManager;
this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory;
@ -89,12 +96,13 @@ public class UsageLimitServiceImpl implements UsageLimitService {
this.messageSource = messageSource;
this.queryFactory = queryFactory;
this.tenantEntityManager = tenantEntityManager;
this.xmlHandlingService = xmlHandlingService;
this.accountingService = accountingService;
this.usageLimitProperties = usageLimitProperties;
this.accountingProperties = accountingProperties;
}
public UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException {
public UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException, JAXBException {
logger.debug(new MapLogEntry("persisting data UsageLimit").And("model", model).And("fields", fields));
this.authorizationService.authorizeForce(Permission.EditUsageLimit);
@ -132,6 +140,8 @@ public class UsageLimitServiceImpl implements UsageLimitService {
data.setTargetMetric(model.getTargetMetric());
data.setValue(model.getValue());
data.setUpdatedAt(Instant.now());
data.setDefinition(this.xmlHandlingService.toXml(this.buildDefinitionEntity(model.getDefinition())));
if (isUpdate)
this.entityManager.merge(data);
else
@ -141,6 +151,17 @@ public class UsageLimitServiceImpl implements UsageLimitService {
return this.builderFactory.builder(UsageLimitBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(BaseFieldSet.build(fields, UsageLimit._id), data);
}
private @NotNull DefinitionEntity buildDefinitionEntity(DefinitionPersist persist) {
DefinitionEntity data = new DefinitionEntity();
if (persist == null)
return data;
data.setHasPeriodicity(persist.getHasPeriodicity());
if (persist.getHasPeriodicity()) data.setPeriodicityRange(persist.getPeriodicityRange());
return data;
}
public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug("deleting UsageLimit: {}", id);
@ -155,13 +176,17 @@ public class UsageLimitServiceImpl implements UsageLimitService {
if (metric == null) throw new MyApplicationException("Target Metric not defined");
Integer currentValue = this.accountingService.getCurrentMetricValue(metric);
try {
this.tenantEntityManager.loadExplicitTenantFilters();
UsageLimitEntity usageLimitEntity = this.queryFactory.query(UsageLimitQuery.class).disableTracking().usageLimitTargetMetrics(metric).isActive(IsActive.Active).firstAs(new BaseFieldSet().ensure(UsageLimit._label).ensure(UsageLimit._targetMetric).ensure(UsageLimit._value));
if (usageLimitEntity != null && currentValue >= usageLimitEntity.getValue()) throw new MyValidationException(this.errors.getUsageLimitException().getCode(), usageLimitEntity.getLabel());
UsageLimitEntity usageLimitEntity = this.queryFactory.query(UsageLimitQuery.class).disableTracking().usageLimitTargetMetrics(metric).isActive(IsActive.Active).first();
if (usageLimitEntity != null) {
DefinitionEntity definition = this.xmlHandlingService.fromXmlSafe(DefinitionEntity.class, usageLimitEntity.getDefinition());
if (definition == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{usageLimitEntity.getId(), DefinitionEntity.class.getSimpleName()}, LocaleContextHolder.getLocale()));
Integer currentValue = this.accountingService.getCurrentMetricValue(metric, definition);
if (currentValue >= usageLimitEntity.getValue()) throw new MyValidationException(this.errors.getUsageLimitException().getCode(), usageLimitEntity.getLabel());
}
} catch (InvalidApplicationException e) {
log.error(e.getMessage(), e);
throw new MyApplicationException(e.getMessage());

View File

@ -46,9 +46,9 @@ import java.util.UUID;
@RestController
@RequestMapping(path = "api/usage-limit")
public class UsageFilterController {
public class UsageLimitController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageFilterController.class));
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageLimitController.class));
private final BuilderFactory builderFactory;
@ -62,7 +62,7 @@ public class UsageFilterController {
private final MessageSource messageSource;
public UsageFilterController(
public UsageLimitController(
BuilderFactory builderFactory,
AuditService auditService,
UsageLimitService usageLimitService,

View File

@ -2,5 +2,6 @@ accounting:
enabled: ${ACCOUNTING_ENABLED}
serviceId: ${SERVICE_ID}
subjectId: unknown
fromInstant: 1990-01-01T05:47:26.853Z
projectFields:
- sum

View File

@ -0,0 +1,4 @@
export enum UsageLimitPeriodicityRange {
Monthly = 0,
Yearly = 1
}

View File

@ -1,3 +1,4 @@
import { UsageLimitPeriodicityRange } from "@app/core/common/enum/usage-limit-periodicity-range";
import { UsageLimitTargetMetric } from "@app/core/common/enum/usage-limit-target-metric";
import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model";
@ -5,10 +6,24 @@ export interface UsageLimit extends BaseEntity {
label: string;
targetMetric: UsageLimitTargetMetric;
value: number;
definition: UsageLimitDefinition;
}
export interface UsageLimitDefinition {
hasPeriodicity: Boolean;
periodicityRange: UsageLimitPeriodicityRange
}
// persist
export interface UsageLimitPersist extends BaseEntityPersist {
label: string;
targetMetric: UsageLimitTargetMetric;
value: number;
definition: UsageLimitDefinitionPersist;
}
export interface UsageLimitDefinitionPersist {
hasPeriodicity: Boolean;
periodicityRange: UsageLimitPeriodicityRange
}

View File

@ -30,6 +30,7 @@ import { PlanBlueprintExtraFieldDataType } from '../../common/enum/plan-blueprin
import { PlanStatus } from '../../common/enum/plan-status';
import { ValidationType } from '../../common/enum/validation-type';
import { UsageLimitTargetMetric } from '@app/core/common/enum/usage-limit-target-metric';
import { UsageLimitPeriodicityRange } from '@app/core/common/enum/usage-limit-periodicity-range';
@Injectable()
export class EnumUtils {
@ -318,4 +319,12 @@ export class EnumUtils {
}
}
public toUsageLimitPeriodicityRangeString(value: UsageLimitPeriodicityRange): string {
switch (value) {
case UsageLimitPeriodicityRange.Monthly: return this.language.instant('TYPES.USAGE-LIMIT-PERIODICITY-RANGE.MONTHLY');
case UsageLimitPeriodicityRange.Yearly: return this.language.instant('TYPES.USAGE-LIMIT-PERIODICITY-RANGE.YEARLY');
default: return '';
}
}
}

View File

@ -52,6 +52,23 @@
<mat-error *ngIf="formGroup.get('value').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<div class="col-4">
<mat-checkbox [formControl]="formGroup.get('definition').get('hasPeriodicity')">
{{'USAGE-LIMIT-EDITOR.FIELDS.HAS-PERIODICITY' | translate}}
<mat-error *ngIf="formGroup.get('definition').get('hasPeriodicity').hasError('backendError')">{{formGroup.get('definition').get('hasPeriodicity').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('definition').get('hasPeriodicity').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-checkbox>
</div>
<div class="col-4" *ngIf="formGroup.get('definition').get('hasPeriodicity').value">
<mat-form-field class="w-100">
<mat-label>{{'USAGE-LIMIT-EDITOR.FIELDS.PERIODICITY-RANGE' | translate}}</mat-label>
<mat-select [formControl]="formGroup.get('definition').get('periodicityRange')">
<mat-option *ngFor="let range of periodicityRangeEnum" [value]="range">{{enumUtils.toUsageLimitPeriodicityRangeString(range)}}</mat-option>
</mat-select>
<mat-error *ngIf="formGroup.get('definition').get('periodicityRange').hasError('backendError')">{{formGroup.get('definition').get('periodicityRange').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('definition').get('periodicityRange').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
</mat-card-content>
</mat-card>

View File

@ -28,6 +28,7 @@ import { UsageLimitEditorService } from './usage-limit-editor.service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { Title } from '@angular/platform-browser';
import { UsageLimitTargetMetric } from '@app/core/common/enum/usage-limit-target-metric';
import { UsageLimitPeriodicityRange } from '@app/core/common/enum/usage-limit-periodicity-range';
@Component({
@ -43,6 +44,7 @@ export class UsageLimitEditorComponent extends BaseEditor<UsageLimitEditorModel,
formGroup: UntypedFormGroup = null;
showInactiveDetails = false;
targetMetricEnum = this.enumUtils.getEnumValues<UsageLimitTargetMetric>(UsageLimitTargetMetric);
periodicityRangeEnum = this.enumUtils.getEnumValues<UsageLimitPeriodicityRange>(UsageLimitPeriodicityRange);
protected get canDelete(): boolean {
return !this.isDeleted && !this.isNew && this.hasPermission(this.authService.permissionEnum.DeleteUsageLimit) && this.editorModel.belongsToCurrentTenant != false;;

View File

@ -1,6 +1,7 @@
import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { UsageLimitPeriodicityRange } from "@app/core/common/enum/usage-limit-periodicity-range";
import { UsageLimitTargetMetric } from "@app/core/common/enum/usage-limit-target-metric";
import { UsageLimit, UsageLimitPersist } from "@app/core/model/usage-limit/usage-limit";
import { UsageLimit, UsageLimitDefinition, UsageLimitDefinitionPersist, UsageLimitPersist } from "@app/core/model/usage-limit/usage-limit";
import { BaseEditorModel } from "@common/base/base-form-editor-model";
import { BackendErrorValidator } from "@common/forms/validation/custom-validator";
import { ValidationErrorModel } from "@common/forms/validation/error-model/validation-error-model";
@ -11,6 +12,7 @@ export class UsageLimitEditorModel extends BaseEditorModel implements UsageLimit
targetMetric: UsageLimitTargetMetric;
value: number;
permissions: string[];
definition: DefinitionEditorModel = new DefinitionEditorModel(this.validationErrorModel);
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
@ -23,6 +25,7 @@ export class UsageLimitEditorModel extends BaseEditorModel implements UsageLimit
this.label = item.label;
this.targetMetric = item.targetMetric;
this.value = item.value;
if (item.definition) this.definition = new DefinitionEditorModel(this.validationErrorModel).fromModel(item.definition);
}
return this;
}
@ -35,6 +38,10 @@ export class UsageLimitEditorModel extends BaseEditorModel implements UsageLimit
label: [{ value: this.label, disabled: disabled }, context.getValidation('label').validators],
targetMetric: [{ value: this.targetMetric, disabled: disabled }, context.getValidation('targetMetric').validators],
value: [{ value: this.value, disabled: disabled }, context.getValidation('value').validators],
definition: this.definition.buildForm({
rootPath: `definition.`,
disabled: disabled
}),
hash: [{ value: this.hash, disabled: disabled }, context.getValidation('hash').validators]
});
}
@ -52,3 +59,56 @@ export class UsageLimitEditorModel extends BaseEditorModel implements UsageLimit
return baseContext;
}
}
export class DefinitionEditorModel implements UsageLimitDefinitionPersist {
hasPeriodicity: Boolean = false;
periodicityRange: UsageLimitPeriodicityRange;
protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
constructor(
public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()
) { }
public fromModel(item: UsageLimitDefinition): DefinitionEditorModel {
if (item) {
this.hasPeriodicity = item.hasPeriodicity;
this.periodicityRange = item.periodicityRange;
}
return this;
}
buildForm(params?: {
context?: ValidationContext,
disabled?: boolean,
rootPath?: string
}): UntypedFormGroup {
let { context = null, disabled = false, rootPath } = params ?? {}
if (context == null) {
context = DefinitionEditorModel.createValidationContext({
validationErrorModel: this.validationErrorModel,
rootPath
});
}
return this.formBuilder.group({
hasPeriodicity: [{ value: this.hasPeriodicity, disabled: disabled }, context.getValidation('hasPeriodicity').validators],
periodicityRange: [{ value: this.periodicityRange, disabled: disabled }, context.getValidation('periodicityRange').validators],
});
}
static createValidationContext(params: {
rootPath?: string,
validationErrorModel: ValidationErrorModel
}): ValidationContext {
const { rootPath = '', validationErrorModel } = params;
const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'hasPeriodicity', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}hasPeriodicity`)] });
baseValidationArray.push({ key: 'periodicityRange', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}periodicityRange`)] });
baseContext.validation = baseValidationArray;
return baseContext;
}
}

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { UsageLimit } from '@app/core/model/usage-limit/usage-limit';
import { UsageLimit, UsageLimitDefinition } from '@app/core/model/usage-limit/usage-limit';
import { UsageLimitService } from '@app/core/services/usage-limit/usage.service';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { BaseEditorResolver } from '@common/base/base-editor.resolver';
@ -22,6 +22,8 @@ export class UsageLimitEditorResolver extends BaseEditorResolver {
nameof<UsageLimit>(x => x.label),
nameof<UsageLimit>(x => x.targetMetric),
nameof<UsageLimit>(x => x.value),
[nameof<UsageLimit>(x => x.definition), nameof<UsageLimitDefinition>(x => x.hasPeriodicity)].join('.'),
[nameof<UsageLimit>(x => x.definition), nameof<UsageLimitDefinition>(x => x.periodicityRange)].join('.'),
nameof<UsageLimit>(x => x.createdAt),
nameof<UsageLimit>(x => x.updatedAt),
nameof<UsageLimit>(x => x.hash),