add UsageLimit backend stack

This commit is contained in:
amentis 2024-07-12 12:09:40 +03:00
parent 91103ee8ef
commit 09ed61ce6f
16 changed files with 1204 additions and 9 deletions

View File

@ -177,6 +177,11 @@ public class AuditableAction {
public static final EventId TenantConfiguration_LookupByType = new EventId(270004, "TenantConfiguration_LookupByType");
public static final EventId Annotation_Created_Notify = new EventId(280000, "Annotation_Created_Notify");
public static final EventId UsageLimit_Query = new EventId(290000, "UsageLimit_Query");
public static final EventId UsageLimit_Lookup = new EventId(290001, "UsageLimit_Lookup");
public static final EventId UsageLimit_Persist = new EventId(290002, "UsageLimit_Persist");
public static final EventId UsageLimit_Delete = new EventId(290003, "UsageLimit_Delete");

View File

@ -200,6 +200,11 @@ public final class Permission {
public static String EditPrefillingSource= "EditPrefillingSource";
public static String DeletePrefillingSource = "DeletePrefillingSource";
//UsageLimit
public static String BrowseUsageLimit = "BrowseUsageLimit";
public static String EditUsageLimit = "EditUsageLimit";
public static String DeleteUsageLimit = "DeleteUsageLimit";
//NotificationTemplate
public static String BrowseStatus = "BrowseStatus";
public static String EditStatus = "EditStatus";
@ -220,6 +225,7 @@ public final class Permission {
public static String ViewReferenceTypePage = "ViewReferenceTypePage";
public static String ViewReferencePaPlge = "ViewReferencePage";
public static String ViewEntityLockPage = "ViewEntityLockPage";
public static String ViewUsageLimitPage = "ViewUsageLimitPage";
public static String ViewDescriptionTemplatePage = "ViewDescriptionTemplatePage";
public static String ViewPlanBlueprintPage = "ViewPlanBlueprintPage";
public static String ViewPublicDescriptionPage = "ViewPublicDescriptionPage";

View File

@ -0,0 +1,44 @@
package org.opencdmp.commons.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import org.opencdmp.data.converters.enums.DatabaseEnum;
import java.util.Map;
public enum UsageLimitMetricValue implements DatabaseEnum<String> {
USER_COUNT(MetricValues.UserCount),
PLAN_COUNT(MetricValues.PlanCount),
BLUEPRINT_COUNT(MetricValues.BlueprintCount),
DESCRIPTION_COUNT(MetricValues.DescriptionCount),
DESCRIPTION_TEMPLATE_COUNT(MetricValues.DescriptionTemplateCount),
DESCRIPTION_TEMPLATE_TYPE_COUNT(MetricValues.DescriptionTemplateTypeCount),
PREFILLING_SOURCES_COUNT(MetricValues.PrefillingSourcesCount),
REFERENCE_TYPE_COUNT(MetricValues.ReferenceTypeCount);
private final String value;
public static class MetricValues {
public static final String UserCount = "user_count";
public static final String PlanCount = "plan_count";
public static final String BlueprintCount = "blueprint_count";
public static final String DescriptionCount = "description_count";
public static final String DescriptionTemplateCount = "description_template_count";
public static final String DescriptionTemplateTypeCount = "description_template_type_count";
public static final String PrefillingSourcesCount = "prefilling_sources_count";
public static final String ReferenceTypeCount = "reference_type_count";
}
UsageLimitMetricValue(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return this.value;
}
private static final Map<String, UsageLimitMetricValue> map = EnumUtils.getEnumValueMap(UsageLimitMetricValue.class);
public static UsageLimitMetricValue of(String i) {
return map.get(i);
}
}

View File

@ -0,0 +1,111 @@
package org.opencdmp.data;
import jakarta.persistence.*;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitMetricValue;
import org.opencdmp.data.converters.enums.IsActiveConverter;
import org.opencdmp.data.tenant.TenantScopedBaseEntity;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "\"UsageLimit\"")
public class UsageLimitEntity extends TenantScopedBaseEntity {
@Id
@Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false)
private UUID id;
public static final String _id = "id";
@Column(name = "label", length = _labelLength, nullable = false)
private String label;
public static final String _label = "label";
public static final int _labelLength = 250;
@Column(name = "metric_value", nullable = false)
private UsageLimitMetricValue metricValue;
public static final String _metricValue = "metricValue";
@Column(name = "value", nullable = false)
private Long value;
public static final String _value = "value";
@Column(name = "is_active", nullable = false)
@Convert(converter = IsActiveConverter.class)
private IsActive isActive;
public static final String _isActive = "isActive";
@Column(name = "created_at", nullable = false)
private Instant createdAt;
public static final String _createdAt = "createdAt";
@Column(name = "updated_at", nullable = false)
private Instant updatedAt;
public static final String _updatedAt = "updatedAt";
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public UsageLimitMetricValue getMetricValue() {
return metricValue;
}
public void setMetricValue(UsageLimitMetricValue metricValue) {
this.metricValue = metricValue;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
public IsActive getIsActive() {
return isActive;
}
public void setIsActive(IsActive isActive) {
this.isActive = isActive;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@ -0,0 +1,109 @@
package org.opencdmp.model;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitMetricValue;
import java.time.Instant;
import java.util.UUID;
public class UsageLimit {
private UUID id;
public static final String _id = "id";
private String label;
public static final String _label = "label";
private UsageLimitMetricValue metricValue;
public static final String _metricValue = "metricValue";
private Long value;
public static final String _value = "value";
private IsActive isActive;
public static final String _isActive = "isActive";
private Instant createdAt;
public static final String _createdAt = "createdAt";
private Instant updatedAt;
public static final String _updatedAt = "updatedAt";
private String hash;
public static final String _hash = "hash";
private Boolean belongsToCurrentTenant;
public static final String _belongsToCurrentTenant = "belongsToCurrentTenant";
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public UsageLimitMetricValue getMetricValue() {
return metricValue;
}
public void setMetricValue(UsageLimitMetricValue metricValue) {
this.metricValue = metricValue;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
public IsActive getIsActive() {
return isActive;
}
public void setIsActive(IsActive isActive) {
this.isActive = isActive;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
public Boolean getBelongsToCurrentTenant() {
return belongsToCurrentTenant;
}
public void setBelongsToCurrentTenant(Boolean belongsToCurrentTenant) {
this.belongsToCurrentTenant = belongsToCurrentTenant;
}
}

View File

@ -0,0 +1,74 @@
package org.opencdmp.model.builder;
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.scope.tenant.TenantScope;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.UsageLimitEntity;
import org.opencdmp.model.UsageLimit;
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
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UsageLimitBuilder extends BaseBuilder<UsageLimit, UsageLimitEntity> {
private final TenantScope tenantScope;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public UsageLimitBuilder(
ConventionService conventionService,
TenantScope tenantScope) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(UsageLimitBuilder.class)));
this.tenantScope = tenantScope;
}
public UsageLimitBuilder authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
@Override
public List<UsageLimit> build(FieldSet fields, List<UsageLimitEntity> 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<UsageLimit> models = new ArrayList<>();
for (UsageLimitEntity d : data) {
UsageLimit m = new UsageLimit();
if (fields.hasField(this.asIndexer(UsageLimit._id)))
m.setId(d.getId());
if (fields.hasField(this.asIndexer(UsageLimit._label)))
m.setLabel(d.getLabel());
if (fields.hasField(this.asIndexer(UsageLimit._metricValue)))
m.setMetricValue(d.getMetricValue());
if (fields.hasField(this.asIndexer(UsageLimit._value)))
m.setValue(d.getValue());
if (fields.hasField(this.asIndexer(UsageLimit._createdAt)))
m.setCreatedAt(d.getCreatedAt());
if (fields.hasField(this.asIndexer(UsageLimit._updatedAt)))
m.setUpdatedAt(d.getUpdatedAt());
if (fields.hasField(this.asIndexer(UsageLimit._isActive)))
m.setIsActive(d.getIsActive());
if (fields.hasField(this.asIndexer(UsageLimit._hash)))
m.setHash(this.hashValue(d.getUpdatedAt()));
if (fields.hasField(this.asIndexer(UsageLimit._belongsToCurrentTenant))) m.setBelongsToCurrentTenant(this.getBelongsToCurrentTenant(d, this.tenantScope));
models.add(m);
}
this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0));
return models;
}
}

View File

@ -0,0 +1,40 @@
package org.opencdmp.model.censorship;
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.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UsageLimitsCensor extends BaseCensor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageLimitsCensor.class));
protected final AuthorizationService authService;
protected final CensorFactory censorFactory;
public UsageLimitsCensor(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

@ -0,0 +1,79 @@
package org.opencdmp.model.deleter;
import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.data.UsageLimitEntity;
import org.opencdmp.query.UsageLimitQuery;
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 javax.management.InvalidApplicationException;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UsageLimitDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageLimitDeleter.class));
private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory;
protected final DeleterFactory deleterFactory;
@Autowired
public UsageLimitDeleter(
TenantEntityManager entityManager,
QueryFactory queryFactory,
DeleterFactory deleterFactory
) {
this.entityManager = entityManager;
this.queryFactory = queryFactory;
this.deleterFactory = deleterFactory;
}
public void deleteAndSaveByIds(List<UUID> ids) throws InvalidApplicationException {
logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(List::size).orElse(0)).And("ids", ids));
List<UsageLimitEntity> data = this.queryFactory.query(UsageLimitQuery.class).ids(ids).collect();
logger.trace("retrieved {} items", Optional.ofNullable(data).map(List::size).orElse(0));
this.deleteAndSave(data);
}
public void deleteAndSave(List<UsageLimitEntity> data) throws InvalidApplicationException {
logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0));
this.delete(data);
logger.trace("saving changes");
this.entityManager.flush();
logger.trace("changes saved");
}
public void delete(List<UsageLimitEntity> data) throws InvalidApplicationException {
logger.debug("will delete {} items", Optional.ofNullable(data).map(List::size).orElse(0));
if (data == null || data.isEmpty())
return;
Instant now = Instant.now();
for (UsageLimitEntity item : data) {
logger.trace("deleting item {}", item.getId());
item.setIsActive(IsActive.Inactive);
item.setUpdatedAt(now);
logger.trace("updating item");
this.entityManager.merge(item);
logger.trace("updated item");
}
}
}

View File

@ -0,0 +1,122 @@
package org.opencdmp.model.persist;
import gr.cite.tools.validation.specification.Specification;
import org.opencdmp.commons.enums.UsageLimitMetricValue;
import org.opencdmp.commons.validation.BaseValidator;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.UsageLimitEntity;
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;
import java.util.UUID;
public class UsageLimitPersist {
private UUID id;
public static final String _id = "id";
private String label;
public static final String _label = "label";
private UsageLimitMetricValue metricValue;;
public static final String _metricValue = "metricValue";
private Long value;
public static final String _value = "value";
private String hash;
public static final String _hash = "hash";
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public String getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label = label;
}
public UsageLimitMetricValue getMetricValue() {
return metricValue;
}
public void setMetricValue(UsageLimitMetricValue metricValue) {
this.metricValue = metricValue;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
public String getHash() {
return this.hash;
}
public void setHash(String hash) {
this.hash = hash;
}
@Component(UsageLimitPersistValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class UsageLimitPersistValidator extends BaseValidator<UsageLimitPersist> {
public static final String ValidatorName = "UsageLimitPersistValidator";
private final MessageSource messageSource;
protected UsageLimitPersistValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
super(conventionService, errors);
this.messageSource = messageSource;
}
@Override
protected Class<UsageLimitPersist> modelClass() {
return UsageLimitPersist.class;
}
@Override
protected List<Specification> specifications(UsageLimitPersist item) {
return Arrays.asList(
this.spec()
.iff(() -> this.isValidGuid(item.getId()))
.must(() -> this.isValidHash(item.getHash()))
.failOn(UsageLimitPersist._hash).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._hash}, LocaleContextHolder.getLocale())),
this.spec()
.iff(() -> !this.isValidGuid(item.getId()))
.must(() -> !this.isValidHash(item.getHash()))
.failOn(UsageLimitPersist._hash).failWith(this.messageSource.getMessage("Validation_OverPosting", new Object[]{}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isEmpty(item.getLabel()))
.failOn(UsageLimitPersist._label).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._label}, LocaleContextHolder.getLocale())),
this.spec()
.iff(() -> !this.isEmpty(item.getLabel()))
.must(() -> this.lessEqualLength(item.getLabel(), UsageLimitEntity._labelLength))
.failOn(UsageLimitPersist._label).failWith(this.messageSource.getMessage("Validation_MaxLength", new Object[]{UsageLimitPersist._label}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isNull(item.getMetricValue()))
.failOn(UsageLimitPersist._metricValue).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._metricValue}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isNull(item.getValue()))
.failOn(UsageLimitPersist._value).failWith(this.messageSource.getMessage("Validation_Required", new Object[]{UsageLimitPersist._value}, LocaleContextHolder.getLocale()))
);
}
}
}

View File

@ -0,0 +1,227 @@
package org.opencdmp.query;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.query.FieldResolver;
import gr.cite.tools.data.query.QueryBase;
import gr.cite.tools.data.query.QueryContext;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitMetricValue;
import org.opencdmp.commons.scope.user.UserScope;
import org.opencdmp.data.*;
import org.opencdmp.model.UsageLimit;
import org.opencdmp.query.utils.QueryUtilsService;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.*;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class UsageLimitQuery extends QueryBase<UsageLimitEntity> {
private String like;
private Collection<UUID> ids;
private Collection<IsActive> isActives;
private Collection<UsageLimitMetricValue> usageLimitMetricValues;
private Collection<UUID> excludedIds;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
public UsageLimitQuery like(String value) {
this.like = value;
return this;
}
public UsageLimitQuery ids(UUID value) {
this.ids = List.of(value);
return this;
}
public UsageLimitQuery ids(UUID... value) {
this.ids = Arrays.asList(value);
return this;
}
public UsageLimitQuery ids(Collection<UUID> values) {
this.ids = values;
return this;
}
public UsageLimitQuery isActive(IsActive value) {
this.isActives = List.of(value);
return this;
}
public UsageLimitQuery isActive(IsActive... value) {
this.isActives = Arrays.asList(value);
return this;
}
public UsageLimitQuery isActive(Collection<IsActive> values) {
this.isActives = values;
return this;
}
public UsageLimitQuery excludedIds(Collection<UUID> values) {
this.excludedIds = values;
return this;
}
public UsageLimitQuery excludedIds(UUID value) {
this.excludedIds = List.of(value);
return this;
}
public UsageLimitQuery excludedIds(UUID... value) {
this.excludedIds = Arrays.asList(value);
return this;
}
public UsageLimitQuery usageLimitMetricValues(UsageLimitMetricValue value) {
this.usageLimitMetricValues = List.of(value);
return this;
}
public UsageLimitQuery usageLimitMetricValues(UsageLimitMetricValue... value) {
this.usageLimitMetricValues = Arrays.asList(value);
return this;
}
public UsageLimitQuery usageLimitMetricValues(Collection<UsageLimitMetricValue> values) {
this.usageLimitMetricValues = values;
return this;
}
public UsageLimitQuery authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
public UsageLimitQuery enableTracking() {
this.noTracking = false;
return this;
}
public UsageLimitQuery disableTracking() {
this.noTracking = true;
return this;
}
private final UserScope userScope;
private final AuthorizationService authService;
private final QueryUtilsService queryUtilsService;
private final TenantEntityManager tenantEntityManager;
public UsageLimitQuery(
UserScope userScope, AuthorizationService authService, QueryUtilsService queryUtilsService, TenantEntityManager tenantEntityManager) {
this.userScope = userScope;
this.authService = authService;
this.queryUtilsService = queryUtilsService;
this.tenantEntityManager = tenantEntityManager;
}
@Override
protected EntityManager entityManager(){
return this.tenantEntityManager.getEntityManager();
}
@Override
protected Class<UsageLimitEntity> entityClass() {
return UsageLimitEntity.class;
}
@Override
protected Boolean isFalseQuery() {
return this.isEmpty(this.ids) || this.isEmpty(this.isActives) || this.isEmpty(this.excludedIds) || this.isEmpty(this.usageLimitMetricValues);
}
@Override
protected <X, Y> Predicate applyFilters(QueryContext<X, Y> queryContext) {
List<Predicate> predicates = new ArrayList<>();
if (this.ids != null) {
CriteriaBuilder.In<UUID> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UsageLimitEntity._id));
for (UUID item : this.ids)
inClause.value(item);
predicates.add(inClause);
}
if (this.like != null && !this.like.isBlank()) {
predicates.add(this.queryUtilsService.ilike(queryContext.CriteriaBuilder, queryContext.Root.get(UsageLimitEntity._label), this.like));
}
if (this.isActives != null) {
CriteriaBuilder.In<IsActive> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UsageLimitEntity._isActive));
for (IsActive item : this.isActives)
inClause.value(item);
predicates.add(inClause);
}
if (this.usageLimitMetricValues != null) {
CriteriaBuilder.In<UsageLimitMetricValue> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(UsageLimitEntity._metricValue));
for (UsageLimitMetricValue item : this.usageLimitMetricValues)
inClause.value(item);
predicates.add(inClause);
}
if (this.excludedIds != null) {
CriteriaBuilder.In<UUID> notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(ReferenceEntity._id));
for (UUID item : this.excludedIds)
notInClause.value(item);
predicates.add(notInClause.not());
}
if (!predicates.isEmpty()) {
Predicate[] predicatesArray = predicates.toArray(new Predicate[0]);
return queryContext.CriteriaBuilder.and(predicatesArray);
} else {
return null;
}
}
@Override
protected UsageLimitEntity convert(Tuple tuple, Set<String> columns) {
UsageLimitEntity item = new UsageLimitEntity();
item.setId(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._id, UUID.class));
item.setTenantId(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._tenantId, UUID.class));
item.setLabel(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._label, String.class));
item.setMetricValue(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._metricValue, UsageLimitMetricValue.class));
item.setValue(QueryBase.convertSafe(tuple, columns, UsageLimitEntity._value, Long.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));
return item;
}
@Override
protected String fieldNameOf(FieldResolver item) {
if (item.match(UsageLimit._id))
return UsageLimitEntity._id;
else if (item.match(UsageLimit._label))
return UsageLimitEntity._label;
else if (item.match(UsageLimit._metricValue))
return UsageLimitEntity._metricValue;
else if (item.match(UsageLimit._value))
return UsageLimitEntity._value;
else if (item.match(UsageLimit._createdAt))
return UsageLimitEntity._createdAt;
else if (item.match(UsageLimit._updatedAt))
return UsageLimitEntity._updatedAt;
else if (item.match(UsageLimit._hash))
return UsageLimitEntity._updatedAt;
else if (item.match(UsageLimit._isActive))
return UsageLimitEntity._isActive;
else if (item.prefix(UsageLimit._belongsToCurrentTenant))
return UsageLimitEntity._tenantId;
else
return null;
}
}

View File

@ -0,0 +1,70 @@
package org.opencdmp.query.lookup;
import gr.cite.tools.data.query.Lookup;
import gr.cite.tools.data.query.QueryFactory;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitMetricValue;
import org.opencdmp.query.UsageLimitQuery;
import java.util.List;
import java.util.UUID;
public class UsageLimitLookup extends Lookup {
private String like;
private List<IsActive> isActive;
private List<UUID> ids;
private List<UsageLimitMetricValue> usageLimitMetricValues;
private List<UUID> excludedIds;
public String getLike() {
return like;
}
public void setLike(String like) {
this.like = like;
}
public List<IsActive> getIsActive() {
return isActive;
}
public void setIsActive(List<IsActive> isActive) {
this.isActive = isActive;
}
public List<UUID> getIds() {
return ids;
}
public void setIds(List<UUID> ids) { this.ids = ids; }
public List<UsageLimitMetricValue> getUsageLimitsMetricValues() {
return usageLimitMetricValues;
}
public void setUsageLimitsMetricValues(List<UsageLimitMetricValue> usageLimitMetricValues) {
this.usageLimitMetricValues = usageLimitMetricValues;
}
public List<UUID> getExcludedIds() {
return excludedIds;
}
public void setExcludedIds(List<UUID> excludeIds) {
this.excludedIds = excludeIds;
}
public UsageLimitQuery enrich(QueryFactory queryFactory) {
UsageLimitQuery query = queryFactory.query(UsageLimitQuery.class);
if (this.like != null) query.like(this.like);
if (this.isActive != null) query.isActive(this.isActive);
if (this.ids != null) query.ids(this.ids);
if (this.usageLimitMetricValues != null) query.usageLimitMetricValues(this.usageLimitMetricValues);
if (this.excludedIds != null) query.excludedIds(this.excludedIds);
this.enrichCommon(query);
return query;
}
}

View File

@ -1,11 +1,23 @@
package org.opencdmp.service.usagelimit;
public class UsageLimitService {
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.FieldSet;
import org.opencdmp.commons.enums.UsageLimitMetricValue;
import org.opencdmp.model.UsageLimit;
import org.opencdmp.model.persist.UsageLimitPersist;
private void checkIncrease(String metric) {
//Get/Calculate current metric value from accountingService
//Find metric value from db
// compare these two and throw UsageLimitException when current > metric value
}
import javax.management.InvalidApplicationException;
import java.util.UUID;
public interface UsageLimitService {
UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException;
void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException;
void checkIncrease(UsageLimitMetricValue metric);
}

View File

@ -0,0 +1,126 @@
package org.opencdmp.service.usagelimit;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.deleter.DeleterFactory;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.authorization.Permission;
import org.opencdmp.commons.enums.IsActive;
import org.opencdmp.commons.enums.UsageLimitMetricValue;
import org.opencdmp.commons.scope.user.UserScope;
import org.opencdmp.convention.ConventionService;
import org.opencdmp.data.TenantEntityManager;
import org.opencdmp.data.UsageLimitEntity;
import org.opencdmp.errorcode.ErrorThesaurusProperties;
import org.opencdmp.model.Tag;
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.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import javax.management.InvalidApplicationException;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
@Service
public class UsageLimitServiceImpl implements UsageLimitService {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageLimitServiceImpl.class));
private final TenantEntityManager entityManager;
private final AuthorizationService authorizationService;
private final DeleterFactory deleterFactory;
private final BuilderFactory builderFactory;
private final ConventionService conventionService;
private final ErrorThesaurusProperties errors;
private final MessageSource messageSource;
private final UserScope userScope;
@Autowired
public UsageLimitServiceImpl(
TenantEntityManager entityManager,
AuthorizationService authorizationService,
DeleterFactory deleterFactory,
BuilderFactory builderFactory,
ConventionService conventionService,
ErrorThesaurusProperties errors,
MessageSource messageSource,
UserScope userScope) {
this.entityManager = entityManager;
this.authorizationService = authorizationService;
this.deleterFactory = deleterFactory;
this.builderFactory = builderFactory;
this.conventionService = conventionService;
this.errors = errors;
this.messageSource = messageSource;
this.userScope = userScope;
}
public UsageLimit persist(UsageLimitPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException, InvalidApplicationException {
logger.debug(new MapLogEntry("persisting data tag").And("model", model).And("fields", fields));
this.authorizationService.authorizeForce(Permission.EditUsageLimit);
Boolean isUpdate = this.conventionService.isValidGuid(model.getId());
UsageLimitEntity data;
if (isUpdate) {
data = this.entityManager.find(UsageLimitEntity.class, model.getId());
if (data == null) throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{model.getId(), Tag.class.getSimpleName()}, LocaleContextHolder.getLocale()));
if (!this.conventionService.hashValue(data.getUpdatedAt()).equals(model.getHash())) throw new MyValidationException(this.errors.getHashConflict().getCode(), this.errors.getHashConflict().getMessage());
} else {
data = new UsageLimitEntity();
data.setId(UUID.randomUUID());
data.setIsActive(IsActive.Active);
data.setCreatedAt(Instant.now());
}
data.setLabel(model.getLabel());
data.setMetricValue(model.getMetricValue());
data.setValue(model.getValue());
data.setUpdatedAt(Instant.now());
if (isUpdate)
this.entityManager.merge(data);
else
this.entityManager.persist(data);
this.entityManager.flush();
return this.builderFactory.builder(UsageLimitBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(BaseFieldSet.build(fields, UsageLimit._id), data);
}
public void deleteAndSave(UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug("deleting UsageLimit: {}", id);
this.authorizationService.authorizeForce(Permission.DeleteUsageLimit);
this.deleterFactory.deleter(UsageLimitDeleter.class).deleteAndSaveByIds(List.of(id));
}
public void checkIncrease(UsageLimitMetricValue metric) {
//TODO
}
}

View File

@ -0,0 +1,142 @@
package org.opencdmp.controllers;
import com.fasterxml.jackson.core.JsonProcessingException;
import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.censor.CensorFactory;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import gr.cite.tools.validation.ValidationFilterAnnotation;
import jakarta.transaction.Transactional;
import jakarta.xml.bind.JAXBException;
import org.opencdmp.audit.AuditableAction;
import org.opencdmp.authorization.AuthorizationFlags;
import org.opencdmp.data.UsageLimitEntity;
import org.opencdmp.model.UsageLimit;
import org.opencdmp.model.builder.UsageLimitBuilder;
import org.opencdmp.model.censorship.UsageLimitsCensor;
import org.opencdmp.model.persist.UsageLimitPersist;
import org.opencdmp.model.result.QueryResult;
import org.opencdmp.query.UsageLimitQuery;
import org.opencdmp.query.lookup.UsageLimitLookup;
import org.opencdmp.service.usagelimit.UsageLimitService;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping(path = "api/usage-limit")
public class UsageFilterController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UsageFilterController.class));
private final BuilderFactory builderFactory;
private final AuditService auditService;
private final UsageLimitService usageLimitService;
private final CensorFactory censorFactory;
private final QueryFactory queryFactory;
private final MessageSource messageSource;
public UsageFilterController(
BuilderFactory builderFactory,
AuditService auditService,
UsageLimitService usageLimitService,
CensorFactory censorFactory,
QueryFactory queryFactory,
MessageSource messageSource) {
this.builderFactory = builderFactory;
this.auditService = auditService;
this.usageLimitService = usageLimitService;
this.censorFactory = censorFactory;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
}
@PostMapping("query")
public QueryResult<UsageLimit> query(@RequestBody UsageLimitLookup lookup) throws MyApplicationException, MyForbiddenException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidApplicationException {
logger.debug("querying {}", UsageLimit.class.getSimpleName());
this.censorFactory.censor(UsageLimitsCensor.class).censor(lookup.getProject());
UsageLimitQuery query = lookup.enrich(this.queryFactory).authorize(AuthorizationFlags.AllExceptPublic);
List<UsageLimitEntity> data = query.collectAs(lookup.getProject());
List<UsageLimit> models = this.builderFactory.builder(UsageLimitBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(lookup.getProject(), data);
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
this.auditService.track(AuditableAction.UsageLimit_Query, "lookup", lookup);
return new QueryResult<>(models, count);
}
@GetMapping("{id}")
public UsageLimit get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidApplicationException {
logger.debug(new MapLogEntry("retrieving" + UsageLimit.class.getSimpleName()).And("id", id).And("fields", fieldSet));
this.censorFactory.censor(UsageLimitsCensor.class).censor(fieldSet);
UsageLimitQuery query = this.queryFactory.query(UsageLimitQuery.class).disableTracking().authorize(AuthorizationFlags.AllExceptPublic).ids(id);
UsageLimit model = this.builderFactory.builder(UsageLimitBuilder.class).authorize(AuthorizationFlags.AllExceptPublic).build(fieldSet, query.firstAs(fieldSet));
if (model == null)
throw new MyNotFoundException(this.messageSource.getMessage("General_ItemNotFound", new Object[]{id, UsageLimit.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.auditService.track(AuditableAction.UsageLimit_Lookup, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", id),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
return model;
}
@PostMapping("persist")
@Transactional
@ValidationFilterAnnotation(validator = UsageLimitPersist.UsageLimitPersistValidator.ValidatorName, argumentName = "model")
public UsageLimit persist(@RequestBody UsageLimitPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("persisting" + UsageLimit.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
this.censorFactory.censor(UsageLimitsCensor.class).censor(fieldSet);
UsageLimit persisted = this.usageLimitService.persist(model, fieldSet);
this.auditService.track(AuditableAction.UsageLimit_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
return persisted;
}
@DeleteMapping("{id}")
@Transactional
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug(new MapLogEntry("retrieving" + UsageLimit.class.getSimpleName()).And("id", id));
this.usageLimitService.deleteAndSave(id);
this.auditService.track(AuditableAction.UsageLimit_Delete, "id", id);
}
}

View File

@ -1063,6 +1063,28 @@ permissions:
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Tenant Permissions
BrowseUsageLimit:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditUsageLimit:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteUsageLimit:
roles:
- Admin
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Status
BrowseStatus:
roles:
@ -1179,6 +1201,12 @@ permissions:
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewUsageLimitPage:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewDescriptionTemplatePage:
roles:
- Admin

View File

@ -4,7 +4,7 @@ BEGIN
PERFORM * FROM "DBVersion" WHERE version = this_version;
IF FOUND THEN RETURN; END IF;
CREATE TABLE public."UsageLimits"
CREATE TABLE public."UsageLimit"
(
id uuid NOT NULL,
label character varying NOT NULL,
@ -15,13 +15,13 @@ BEGIN
is_active smallint NOT NULL,
tenant uuid,
PRIMARY KEY (id),
CONSTRAINT "UsageLimits_tenant_fkey" FOREIGN KEY (tenant)
CONSTRAINT "UsageLimit_tenant_fkey" FOREIGN KEY (tenant)
REFERENCES public."Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
);
INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.01.067', '2024-11-07 12:00:00.000000+02', now(), 'Add UsageLimits table.');
INSERT INTO public."DBVersion" VALUES ('DMPDB', '00.01.067', '2024-11-07 12:00:00.000000+02', now(), 'Add UsageLimit table.');
END$$;