From bd436e55b15cc0d257d93955c70747ceea400baa Mon Sep 17 00:00:00 2001 From: Thomas Georgios Giannos Date: Tue, 13 Feb 2024 12:48:55 +0200 Subject: [PATCH] Adding annotation controller with persist / delete / query / lookup on annotation service, implementing annotation query --- .../web/controllers/AnnotationController.java | 122 +++++++++++++++++- .../annotation/audit/AuditableAction.java | 46 ++++--- .../model/censorship/AnnotationCensor.java | 37 ++++++ .../annotation/query/AnnotationQuery.java | 97 +++++++++++++- .../query/lookup/AnnotationLookup.java | 70 +++++++++- .../annotation/AnnotationServiceImpl.java | 2 +- .../TenantConfigurationServiceImpl.java | 2 +- 7 files changed, 346 insertions(+), 30 deletions(-) create mode 100644 annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java diff --git a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java index adbd3cdef..88af9d2f1 100644 --- a/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java +++ b/annotation-service/annotation-web/src/main/java/gr/cite/annotation/web/controllers/AnnotationController.java @@ -1,13 +1,131 @@ package gr.cite.annotation.web.controllers; +import com.fasterxml.jackson.core.JsonProcessingException; +import gr.cite.annotation.audit.AuditableAction; +import gr.cite.annotation.data.AnnotationEntity; +import gr.cite.annotation.model.Annotation; +import gr.cite.annotation.model.builder.AnnotationBuilder; +import gr.cite.annotation.model.censorship.AnnotationCensor; +import gr.cite.annotation.model.persist.AnnotationPersist; +import gr.cite.annotation.query.AnnotationQuery; +import gr.cite.annotation.query.lookup.AnnotationLookup; +import gr.cite.annotation.service.annotation.AnnotationService; +import gr.cite.annotation.web.model.QueryResult; +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.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import javax.management.InvalidApplicationException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; @RestController @RequestMapping(path = "api/annotation", produces = MediaType.APPLICATION_JSON_VALUE) public class AnnotationController { + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationController.class)); + private final CensorFactory censorFactory; + + private final QueryFactory queryFactory; + + private final BuilderFactory builderFactory; + + private final AuditService auditService; + + private final MessageSource messageSource; + + private final AnnotationService annotationService; + + public AnnotationController(CensorFactory censorFactory, QueryFactory queryFactory, BuilderFactory builderFactory, AuditService auditService, MessageSource messageSource, AnnotationService annotationService) { + this.censorFactory = censorFactory; + this.queryFactory = queryFactory; + this.builderFactory = builderFactory; + this.auditService = auditService; + this.messageSource = messageSource; + this.annotationService = annotationService; + } + + @PostMapping("query") + public QueryResult query(@RequestBody AnnotationLookup lookup) { + logger.debug("querying {}", Annotation.class.getSimpleName()); + + this.censorFactory.censor(AnnotationCensor.class).censor(lookup.getProject(), null); + + AnnotationQuery query = lookup.enrich(this.queryFactory); + List data = query.collectAs(lookup.getProject()); + List models = this.builderFactory.builder(AnnotationBuilder.class).build(lookup.getProject(), data); + long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size(); + + this.auditService.track(AuditableAction.Annotation_Query, "lookup", lookup); + + return new QueryResult<>(models, count); + } + + @GetMapping("{id}") + public Annotation get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException { + logger.debug(new MapLogEntry("retrieving" + Annotation.class.getSimpleName()).And("id", id).And("fields", fieldSet)); + + this.censorFactory.censor(AnnotationCensor.class).censor(fieldSet, null); + + AnnotationQuery query = this.queryFactory.query(AnnotationQuery.class).ids(id); + Annotation model = this.builderFactory.builder(AnnotationBuilder.class).build(fieldSet, query.firstAs(fieldSet)); + if (model == null) + throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Annotation.class.getSimpleName()}, LocaleContextHolder.getLocale())); + + this.auditService.track(AuditableAction.Annotation_Lookup, Map.ofEntries( + new AbstractMap.SimpleEntry("id", id), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + + return model; + } + + @PostMapping("persist") + @Transactional + @ValidationFilterAnnotation(validator = AnnotationPersist.AnnotationPersistValidator.ValidatorName, argumentName = "model") + public Annotation persist(@RequestBody AnnotationPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException { + logger.debug(new MapLogEntry("persisting" + Annotation.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet)); + + this.censorFactory.censor(AnnotationCensor.class).censor(fieldSet, null); + + Annotation persisted = this.annotationService.persist(model, fieldSet); + + this.auditService.track(AuditableAction.Annotation_Persist, Map.ofEntries( + new AbstractMap.SimpleEntry("model", model), + new AbstractMap.SimpleEntry("fields", fieldSet) + )); + + return persisted; + } + + @DeleteMapping("{id}") + @Transactional + public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException { + logger.debug(new MapLogEntry("retrieving" + Annotation.class.getSimpleName()).And("id", id)); + + this.annotationService.deleteAndSave(id); + + this.auditService.track(AuditableAction.Annotation_Delete, "id", id); + } } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/audit/AuditableAction.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/audit/AuditableAction.java index 16d97dea3..6d65a7a31 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/audit/AuditableAction.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/audit/AuditableAction.java @@ -3,11 +3,11 @@ package gr.cite.annotation.audit; import gr.cite.tools.logging.EventId; public class AuditableAction { - public static final EventId Tenant_Available_Notifiers_Query = new EventId(2006, "Tenant_Available_Notifiers_Query"); +// public static final EventId Tenant_Available_Notifiers_Query = new EventId(2006, "Tenant_Available_Notifiers_Query"); public static final EventId Principal_Lookup = new EventId(6000, "Principal_Lookup"); public static final EventId Tenants_Lookup = new EventId(6001, "Tenants_Lookup"); - public static final EventId User_Available_Notifiers_Query = new EventId(10004, "User_Available_Notifiers_Query"); +// public static final EventId User_Available_Notifiers_Query = new EventId(10004, "User_Available_Notifiers_Query"); public static final EventId User_Query = new EventId(11000, "User_Query"); public static final EventId User_Lookup = new EventId(11001, "User_Lookup"); @@ -19,30 +19,36 @@ public class AuditableAction { public static final EventId Tenant_Persist = new EventId(12002, "Tenant_Persist"); public static final EventId Tenant_Delete = new EventId(12003, "Tenant_Delete"); - public static final EventId Notification_Query = new EventId(19000, "Notification_Query"); - public static final EventId Notification_Lookup = new EventId(19001, "Notification_Lookup"); - public static final EventId Notification_Persist = new EventId(19002, "Notification_Persist"); - public static final EventId Notification_Delete = new EventId(19003, "Notification_Delete"); +// public static final EventId Notification_Query = new EventId(19000, "Notification_Query"); +// public static final EventId Notification_Lookup = new EventId(19001, "Notification_Lookup"); +// public static final EventId Notification_Persist = new EventId(19002, "Notification_Persist"); +// public static final EventId Notification_Delete = new EventId(19003, "Notification_Delete"); - public static final EventId InApp_Notification_Query = new EventId(20000, "InApp_Notification_Query"); - public static final EventId InApp_Notification_Lookup = new EventId(20001, "InApp_Notification_Lookup"); - public static final EventId InApp_Notification_Persist = new EventId(20002, "InApp_Notification_Persist"); - public static final EventId InApp_Notification_Delete = new EventId(20003, "InApp_Notification_Delete"); - public static final EventId InApp_Notification_Read = new EventId(20003, "InApp_Notification_Read"); - public static final EventId InApp_Notification_Read_All = new EventId(20003, "InApp_Notification_Read_All"); +// public static final EventId InApp_Notification_Query = new EventId(20000, "InApp_Notification_Query"); +// public static final EventId InApp_Notification_Lookup = new EventId(20001, "InApp_Notification_Lookup"); +// public static final EventId InApp_Notification_Persist = new EventId(20002, "InApp_Notification_Persist"); +// public static final EventId InApp_Notification_Delete = new EventId(20003, "InApp_Notification_Delete"); +// public static final EventId InApp_Notification_Read = new EventId(20003, "InApp_Notification_Read"); +// public static final EventId InApp_Notification_Read_All = new EventId(20003, "InApp_Notification_Read_All"); public static final EventId Tenant_Configuration_Query = new EventId(21000, "Tenant_Configuration_Query"); public static final EventId Tenant_Configuration_Lookup = new EventId(21001, "Tenant_Configuration_Lookup"); public static final EventId Tenant_Configuration_Persist = new EventId(21002, "Tenant_Configuration_Persist"); public static final EventId Tenant_Configuration_Delete = new EventId(21003, "Tenant_Configuration_Delete"); - public static final EventId User_Notification_Preference_Query = new EventId(22000, "User_Notification_Preference_Query"); - public static final EventId User_Notification_Preference_Lookup = new EventId(22001, "User_Notification_Preference_Lookup"); - public static final EventId User_Notification_Preference_Persist = new EventId(22002, "User_Notification_Preference_Persist"); - public static final EventId User_Notification_Preference_Delete = new EventId(22003, "User_Notification_Preference_Delete"); +// public static final EventId User_Notification_Preference_Query = new EventId(22000, "User_Notification_Preference_Query"); +// public static final EventId User_Notification_Preference_Lookup = new EventId(22001, "User_Notification_Preference_Lookup"); +// public static final EventId User_Notification_Preference_Persist = new EventId(22002, "User_Notification_Preference_Persist"); +// public static final EventId User_Notification_Preference_Delete = new EventId(22003, "User_Notification_Preference_Delete"); +// +// public static final EventId Notification_Template_Query = new EventId(23000, "Notification_Template_Query"); +// public static final EventId Notification_Template_Lookup = new EventId(23001, "Notification_Template_Lookup"); +// public static final EventId Notification_Template_Persist = new EventId(23002, "Notification_Template_Persist"); +// public static final EventId Notification_Template_Delete = new EventId(23003, "Notification_Template_Delete"); + + public static final EventId Annotation_Query = new EventId(24000, "Annotation_Query"); + public static final EventId Annotation_Lookup = new EventId(24001, "Annotation_Lookup"); + public static final EventId Annotation_Persist = new EventId(24002, "Annotation_Persist"); + public static final EventId Annotation_Delete = new EventId(24003, "Annotation_Delete"); - public static final EventId Notification_Template_Query = new EventId(23000, "Notification_Template_Query"); - public static final EventId Notification_Template_Lookup = new EventId(23001, "Notification_Template_Lookup"); - public static final EventId Notification_Template_Persist = new EventId(23002, "Notification_Template_Persist"); - public static final EventId Notification_Template_Delete = new EventId(23003, "Notification_Template_Delete"); } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java new file mode 100644 index 000000000..538074557 --- /dev/null +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/model/censorship/AnnotationCensor.java @@ -0,0 +1,37 @@ +package gr.cite.annotation.model.censorship; + +import gr.cite.annotation.authorization.Permission; +import gr.cite.annotation.convention.ConventionService; +import gr.cite.commons.web.authz.service.AuthorizationService; +import gr.cite.tools.fieldset.FieldSet; +import gr.cite.tools.logging.DataLogEntry; +import gr.cite.tools.logging.LoggerService; +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 +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class AnnotationCensor extends BaseCensor{ + + private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationCensor.class)); + + protected final AuthorizationService authService; + + public AnnotationCensor(ConventionService conventionService, AuthorizationService authService) { + super(conventionService); + this.authService = authService; + } + + public void censor(FieldSet fields, UUID userId) { + logger.debug(new DataLogEntry("censoring fields", fields)); + if (fields == null || fields.isEmpty()) + return; + + this.authService.authorizeForce(Permission.BrowseAnnotation); + } + +} diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java index 37c29b2ce..7439e6d89 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/AnnotationQuery.java @@ -4,28 +4,35 @@ import gr.cite.annotation.authorization.AuthorizationFlags; import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.common.scope.user.UserScope; import gr.cite.annotation.data.AnnotationEntity; +import gr.cite.annotation.model.Annotation; 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.Tuple; +import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Predicate; 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(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class AnnotationQuery extends QueryBase { + private String like; + private Collection ids; private Collection excludedIds; private Collection isActives; + private Collection entityIds; + private EnumSet authorize = EnumSet.of(AuthorizationFlags.None); private final UserScope userScope; @@ -37,6 +44,11 @@ public class AnnotationQuery extends QueryBase { this.authService = authService; } + public AnnotationQuery like(String value) { + this.like = like; + return this; + } + public AnnotationQuery ids(UUID value) { this.ids = List.of(value); return this; @@ -82,28 +94,103 @@ public class AnnotationQuery extends QueryBase { return this; } + public AnnotationQuery entityIds(UUID value) { + this.entityIds = List.of(value); + return this; + } + + public AnnotationQuery entityIds(UUID... value) { + this.entityIds = Arrays.asList(value); + return this; + } + + public AnnotationQuery entityIds(Collection values) { + this.entityIds = values; + return this; + } + @Override protected Boolean isFalseQuery() { - return null; + return this.isEmpty(this.ids) || this.isEmpty(this.excludedIds) || this.isEmpty(this.isActives) || this.isEmpty(this.entityIds); } @Override protected Class entityClass() { - return null; + return AnnotationEntity.class; } @Override protected Predicate applyFilters(QueryContext queryContext) { - return null; + List predicates = new ArrayList<>(); + + if (this.like != null && !this.like.isBlank()) { + Predicate likePredicate = queryContext.CriteriaBuilder.or( + queryContext.CriteriaBuilder.like(queryContext.Root.get(AnnotationEntity._payload), this.like) + ); + predicates.add(likePredicate); + } + if (this.ids != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(AnnotationEntity._id)); + for (UUID item : this.ids) + inClause.value(item); + predicates.add(inClause); + } + if (this.excludedIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(AnnotationEntity._id)); + for (UUID item : this.ids) + inClause.value(item); + predicates.add(inClause.not()); + } + if (this.isActives != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(AnnotationEntity._isActive)); + for (IsActive item : this.isActives) + inClause.value(item); + predicates.add(inClause); + } + if (this.entityIds != null) { + CriteriaBuilder.In inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(AnnotationEntity._entityId)); + for (UUID item : this.entityIds) + inClause.value(item); + predicates.add(inClause); + } + + if (!predicates.isEmpty()) { + Predicate[] predicatesArray = predicates.toArray(new Predicate[0]); + return queryContext.CriteriaBuilder.and(predicatesArray); + } else { + return null; + } } @Override protected String fieldNameOf(FieldResolver item) { - return null; + if (item.match(Annotation._id)) + return AnnotationEntity._id; + else if (item.match(Annotation._entityId)) + return AnnotationEntity._entityId; + else if (item.match(Annotation._payload)) + return AnnotationEntity._payload; + else if (item.match(Annotation._createdAt)) + return AnnotationEntity._createdAt; + else if (item.match(Annotation._updatedAt)) + return AnnotationEntity._updatedAt; +// else if (item.match(Annotation._hash)) return AnnotationEntity._updatedAt; + else if (item.match(Annotation._isActive)) + return AnnotationEntity._isActive; + else + return null; } @Override protected AnnotationEntity convert(Tuple tuple, Set columns) { - return null; + AnnotationEntity item = new AnnotationEntity(); + item.setId(QueryBase.convertSafe(tuple, columns, AnnotationEntity._id, UUID.class)); + item.setEntityId(QueryBase.convertSafe(tuple, columns, AnnotationEntity._entityId, UUID.class)); + item.setPayload(QueryBase.convertSafe(tuple, columns, AnnotationEntity._payload, String.class)); + item.setCreatedAt(QueryBase.convertSafe(tuple, columns, AnnotationEntity._createdAt, Instant.class)); + item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, AnnotationEntity._updatedAt, Instant.class)); + item.setIsActive(QueryBase.convertSafe(tuple, columns, AnnotationEntity._isActive, IsActive.class)); + return item; } + } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/lookup/AnnotationLookup.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/lookup/AnnotationLookup.java index cefb9611a..192ef3788 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/query/lookup/AnnotationLookup.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/query/lookup/AnnotationLookup.java @@ -1,5 +1,73 @@ package gr.cite.annotation.query.lookup; -public class AnnotationLookup { +import gr.cite.annotation.common.enums.IsActive; +import gr.cite.annotation.query.AnnotationQuery; +import gr.cite.tools.data.query.Lookup; +import gr.cite.tools.data.query.QueryFactory; + +import java.util.List; +import java.util.UUID; + +public class AnnotationLookup extends Lookup { + + private String like; + + private List ids; + + private List excludedIds; + + private List isActive; + + private List entityIds; + + public String getLike() { + return like; + } + + public void setLike(String like) { + this.like = like; + } + + public List getIds() { + return ids; + } + + public void setIds(List ids) { + this.ids = ids; + } + + public List getExcludedIds() { + return excludedIds; + } + + public void setExcludedIds(List excludedIds) { + this.excludedIds = excludedIds; + } + + public List getIsActive() { + return isActive; + } + + public void setIsActive(List isActive) { + this.isActive = isActive; + } + + public AnnotationQuery enrich(QueryFactory queryFactory) { + AnnotationQuery query = queryFactory.query(AnnotationQuery.class); + if (this.like != null) + query.like(this.like); + if (this.ids != null) + query.ids(this.ids); + if (this.excludedIds != null) + query.excludedIds(this.excludedIds); + if (this.isActive != null) + query.isActive(this.isActive); + if (this.entityIds != null) + query.entityIds(this.entityIds); + + this.enrichCommon(query); + + return query; + } } diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java index 9a2bcf6cc..bb84e4f95 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/service/annotation/AnnotationServiceImpl.java @@ -57,7 +57,7 @@ public class AnnotationServiceImpl implements AnnotationService { @Override @Transactional public Annotation persist(AnnotationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException { - logger.debug(new MapLogEntry("persisting user").And("model", model).And("fields", fields)); + logger.debug(new MapLogEntry("persisting annotation").And("model", model).And("fields", fields)); this.authorizationService.authorizeForce(Permission.EditAnnotation); diff --git a/annotation-service/annotation/src/main/java/gr/cite/annotation/service/tenantconfiguration/TenantConfigurationServiceImpl.java b/annotation-service/annotation/src/main/java/gr/cite/annotation/service/tenantconfiguration/TenantConfigurationServiceImpl.java index acc101a8b..bae4060ac 100644 --- a/annotation-service/annotation/src/main/java/gr/cite/annotation/service/tenantconfiguration/TenantConfigurationServiceImpl.java +++ b/annotation-service/annotation/src/main/java/gr/cite/annotation/service/tenantconfiguration/TenantConfigurationServiceImpl.java @@ -158,7 +158,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic @Override public void deleteAndSave(UUID id) throws InvalidApplicationException { logger.debug("deleting tenant Configuration: {}", id); - this.authorizationService.authorizeForce(Permission.DeleteNotification); + this.authorizationService.authorizeForce(Permission.EditTenantConfiguration); this.deleterFactory.deleter(TenantConfigurationDeleter.class).deleteAndSaveByIds(List.of(id)); }