Adding annotation controller with persist / delete / query / lookup on annotation service, implementing annotation query

This commit is contained in:
Thomas Georgios Giannos 2024-02-13 12:48:55 +02:00
parent 40316781a2
commit bd436e55b1
7 changed files with 346 additions and 30 deletions

View File

@ -1,13 +1,131 @@
package gr.cite.annotation.web.controllers; 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.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;
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 @RestController
@RequestMapping(path = "api/annotation", produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(path = "api/annotation", produces = MediaType.APPLICATION_JSON_VALUE)
public class AnnotationController { 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<Annotation> 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<AnnotationEntity> data = query.collectAs(lookup.getProject());
List<Annotation> 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<String, Object>("id", id),
new AbstractMap.SimpleEntry<String, Object>("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<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" + Annotation.class.getSimpleName()).And("id", id));
this.annotationService.deleteAndSave(id);
this.auditService.track(AuditableAction.Annotation_Delete, "id", id);
}
} }

View File

@ -3,11 +3,11 @@ package gr.cite.annotation.audit;
import gr.cite.tools.logging.EventId; import gr.cite.tools.logging.EventId;
public class AuditableAction { 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 Principal_Lookup = new EventId(6000, "Principal_Lookup");
public static final EventId Tenants_Lookup = new EventId(6001, "Tenants_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_Query = new EventId(11000, "User_Query");
public static final EventId User_Lookup = new EventId(11001, "User_Lookup"); 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_Persist = new EventId(12002, "Tenant_Persist");
public static final EventId Tenant_Delete = new EventId(12003, "Tenant_Delete"); 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_Query = new EventId(19000, "Notification_Query");
public static final EventId Notification_Lookup = new EventId(19001, "Notification_Lookup"); // 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_Persist = new EventId(19002, "Notification_Persist");
public static final EventId Notification_Delete = new EventId(19003, "Notification_Delete"); // 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_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_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_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_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 = 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_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_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_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_Persist = new EventId(21002, "Tenant_Configuration_Persist");
public static final EventId Tenant_Configuration_Delete = new EventId(21003, "Tenant_Configuration_Delete"); 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_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_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_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_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");
} }

View File

@ -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);
}
}

View File

@ -4,28 +4,35 @@ import gr.cite.annotation.authorization.AuthorizationFlags;
import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.common.enums.IsActive;
import gr.cite.annotation.common.scope.user.UserScope; import gr.cite.annotation.common.scope.user.UserScope;
import gr.cite.annotation.data.AnnotationEntity; import gr.cite.annotation.data.AnnotationEntity;
import gr.cite.annotation.model.Annotation;
import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.tools.data.query.FieldResolver; import gr.cite.tools.data.query.FieldResolver;
import gr.cite.tools.data.query.QueryBase; import gr.cite.tools.data.query.QueryBase;
import gr.cite.tools.data.query.QueryContext; import gr.cite.tools.data.query.QueryContext;
import jakarta.persistence.Tuple; import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.*; import java.util.*;
@Component @Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AnnotationQuery extends QueryBase<AnnotationEntity> { public class AnnotationQuery extends QueryBase<AnnotationEntity> {
private String like;
private Collection<UUID> ids; private Collection<UUID> ids;
private Collection<UUID> excludedIds; private Collection<UUID> excludedIds;
private Collection<IsActive> isActives; private Collection<IsActive> isActives;
private Collection<UUID> entityIds;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None); private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
private final UserScope userScope; private final UserScope userScope;
@ -37,6 +44,11 @@ public class AnnotationQuery extends QueryBase<AnnotationEntity> {
this.authService = authService; this.authService = authService;
} }
public AnnotationQuery like(String value) {
this.like = like;
return this;
}
public AnnotationQuery ids(UUID value) { public AnnotationQuery ids(UUID value) {
this.ids = List.of(value); this.ids = List.of(value);
return this; return this;
@ -82,28 +94,103 @@ public class AnnotationQuery extends QueryBase<AnnotationEntity> {
return this; 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<UUID> values) {
this.entityIds = values;
return this;
}
@Override @Override
protected Boolean isFalseQuery() { protected Boolean isFalseQuery() {
return null; return this.isEmpty(this.ids) || this.isEmpty(this.excludedIds) || this.isEmpty(this.isActives) || this.isEmpty(this.entityIds);
} }
@Override @Override
protected Class<AnnotationEntity> entityClass() { protected Class<AnnotationEntity> entityClass() {
return null; return AnnotationEntity.class;
} }
@Override @Override
protected <X, Y> Predicate applyFilters(QueryContext<X, Y> queryContext) { protected <X, Y> Predicate applyFilters(QueryContext<X, Y> queryContext) {
return null; List<Predicate> 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<UUID> 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<UUID> 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<IsActive> 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<UUID> 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 @Override
protected String fieldNameOf(FieldResolver item) { 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 @Override
protected AnnotationEntity convert(Tuple tuple, Set<String> columns) { protected AnnotationEntity convert(Tuple tuple, Set<String> 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;
} }
} }

View File

@ -1,5 +1,73 @@
package gr.cite.annotation.query.lookup; 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<UUID> ids;
private List<UUID> excludedIds;
private List<IsActive> isActive;
private List<UUID> entityIds;
public String getLike() {
return like;
}
public void setLike(String like) {
this.like = like;
}
public List<UUID> getIds() {
return ids;
}
public void setIds(List<UUID> ids) {
this.ids = ids;
}
public List<UUID> getExcludedIds() {
return excludedIds;
}
public void setExcludedIds(List<UUID> excludedIds) {
this.excludedIds = excludedIds;
}
public List<IsActive> getIsActive() {
return isActive;
}
public void setIsActive(List<IsActive> 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;
}
} }

View File

@ -57,7 +57,7 @@ public class AnnotationServiceImpl implements AnnotationService {
@Override @Override
@Transactional @Transactional
public Annotation persist(AnnotationPersist model, FieldSet fields) throws MyForbiddenException, MyValidationException, MyApplicationException, MyNotFoundException { 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); this.authorizationService.authorizeForce(Permission.EditAnnotation);

View File

@ -158,7 +158,7 @@ public class TenantConfigurationServiceImpl implements TenantConfigurationServic
@Override @Override
public void deleteAndSave(UUID id) throws InvalidApplicationException { public void deleteAndSave(UUID id) throws InvalidApplicationException {
logger.debug("deleting tenant Configuration: {}", id); 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)); this.deleterFactory.deleter(TenantConfigurationDeleter.class).deleteAndSaveByIds(List.of(id));
} }