multitenant changes

This commit is contained in:
Efstratios Giannopoulos 2024-04-04 16:39:40 +03:00
parent 5ec8417f4e
commit ab993c43af
112 changed files with 2639 additions and 2816 deletions

View File

@ -4,6 +4,7 @@ import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.enums.IsActive; import gr.cite.annotation.common.enums.IsActive;
import gr.cite.annotation.common.scope.fake.FakeRequestScope; import gr.cite.annotation.common.scope.fake.FakeRequestScope;
import gr.cite.annotation.data.QueueInboxEntity; import gr.cite.annotation.data.QueueInboxEntity;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.annotation.integrationevent.TrackedEvent; import gr.cite.annotation.integrationevent.TrackedEvent;
import gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval.AnnotationEntitiesRemovalIntegrationEventHandler; import gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval.AnnotationEntitiesRemovalIntegrationEventHandler;
import gr.cite.annotation.integrationevent.inbox.annotationentitiestouch.AnnotationEntitiesTouchedIntegrationEventHandler; import gr.cite.annotation.integrationevent.inbox.annotationentitiestouch.AnnotationEntitiesTouchedIntegrationEventHandler;
@ -278,6 +279,10 @@ public class InboxRepositoryImpl implements InboxRepository {
entityManager = entityManagerFactory.createEntityManager(); entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction(); transaction = entityManager.getTransaction();
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
transaction.begin(); transaction.begin();
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);

View File

@ -32,6 +32,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.management.InvalidApplicationException;
import java.util.*; import java.util.*;
@Component @Component
@ -44,15 +45,26 @@ public class AnnotationEntitiesRemovalIntegrationEventHandlerImpl implements Ann
private final ValidatorFactory validatorFactory; private final ValidatorFactory validatorFactory;
private final ApplicationContext applicationContext;
private final QueryFactory queryFactory; private final QueryFactory queryFactory;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
private final DeleterFactory deleterFactory;
private final TenantScope tenantScope;
public AnnotationEntitiesRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, ApplicationContext applicationContext, QueryFactory queryFactory) {
public AnnotationEntitiesRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, QueryFactory queryFactory, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, AuditService auditService, TenantEntityManager tenantEntityManager, DeleterFactory deleterFactory, TenantScope tenantScope) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.validatorFactory = validatorFactory; this.validatorFactory = validatorFactory;
this.applicationContext = applicationContext;
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.deleterFactory = deleterFactory;
this.tenantScope = tenantScope;
} }
@Override @Override
@ -65,83 +77,53 @@ public class AnnotationEntitiesRemovalIntegrationEventHandlerImpl implements Ann
this.validatorFactory.validator(AnnotationEntitiesRemovalIntegrationEvent.AnnotationEntitiesRemovalIntegrationEventValidator.class).validateForce(event); this.validatorFactory.validator(AnnotationEntitiesRemovalIntegrationEvent.AnnotationEntitiesRemovalIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
try { TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
TenantScope scope = this.applicationContext.getBean(TenantScope.class); if (tenant == null) {
if (scope.isMultitenant() && properties.getTenantId() != null) { logger.error("missing tenant from event message");
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); return EventProcessingStatus.Error;
if (tenant == null) { }
logger.error("missing tenant from event message"); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
return EventProcessingStatus.Error; } else if (this.tenantScope.isMultitenant()) {
}
scope.setTenant(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
// logger.error("missing tenant from event message"); // logger.error("missing tenant from event message");
// return EventProcessingStatus.Error; // return EventProcessingStatus.Error;
scope.setTenant(null, scope.getDefaultTenantCode()); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
DeleterFactory deleterFactory = this.applicationContext.getBean(DeleterFactory.class);
transaction = entityManager.getTransaction();
transaction.begin();
try {
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
tenantEntityManager.disableTenantFilters();
EntityUserQuery entityUserQuery = this.queryFactory.query(EntityUserQuery.class);
List<EntityUserEntity> items = entityUserQuery
.entityIds(event.getEntityIds())
.isActive(IsActive.Active)
.collect();
deleterFactory.deleter(gr.cite.EntityUser.model.deleter.EntityUserDeleter.class).delete(items);
entityManager.flush();
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
tenantEntityManager.disableTenantFilters();
EntityUserQuery entityUserQuery = this.queryFactory.query(EntityUserQuery.class);
List<EntityUserEntity> items = entityUserQuery
.entityIds(event.getEntityIds())
.isActive(IsActive.Active)
.collect();
deleterFactory.deleter(gr.cite.EntityUser.model.deleter.EntityUserDeleter.class).delete(items);
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
try {
this.tenantEntityManager.enableTenantFilters();
} catch (InvalidApplicationException e) {
}
} }
return EventProcessingStatus.Success; return status;
} }
} }

View File

@ -14,6 +14,7 @@ import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.Tenant; import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.query.EntityUserQuery; import gr.cite.annotation.query.EntityUserQuery;
import gr.cite.annotation.query.TenantQuery; import gr.cite.annotation.query.TenantQuery;
import gr.cite.annotation.service.tenant.TenantService;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
@ -32,6 +33,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.management.InvalidApplicationException;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -46,15 +48,24 @@ public class AnnotationEntitiesTouchedIntegrationEventHandlerImpl implements Ann
private final ValidatorFactory validatorFactory; private final ValidatorFactory validatorFactory;
private final ApplicationContext applicationContext;
private final QueryFactory queryFactory; private final QueryFactory queryFactory;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
private final DeleterFactory deleterFactory;
private final TenantScope tenantScope;
public AnnotationEntitiesTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, ApplicationContext applicationContext, QueryFactory queryFactory) { public AnnotationEntitiesTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, QueryFactory queryFactory, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, AuditService auditService, TenantEntityManager tenantEntityManager, DeleterFactory deleterFactory, TenantScope tenantScope) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.validatorFactory = validatorFactory; this.validatorFactory = validatorFactory;
this.applicationContext = applicationContext; this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
this.deleterFactory = deleterFactory;
this.tenantScope = tenantScope;
} }
@Override @Override
@ -67,102 +78,72 @@ public class AnnotationEntitiesTouchedIntegrationEventHandlerImpl implements Ann
this.validatorFactory.validator(AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntitiesTouchedIntegrationEventValidator.class).validateForce(event); this.validatorFactory.validator(AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntitiesTouchedIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
try { TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
TenantScope scope = this.applicationContext.getBean(TenantScope.class); if (tenant == null) {
if (scope.isMultitenant() && properties.getTenantId() != null) { logger.error("missing tenant from event message");
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); return EventProcessingStatus.Error;
if (tenant == null) { }
logger.error("missing tenant from event message"); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
return EventProcessingStatus.Error; } else if (this.tenantScope.isMultitenant()) {
}
scope.setTenant(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
// logger.error("missing tenant from event message"); // logger.error("missing tenant from event message");
// return EventProcessingStatus.Error; // return EventProcessingStatus.Error;
scope.setTenant(null, scope.getDefaultTenantCode()); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
DeleterFactory deleterFactory = this.applicationContext.getBean(DeleterFactory.class);
transaction = entityManager.getTransaction();
transaction.begin();
try {
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
tenantEntityManager.disableTenantFilters();
for (AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent entityEvent : event.getEvents()) {
EntityUserQuery entityUserQuery = this.queryFactory.query(EntityUserQuery.class);
List<EntityUserEntity> items = entityUserQuery
.entityIds(entityEvent.getEntityId())
.isActive(IsActive.Active)
.collect();
List<UUID> updatedCreatedIds = new ArrayList<>();
for (UUID user : entityEvent.getUserIds()) {
EntityUserEntity data = items.stream().filter(x -> x.getUserId().equals(user)).findFirst().orElse(null);
if (data == null) {
data = new EntityUserEntity();
data.setId(UUID.randomUUID());
data.setEntityId(entityEvent.getEntityId());
data.setUserId(user);
data.setTenantId(properties.getTenantId());
data.setCreatedAt(Instant.now());
data.setUpdatedAt(Instant.now());
data.setIsActive(IsActive.Active);
entityManager.persist(data);
}
updatedCreatedIds.add(data.getId());
}
List<EntityUserEntity> toDelete = items.stream().filter(x -> updatedCreatedIds.stream().noneMatch(y -> y.equals(x.getId()))).collect(Collectors.toList());
deleterFactory.deleter(gr.cite.EntityUser.model.deleter.EntityUserDeleter.class).delete(toDelete);
entityManager.flush();
}
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
tenantEntityManager.disableTenantFilters();
for (AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntityTouchedIntegrationEvent entityEvent : event.getEvents()) {
EntityUserQuery entityUserQuery = this.queryFactory.query(EntityUserQuery.class);
List<EntityUserEntity> items = entityUserQuery
.entityIds(entityEvent.getEntityId())
.isActive(IsActive.Active)
.collect();
List<UUID> updatedCreatedIds = new ArrayList<>();
for (UUID user : entityEvent.getUserIds()) {
EntityUserEntity data = items.stream().filter(x -> x.getUserId().equals(user)).findFirst().orElse(null);
if (data == null) {
data = new EntityUserEntity();
data.setId(UUID.randomUUID());
data.setEntityId(entityEvent.getEntityId());
data.setUserId(user);
data.setTenantId(properties.getTenantId());
data.setCreatedAt(Instant.now());
data.setUpdatedAt(Instant.now());
data.setIsActive(IsActive.Active);
}
updatedCreatedIds.add(data.getId());
}
List<EntityUserEntity> toDelete = items.stream().filter(x -> updatedCreatedIds.stream().noneMatch(y -> y.equals(x.getId()))).collect(Collectors.toList());
deleterFactory.deleter(gr.cite.EntityUser.model.deleter.EntityUserDeleter.class).delete(toDelete);
tenantEntityManager.flush();
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
}
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
try {
this.tenantEntityManager.enableTenantFilters();
} catch (InvalidApplicationException e) {
}
} }
return EventProcessingStatus.Success;
return status;
} }
} }

View File

@ -1,26 +1,18 @@
package gr.cite.annotation.integrationevent.inbox.tenantremoval; package gr.cite.annotation.integrationevent.inbox.tenantremoval;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.annotation.audit.AuditableAction; import gr.cite.annotation.audit.AuditableAction;
import gr.cite.annotation.common.JsonHandlingService; import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.scope.fake.FakeRequestScope; import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.annotation.errorcode.ErrorThesaurusProperties;
import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus; import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus;
import gr.cite.annotation.integrationevent.inbox.InboxPrincipal; import gr.cite.annotation.integrationevent.inbox.InboxPrincipal;
import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties; import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.service.tenant.TenantService; import gr.cite.annotation.service.tenant.TenantService;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -34,13 +26,20 @@ public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIn
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantRemovalIntegrationEventHandlerImpl.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantRemovalIntegrationEventHandlerImpl.class));
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ApplicationContext applicationContext; private final ClaimExtractorProperties claimExtractorProperties;
private final TenantService tenantService;
private final AuditService auditService;
public TenantRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext) { private final TenantEntityManager tenantEntityManager;
private final TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler;
public TenantRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, TenantService tenantService, AuditService auditService, TenantEntityManager tenantEntityManager, TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext; this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.tenantRemovalConsistencyHandler = tenantRemovalConsistencyHandler;
} }
@Override @Override
@ -49,65 +48,34 @@ public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIn
if (event == null) if (event == null)
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class); currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler = this.applicationContext.getBean(TenantRemovalConsistencyHandler.class); if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId())))) {
if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId())))) status = EventProcessingStatus.Postponed;
return EventProcessingStatus.Postponed; currentPrincipalResolver.pop();
return status;
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
try {
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
tenantEntityManager.disableTenantFilters();
TenantService tenantService = this.applicationContext.getBean(TenantService.class);
tenantService.deleteAndSave(event.getId());
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.Tenant_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
transaction.commit();
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
tenantEntityManager.disableTenantFilters();
tenantService.deleteAndSave(event.getId());
auditService.track(AuditableAction.Tenant_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
} }
return null; return status;
} }
} }

View File

@ -1,27 +1,20 @@
package gr.cite.annotation.integrationevent.inbox.tenanttouch; package gr.cite.annotation.integrationevent.inbox.tenanttouch;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.annotation.audit.AuditableAction; import gr.cite.annotation.audit.AuditableAction;
import gr.cite.annotation.common.JsonHandlingService; import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.scope.fake.FakeRequestScope; import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus; import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus;
import gr.cite.annotation.integrationevent.inbox.InboxPrincipal; import gr.cite.annotation.integrationevent.inbox.InboxPrincipal;
import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties; import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.persist.TenantTouchedIntegrationEventPersist; import gr.cite.annotation.model.persist.TenantTouchedIntegrationEventPersist;
import gr.cite.annotation.service.tenant.TenantService; import gr.cite.annotation.service.tenant.TenantService;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.validation.ValidatorFactory; import gr.cite.tools.validation.ValidatorFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -34,15 +27,23 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantTouchedIntegrationEventHandlerImpl.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantTouchedIntegrationEventHandlerImpl.class));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final ValidatorFactory validatorFactory; private final ValidatorFactory validatorFactory;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final TenantService tenantService;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
public TenantTouchedIntegrationEventHandlerImpl(ApplicationContext applicationContext, JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory) { public TenantTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, TenantService tenantService, AuditService auditService, TenantEntityManager tenantEntityManager) {
this.applicationContext = applicationContext;
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.validatorFactory = validatorFactory; this.validatorFactory = validatorFactory;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
} }
@Override @Override
@ -56,59 +57,25 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
model.setCode(event.getCode()); model.setCode(event.getCode());
this.validatorFactory.validator(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.class).validateForce(model); this.validatorFactory.validator(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.class).validateForce(model);
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; tenantEntityManager.disableTenantFilters();
try (FakeRequestScope ignored = new FakeRequestScope()) { try {
try { currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); tenantService.persist(model, null);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction(); auditService.track(AuditableAction.Tenant_Persist, Map.ofEntries(
transaction.begin(); new AbstractMap.SimpleEntry<String, Object>("model", model)
));
try {
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
tenantEntityManager.disableTenantFilters();
TenantService tenantService = this.applicationContext.getBean(TenantService.class);
tenantService.persist(model, null);
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.Tenant_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model)
));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
transaction.commit();
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
} }
return null; return status;
} }
} }

View File

@ -1,12 +1,10 @@
package gr.cite.annotation.integrationevent.inbox.userremoval; package gr.cite.annotation.integrationevent.inbox.userremoval;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.annotation.audit.AuditableAction; import gr.cite.annotation.audit.AuditableAction;
import gr.cite.annotation.common.JsonHandlingService; import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.scope.fake.FakeRequestScope;
import gr.cite.annotation.common.scope.tenant.TenantScope; import gr.cite.annotation.common.scope.tenant.TenantScope;
import gr.cite.annotation.data.TenantEntity; import gr.cite.annotation.data.TenantEntity;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.annotation.errorcode.ErrorThesaurusProperties; import gr.cite.annotation.errorcode.ErrorThesaurusProperties;
import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus; import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus;
import gr.cite.annotation.integrationevent.inbox.InboxPrincipal; import gr.cite.annotation.integrationevent.inbox.InboxPrincipal;
@ -14,19 +12,15 @@ import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.Tenant; import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.query.TenantQuery; import gr.cite.annotation.query.TenantQuery;
import gr.cite.annotation.service.user.UserService; import gr.cite.annotation.service.user.UserService;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyValidationException; import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
@ -43,22 +37,35 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errors; private final ErrorThesaurusProperties errors;
private final MessageSource messageSource; private final MessageSource messageSource;
private final QueryFactory queryFactory;
private final TenantScope tenantScope;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final UserRemovalConsistencyHandler userRemovalConsistencyHandler;
private final UserService userService;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
public UserRemovalIntegrationEventHandlerImpl( public UserRemovalIntegrationEventHandlerImpl(
JsonHandlingService jsonHandlingService, JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext,
ErrorThesaurusProperties errors, ErrorThesaurusProperties errors,
MessageSource messageSource MessageSource messageSource, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, UserRemovalConsistencyHandler userRemovalConsistencyHandler, UserService userService, AuditService auditService, TenantEntityManager tenantEntityManager
) { ) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
this.errors = errors; this.errors = errors;
this.messageSource = messageSource; this.messageSource = messageSource;
this.queryFactory = queryFactory;
this.tenantScope = tenantScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.userRemovalConsistencyHandler = userRemovalConsistencyHandler;
this.userService = userService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
} }
@Override @Override
@ -71,77 +78,44 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
logger.debug("Handling {}", UserRemovalIntegrationEvent.class.getSimpleName()); logger.debug("Handling {}", UserRemovalIntegrationEvent.class.getSimpleName());
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
try { TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); if (tenant == null) {
TenantScope scope = this.applicationContext.getBean(TenantScope.class); logger.error("missing tenant from event message");
if (scope.isMultitenant() && properties.getTenantId() != null) { return EventProcessingStatus.Error;
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); }
if (tenant == null) { this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
logger.error("missing tenant from event message"); } else if (this.tenantScope.isMultitenant()) {
return EventProcessingStatus.Error;
}
scope.setTenant(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
// logger.error("missing tenant from event message"); // logger.error("missing tenant from event message");
scope.setTenant(null, scope.getDefaultTenantCode()); // return EventProcessingStatus.Error;
} this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
UserRemovalConsistencyHandler userRemovalConsistencyHandler = this.applicationContext.getBean(UserRemovalConsistencyHandler.class);
if (!(userRemovalConsistencyHandler.isConsistent(new UserRemovalConsistencyPredicates(event.getUserId()))))
return EventProcessingStatus.Postponed;
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
try {
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
UserService userService = this.applicationContext.getBean(UserService.class);
userService.deleteAndSave(event.getUserId());
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.User_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getUserId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
transaction.commit();
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
if (!(userRemovalConsistencyHandler.isConsistent(new UserRemovalConsistencyPredicates(event.getUserId())))) {
status = EventProcessingStatus.Postponed;
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
return status;
}
userService.deleteAndSave(event.getUserId());
auditService.track(AuditableAction.User_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getUserId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
} }
return EventProcessingStatus.Success;
return status;
} }
} }

View File

@ -4,7 +4,6 @@ import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.annotation.audit.AuditableAction; import gr.cite.annotation.audit.AuditableAction;
import gr.cite.annotation.common.JsonHandlingService; import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.scope.fake.FakeRequestScope;
import gr.cite.annotation.common.scope.tenant.TenantScope; import gr.cite.annotation.common.scope.tenant.TenantScope;
import gr.cite.annotation.data.TenantEntity; import gr.cite.annotation.data.TenantEntity;
import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus; import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus;
@ -19,13 +18,8 @@ import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.validation.ValidatorFactory; import gr.cite.tools.validation.ValidatorFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -38,19 +32,30 @@ public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegr
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserTouchedIntegrationEventHandlerImpl.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserTouchedIntegrationEventHandlerImpl.class));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final ValidatorFactory validatorFactory; private final ValidatorFactory validatorFactory;
private final QueryFactory queryFactory;
private final TenantScope tenantScope;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final UserService userService;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
public UserTouchedIntegrationEventHandlerImpl( public UserTouchedIntegrationEventHandlerImpl(
JsonHandlingService jsonHandlingService, JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext, ValidatorFactory validatorFactory, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, UserService userService, AuditService auditService, TenantEntityManager tenantEntityManager) {
ValidatorFactory validatorFactory) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
this.validatorFactory = validatorFactory; this.validatorFactory = validatorFactory;
this.queryFactory = queryFactory;
this.tenantScope = tenantScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.userService = userService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
} }
@Override @Override
@ -63,71 +68,37 @@ public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegr
this.validatorFactory.validator(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.class).validateForce(event); this.validatorFactory.validator(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
try { TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); if (tenant == null) {
TenantScope scope = this.applicationContext.getBean(TenantScope.class); logger.error("missing tenant from event message");
if (scope.isMultitenant() && properties.getTenantId() != null) { return EventProcessingStatus.Error;
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code)); }
if (tenant == null) { this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
logger.error("missing tenant from event message"); } else if (this.tenantScope.isMultitenant()) {
return EventProcessingStatus.Error;
}
scope.setTenant(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
// logger.error("missing tenant from event message"); // logger.error("missing tenant from event message");
// return EventProcessingStatus.Error; // return EventProcessingStatus.Error;
scope.setTenant(null, scope.getDefaultTenantCode()); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
ClaimExtractorProperties claimExtractorProperties = this.applicationContext.getBean(ClaimExtractorProperties.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
try {
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
UserService userService = this.applicationContext.getBean(UserService.class);
userService.persist(event, null);
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
userService.persist(event, null);
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
} }
return EventProcessingStatus.Success;
return status;
} }
} }

View File

@ -36,6 +36,12 @@ public final class Permission {
public static String EditLanguage = "EditLanguage"; public static String EditLanguage = "EditLanguage";
public static String DeleteLanguage = "DeleteLanguage"; public static String DeleteLanguage = "DeleteLanguage";
//NotificationTemplate
public static String BrowseNotificationTemplate = "BrowseNotificationTemplate";
public static String EditNotificationTemplate = "EditNotificationTemplate";
public static String DeleteNotificationTemplate = "DeleteNotificationTemplate";
//Language //Language
public static String BrowseStatistics = "BrowseStatistics"; public static String BrowseStatistics = "BrowseStatistics";
public static String BrowsePublicStatistics = "BrowsePublicStatistics"; public static String BrowsePublicStatistics = "BrowsePublicStatistics";

View File

@ -4,6 +4,7 @@ import eu.eudat.commons.JsonHandlingService;
import eu.eudat.commons.enums.IsActive; import eu.eudat.commons.enums.IsActive;
import eu.eudat.commons.fake.FakeRequestScope; import eu.eudat.commons.fake.FakeRequestScope;
import eu.eudat.data.QueueInboxEntity; import eu.eudat.data.QueueInboxEntity;
import eu.eudat.data.TenantEntityManager;
import eu.eudat.integrationevent.TrackedEvent; import eu.eudat.integrationevent.TrackedEvent;
import eu.eudat.query.QueueInboxQuery; import eu.eudat.query.QueueInboxQuery;
import gr.cite.queueinbox.entity.QueueInbox; import gr.cite.queueinbox.entity.QueueInbox;
@ -28,12 +29,10 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
public class InboxRepositoryImpl implements InboxRepository { public class InboxRepositoryImpl implements InboxRepository { private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InboxRepositoryImpl.class));
protected final ApplicationContext applicationContext; protected final ApplicationContext applicationContext;
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InboxRepositoryImpl.class));
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final InboxProperties inboxProperties; private final InboxProperties inboxProperties;
@ -234,10 +233,17 @@ public class InboxRepositoryImpl implements InboxRepository {
} }
private QueueInboxEntity createQueueInboxEntity(InboxCreatorParams inboxCreatorParams) { private QueueInboxEntity createQueueInboxEntity(InboxCreatorParams inboxCreatorParams) {
QueueInboxEntity queueMessage = new QueueInboxEntity(); QueueInboxEntity queueMessage = new QueueInboxEntity();
queueMessage.setId(UUID.randomUUID()); queueMessage.setId(UUID.randomUUID());
Object tenantId = inboxCreatorParams.getHeaders() != null ? inboxCreatorParams.getHeaders().getOrDefault(IntegrationEventMessageConstants.TENANT, null) : null; Object tenantId = inboxCreatorParams.getHeaders() != null ? inboxCreatorParams.getHeaders().getOrDefault(IntegrationEventMessageConstants.TENANT, null) : null;
if (tenantId instanceof UUID) queueMessage.setTenantId((UUID) tenantId); if (tenantId instanceof UUID) queueMessage.setTenantId((UUID) tenantId);
else if (tenantId instanceof String) {
try {
queueMessage.setTenantId(UUID.fromString((String) tenantId));
} catch (Exception e) {
}
}
queueMessage.setExchange(this.inboxProperties.getExchange()); queueMessage.setExchange(this.inboxProperties.getExchange());
queueMessage.setRoute(inboxCreatorParams.getRoutingKey()); queueMessage.setRoute(inboxCreatorParams.getRoutingKey());
queueMessage.setQueue(inboxCreatorParams.getQueueName()); queueMessage.setQueue(inboxCreatorParams.getQueueName());
@ -265,6 +271,10 @@ public class InboxRepositoryImpl implements InboxRepository {
entityManager = entityManagerFactory.createEntityManager(); entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction(); transaction = entityManager.getTransaction();
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
transaction.begin(); transaction.begin();
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
@ -274,7 +284,7 @@ public class InboxRepositoryImpl implements InboxRepository {
logger.warn("Could not lookup queue inbox {} to process. Continuing...", candidateInfo.getId()); logger.warn("Could not lookup queue inbox {} to process. Continuing...", candidateInfo.getId());
} else { } else {
EventProcessingStatus status = this.processMessage(queueInboxMessage.getRoute(), queueInboxMessage.getMessageId().toString(), queueInboxMessage.getApplicationId(), queueInboxMessage.getMessage()); EventProcessingStatus status = this.processMessage(queueInboxMessage);
switch (status) { switch (status) {
case Success: { case Success: {
queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL); queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL);
@ -317,29 +327,39 @@ public class InboxRepositoryImpl implements InboxRepository {
return success; return success;
} }
private EventProcessingStatus processMessage(String routingKey, String messageId, String appId, String message) {
private EventProcessingStatus processMessage(QueueInboxEntity queueInboxMessage) {
IntegrationEventHandler handler = null; IntegrationEventHandler handler = null;
logger.debug("Processing message with routing key '{}'", queueInboxMessage.getRoute());
// if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getTenantRemovalTopic()))
// handler = this.applicationContext.getBean(TenantRemovalIntegrationEventHandler.class);
// else {
// logger.error("No handler found for message routing key '{}'. Discarding.", queueInboxMessage.getRoute());
// handler = null;
// }
if (handler == null) if (handler == null)
return EventProcessingStatus.Discard; return EventProcessingStatus.Discard;
IntegrationEventProperties properties = new IntegrationEventProperties(); IntegrationEventProperties properties = new IntegrationEventProperties();
properties.setAppId(appId); properties.setAppId(queueInboxMessage.getApplicationId());
properties.setMessageId(messageId); properties.setMessageId(queueInboxMessage.getMessageId().toString());
properties.setTenantId(queueInboxMessage.getTenantId());
TrackedEvent event = this.jsonHandlingService.fromJsonSafe(TrackedEvent.class, message); TrackedEvent event = this.jsonHandlingService.fromJsonSafe(TrackedEvent.class, queueInboxMessage.getMessage());
// using (LogContext.PushProperty(this._logTrackingConfig.LogTrackingContextName, @event.TrackingContextTag)) // using (LogContext.PushProperty(this._logTrackingConfig.LogTrackingContextName, @event.TrackingContextTag))
// { // {
try { try {
return handler.handle(properties, message); return handler.handle(properties, queueInboxMessage.getMessage());
} catch (Exception ex) { } catch (Exception ex) {
logger.error("problem handling event from routing key " + routingKey + ". Setting nack and continuing...", ex); logger.error("problem handling event from routing key " + queueInboxMessage.getRoute() + ". Setting nack and continuing...", ex);
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
} }
// } // }
} }
private Boolean RoutingKeyMatched(String routingKey, List<String> topics) { private Boolean routingKeyMatched(String routingKey, List<String> topics) {
if (topics == null || topics.isEmpty()) if (topics == null || topics.isEmpty())
return false; return false;
return topics.stream().anyMatch(x -> x.equals(routingKey)); return topics.stream().anyMatch(x -> x.equals(routingKey));

View File

@ -1,10 +1,13 @@
package eu.eudat.integrationevent.inbox; package eu.eudat.integrationevent.inbox;
import java.util.UUID;
public class IntegrationEventProperties { public class IntegrationEventProperties {
private String messageId; private String messageId;
private String appId; private String appId;
private UUID tenantId;
public String getMessageId() { public String getMessageId() {
return messageId; return messageId;
@ -22,4 +25,11 @@ public class IntegrationEventProperties {
this.appId = appId; this.appId = appId;
} }
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
} }

View File

@ -23,7 +23,7 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
OutboxIntegrationEvent message = new OutboxIntegrationEvent(); OutboxIntegrationEvent message = new OutboxIntegrationEvent();
message.setMessageId(UUID.randomUUID()); message.setMessageId(UUID.randomUUID());
message.setType(OutboxIntegrationEvent.TENANT_TOUCH); message.setType(OutboxIntegrationEvent.TENANT_TOUCH);
message.setTenantId(event.getId()); // message.setTenantId(event.getId()); //Hack Can not Queue inbox before tenant created
message.setEvent(event); message.setEvent(event);
this.outboxService.publish(message); this.outboxService.publish(message);

View File

@ -167,11 +167,6 @@ public class StorageFileCleanupTask implements Closeable, ApplicationListener<A
return success; return success;
} }
@Transactional
private void purgeSafe(StorageFileService storageFileService, UUID fileId){
storageFileService.purgeSafe(fileId);
}
private CandidateInfo candidate(Instant lastCandidateCreationTimestamp) { private CandidateInfo candidate(Instant lastCandidateCreationTimestamp) {
EntityTransaction transaction = null; EntityTransaction transaction = null;
EntityManager entityManager = null; EntityManager entityManager = null;

View File

@ -110,7 +110,25 @@ permissions:
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
# NotificationTemplate
BrowseNotificationTemplate:
roles: [ ]
clients: [ ]
allowAnonymous: true
allowAuthenticated: true
EditNotificationTemplate:
roles:
- TenantAdmin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteNotificationTemplate:
roles:
- TenantAdmin
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Language # Language
BrowseLanguage: BrowseLanguage:
roles: [ ] roles: [ ]

View File

@ -41,7 +41,7 @@ queue:
generate-file-topic: generate.file generate-file-topic: generate.file
rabbitmq: rabbitmq:
enable: true enable: true
interval-seconds: 30 interval-seconds: 3
options: options:
retry-threashold: 100 retry-threashold: 100
retry-delay-step-seconds: 300 retry-delay-step-seconds: 300
@ -54,7 +54,7 @@ queue:
exchange: null exchange: null
rabbitmq: rabbitmq:
enable: true enable: true
interval-seconds: 30 interval-seconds: 3
options: options:
retry-threashold: 100 retry-threashold: 100
retry-delay-step-seconds: 300 retry-delay-step-seconds: 300

View File

@ -26,11 +26,11 @@ BEGIN
notified_with smallint, notified_with smallint,
CONSTRAINT "ntf_Notification_pkey" PRIMARY KEY (id), CONSTRAINT "ntf_Notification_pkey" PRIMARY KEY (id),
CONSTRAINT "ntf_Notification_tenant_fkey" FOREIGN KEY (tenant) CONSTRAINT "ntf_Notification_tenant_fkey" FOREIGN KEY (tenant)
REFERENCES public."Tenant" (id) MATCH SIMPLE REFERENCES public."ntf_Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION
ON DELETE NO ACTION, ON DELETE NO ACTION,
CONSTRAINT "ntf_Notification_user_fkey" FOREIGN KEY ("user") CONSTRAINT "ntf_Notification_user_fkey" FOREIGN KEY ("user")
REFERENCES public."User" (id) MATCH SIMPLE REFERENCES public."ntf_User" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION
ON DELETE NO ACTION ON DELETE NO ACTION
); );

View File

@ -10,20 +10,15 @@ BEGIN
channel smallint NOT NULL, channel smallint NOT NULL,
notification_type uuid NOT NULL, notification_type uuid NOT NULL,
kind smallint NOT NULL, kind smallint NOT NULL,
language uuid NOT NULL, language_code character varying(200) COLLATE pg_catalog."default" NOT NULL,
value text NOT NULL, value text NOT NULL,
is_active smallint NOT NULL, is_active smallint NOT NULL,
created_at timestamp without time zone NOT NULL, created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL,
tenant uuid, tenant uuid,
CONSTRAINT "NotificationTemplate_pkey" PRIMARY KEY (id), CONSTRAINT "NotificationTemplate_pkey" PRIMARY KEY (id),
CONSTRAINT "NotificationTemplate_language_fkey" FOREIGN KEY (language)
REFERENCES public."Language" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID,
CONSTRAINT "NotificationTemplate_tenant_fkey" FOREIGN KEY (tenant) CONSTRAINT "NotificationTemplate_tenant_fkey" FOREIGN KEY (tenant)
REFERENCES public."Tenant" (id) MATCH SIMPLE REFERENCES public."ntf_Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION
ON DELETE NO ACTION ON DELETE NO ACTION
NOT VALID NOT VALID

View File

@ -21,7 +21,7 @@ BEGIN
is_active smallint NOT NULL, is_active smallint NOT NULL,
CONSTRAINT "ntf_QueryInbox_pkey" PRIMARY KEY (id), CONSTRAINT "ntf_QueryInbox_pkey" PRIMARY KEY (id),
CONSTRAINT "ntf_QueryInbox_tenant_fkey" FOREIGN KEY (tenant) CONSTRAINT "ntf_QueryInbox_tenant_fkey" FOREIGN KEY (tenant)
REFERENCES public."Tenant" (id) MATCH SIMPLE REFERENCES public."ntf_Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION
ON DELETE NO ACTION ON DELETE NO ACTION
NOT VALID NOT VALID

View File

@ -21,7 +21,7 @@ BEGIN
is_active smallint NOT NULL, is_active smallint NOT NULL,
CONSTRAINT "ntf_QueueOutbox_pkey" PRIMARY KEY (id), CONSTRAINT "ntf_QueueOutbox_pkey" PRIMARY KEY (id),
CONSTRAINT "ntf_QueueOutbox_tenant_fkey" FOREIGN KEY (tenant) CONSTRAINT "ntf_QueueOutbox_tenant_fkey" FOREIGN KEY (tenant)
REFERENCES public."Tenant" (id) MATCH SIMPLE REFERENCES public."ntf_Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION
ON DELETE NO ACTION ON DELETE NO ACTION
); );

View File

@ -21,11 +21,11 @@ BEGIN
is_active smallint NOT NULL, is_active smallint NOT NULL,
CONSTRAINT "ntf_InAppNotification_pkey" PRIMARY KEY (id), CONSTRAINT "ntf_InAppNotification_pkey" PRIMARY KEY (id),
CONSTRAINT "ntf_InAppNotification_tenant_fkey" FOREIGN KEY (tenant) CONSTRAINT "ntf_InAppNotification_tenant_fkey" FOREIGN KEY (tenant)
REFERENCES public."Tenant" (id) MATCH SIMPLE REFERENCES public."ntf_tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION
ON DELETE NO ACTION, ON DELETE NO ACTION,
CONSTRAINT "ntf_InAppNotification_user_fkey" FOREIGN KEY ("user") CONSTRAINT "ntf_InAppNotification_user_fkey" FOREIGN KEY ("user")
REFERENCES public."User" (id) MATCH SIMPLE REFERENCES public."ntf_User" (id) MATCH SIMPLE
ON UPDATE NO ACTION ON UPDATE NO ACTION
ON DELETE NO ACTION ON DELETE NO ACTION
); );

View File

@ -2,7 +2,6 @@ import { NotificationTemplateChannel } from "@app/core/common/enum/notification-
import { NotificationTemplateKind } from "@app/core/common/enum/notification-template-kind"; import { NotificationTemplateKind } from "@app/core/common/enum/notification-template-kind";
import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model"; import { BaseEntity, BaseEntityPersist } from "@common/base/base-entity.model";
import { Guid } from "@common/types/guid"; import { Guid } from "@common/types/guid";
import { Language } from "../language/language";
import { NotificationDataType } from "@app/core/common/enum/notification-data-type"; import { NotificationDataType } from "@app/core/common/enum/notification-data-type";
import { EmailOverrideMode } from "@app/core/common/enum/email-override-mode"; import { EmailOverrideMode } from "@app/core/common/enum/email-override-mode";
import { NotificationType } from "@app/core/common/enum/notification-type"; import { NotificationType } from "@app/core/common/enum/notification-type";
@ -11,7 +10,7 @@ export interface NotificationTemplate extends BaseEntity{
channel: NotificationTemplateChannel; channel: NotificationTemplateChannel;
notificationType: NotificationType; notificationType: NotificationType;
kind: NotificationTemplateKind; kind: NotificationTemplateKind;
language: Language; languageCode: string;
value: NotificationTemplateValue; value: NotificationTemplateValue;
} }
@ -49,7 +48,7 @@ export interface NotificationTemplatePersist extends BaseEntityPersist{
channel: NotificationTemplateChannel; channel: NotificationTemplateChannel;
notificationType: NotificationType; notificationType: NotificationType;
kind: NotificationTemplateKind; kind: NotificationTemplateKind;
languageId: Guid; languageCode: string;
value: NotificationTemplateValuePersist; value: NotificationTemplateValuePersist;
} }
@ -79,4 +78,4 @@ export interface NotificationFieldInfoPersist {
key: string; key: string;
type: NotificationDataType, type: NotificationDataType,
value: string; value: string;
} }

View File

@ -45,13 +45,13 @@
<div class="col-12 col-lg-4"> <div class="col-12 col-lg-4">
<mat-form-field class="w-100"> <mat-form-field class="w-100">
<mat-label>{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.LANGUAGE' | translate}}</mat-label> <mat-label>{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.LANGUAGE' | translate}}</mat-label>
<mat-select [formControl]="formGroup.get('languageId')" name="language" required> <mat-select [formControl]="formGroup.get('languageCode')" name="language" required>
<mat-option *ngFor="let language of languages" [value]="language.id"> <mat-option *ngFor="let language of languages" [value]="language.code">
{{language.code}} {{language.code}}
</mat-option> </mat-option>
</mat-select> </mat-select>
<mat-error *ngIf="formGroup.get('languageId').hasError('backendError')">{{formGroup.get('languageId').getError('backendError').message}}</mat-error> <mat-error *ngIf="formGroup.get('languageCode').hasError('backendError')">{{formGroup.get('languageCode').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('languageId').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="formGroup.get('languageCode').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="col-12 col-lg-4"> <div class="col-12 col-lg-4">
@ -123,7 +123,7 @@
<mat-error *ngIf="formGroup.get('value').get('bodyKey').hasError('backendError')">{{formGroup.get('value').get('bodyKey').getError('backendError').message}}</mat-error> <mat-error *ngIf="formGroup.get('value').get('bodyKey').hasError('backendError')">{{formGroup.get('value').get('bodyKey').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('value').get('bodyKey').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="formGroup.get('value').get('bodyKey').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="col-12"> <div class="col-12">
<mat-label>{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BODY-TEXT' | translate}}</mat-label> <mat-label>{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BODY-TEXT' | translate}}</mat-label>
<editor class="w-100 mt-2" [init]="{ <editor class="w-100 mt-2" [init]="{
@ -143,7 +143,7 @@
toolbar: toolbar:
'undo redo | formatselect | bold italic backcolor | \ 'undo redo | formatselect | bold italic backcolor | \
alignleft aligncenter alignright alignjustify | \ alignleft aligncenter alignright alignjustify | \
bullist numlist outdent indent | code | searchreplace | preview | removeformat | help | link | image' bullist numlist outdent indent | code | searchreplace | preview | removeformat | help | link | image'
}" [formControl]="formGroup.get('value').get('bodyText')"></editor> }" [formControl]="formGroup.get('value').get('bodyText')"></editor>
<mat-error *ngIf="formGroup.get('value').get('bodyText').hasError('backendError')">{{formGroup.get('value').get('bodyText').getError('backendError').message}}</mat-error> <mat-error *ngIf="formGroup.get('value').get('bodyText').hasError('backendError')">{{formGroup.get('value').get('bodyText').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('value').get('bodyText').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="formGroup.get('value').get('bodyText').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
@ -191,7 +191,7 @@
<mat-icon>cancel</mat-icon> <mat-icon>cancel</mat-icon>
</button> </button>
</mat-chip-row> </mat-chip-row>
<input placeholder="{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY-PLACEHOLDER' | translate}}" <input placeholder="{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY-PLACEHOLDER' | translate}}"
[matChipInputFor]="cc" [matChipInputFor]="cc"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true" [matChipInputAddOnBlur]="true"
@ -225,7 +225,7 @@
<mat-icon>cancel</mat-icon> <mat-icon>cancel</mat-icon>
</button> </button>
</mat-chip-row> </mat-chip-row>
<input placeholder="{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY-PLACEHOLDER' | translate}}" <input placeholder="{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY-PLACEHOLDER' | translate}}"
[matChipInputFor]="bcc" [matChipInputFor]="bcc"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true" [matChipInputAddOnBlur]="true"
@ -259,7 +259,7 @@
<mat-icon>cancel</mat-icon> <mat-icon>cancel</mat-icon>
</button> </button>
</mat-chip-row> </mat-chip-row>
<input placeholder="{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY-PLACEHOLDER' | translate}}" <input placeholder="{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.MANDATORY-PLACEHOLDER' | translate}}"
[matChipInputFor]="extraDataKeys" [matChipInputFor]="extraDataKeys"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true" [matChipInputAddOnBlur]="true"
@ -268,7 +268,7 @@
<mat-error *ngIf="formGroup.get('value').get('extraDataKeys').hasError('backendError')">{{formGroup.get('value').get('extraDataKeys').getError('backendError').message}}</mat-error> <mat-error *ngIf="formGroup.get('value').get('extraDataKeys').hasError('backendError')">{{formGroup.get('value').get('extraDataKeys').getError('backendError').message}}</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</form> </form>

View File

@ -15,7 +15,7 @@ export class NotificationTemplateEditorModel extends BaseEditorModel implements
channel: NotificationTemplateChannel; channel: NotificationTemplateChannel;
notificationType: NotificationType; notificationType: NotificationType;
kind: NotificationTemplateKind; kind: NotificationTemplateKind;
languageId: Guid; languageCode: string;
value: NotificationTemplateValueEditorModel = new NotificationTemplateValueEditorModel(); value: NotificationTemplateValueEditorModel = new NotificationTemplateValueEditorModel();
permissions: string[]; permissions: string[];
@ -30,10 +30,10 @@ export class NotificationTemplateEditorModel extends BaseEditorModel implements
this.channel = item.channel; this.channel = item.channel;
this.notificationType = item.notificationType; this.notificationType = item.notificationType;
this.kind = item.kind; this.kind = item.kind;
if(item.language && item.language.id) this.languageId = item.language.id; if(item.languageCode && item.languageCode) this.languageCode = item.languageCode;
if (item.value) { this.value = new NotificationTemplateValueEditorModel(this.validationErrorModel).fromModel(item.value); } if (item.value) { this.value = new NotificationTemplateValueEditorModel(this.validationErrorModel).fromModel(item.value); }
} }
return this; return this;
} }
@ -45,7 +45,7 @@ export class NotificationTemplateEditorModel extends BaseEditorModel implements
channel: [{ value: this.channel, disabled: disabled }, context.getValidation('channel').validators], channel: [{ value: this.channel, disabled: disabled }, context.getValidation('channel').validators],
notificationType: [{ value: this.notificationType, disabled: disabled }, context.getValidation('notificationType').validators], notificationType: [{ value: this.notificationType, disabled: disabled }, context.getValidation('notificationType').validators],
kind: [{ value: this.kind, disabled: disabled }, context.getValidation('kind').validators], kind: [{ value: this.kind, disabled: disabled }, context.getValidation('kind').validators],
languageId: [{ value: this.languageId, disabled: disabled }, context.getValidation('languageId').validators], languageCode: [{ value: this.languageCode, disabled: disabled }, context.getValidation('languageCode').validators],
value: this.value.buildForm({ value: this.value.buildForm({
rootPath: `value.` rootPath: `value.`
}), }),
@ -60,7 +60,7 @@ export class NotificationTemplateEditorModel extends BaseEditorModel implements
baseValidationArray.push({ key: 'channel', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'channel')] }); baseValidationArray.push({ key: 'channel', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'channel')] });
baseValidationArray.push({ key: 'notificationType', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'notificationType')] }); baseValidationArray.push({ key: 'notificationType', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'notificationType')] });
baseValidationArray.push({ key: 'kind', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'kind')] }); baseValidationArray.push({ key: 'kind', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'kind')] });
baseValidationArray.push({ key: 'languageId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'languageId')] }); baseValidationArray.push({ key: 'languageCode', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'languageCode')] });
baseValidationArray.push({ key: 'value', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'value')] }); baseValidationArray.push({ key: 'value', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'value')] });
baseValidationArray.push({ key: 'hash', validators: [] }); baseValidationArray.push({ key: 'hash', validators: [] });
@ -175,7 +175,7 @@ export class NotificationTemplateValueEditorModel implements NotificationTemplat
baseValidationArray.push({ key: 'bcc', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}bcc`)] }); baseValidationArray.push({ key: 'bcc', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}bcc`)] });
baseValidationArray.push({ key: 'bccMode', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}bccMode`)] }); baseValidationArray.push({ key: 'bccMode', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}bccMode`)] });
baseValidationArray.push({ key: 'extraDataKeys', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}extraDataKeys`)] }); baseValidationArray.push({ key: 'extraDataKeys', validators: [BackendErrorValidator(validationErrorModel, `${rootPath}extraDataKeys`)] });
baseContext.validation = baseValidationArray; baseContext.validation = baseValidationArray;
return baseContext; return baseContext;
} }
@ -243,7 +243,7 @@ export class NotificationFieldOptionsEditorModel implements NotificationFieldOpt
validationErrorModel: this.validationErrorModel, validationErrorModel: this.validationErrorModel,
rootPath rootPath
}); });
} }
return this.formBuilder.group({ return this.formBuilder.group({
mandatory: [{ value: this.mandatory, disabled: disabled }, context.getValidation('mandatory').validators], mandatory: [{ value: this.mandatory, disabled: disabled }, context.getValidation('mandatory').validators],
@ -256,7 +256,7 @@ export class NotificationFieldOptionsEditorModel implements NotificationFieldOpt
), context.getValidation('optional').validators ), context.getValidation('optional').validators
), ),
}); });
} }
static createValidationContext(params: { static createValidationContext(params: {
@ -350,11 +350,11 @@ export class NotificationFieldInfoEditorModel implements NotificationFieldInfoPe
const baseContext: ValidationContext = new ValidationContext(); const baseContext: ValidationContext = new ValidationContext();
const baseValidationArray: Validation[] = new Array<Validation>(); const baseValidationArray: Validation[] = new Array<Validation>();
baseValidationArray.push({ key: 'key', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}key`)] }); baseValidationArray.push({ key: 'key', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}key`)] });
baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}type`)] }); baseValidationArray.push({ key: 'type', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}type`)] });
baseValidationArray.push({ key: 'value', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}value`)] }); baseValidationArray.push({ key: 'value', validators: [Validators.required, BackendErrorValidator(validationErrorModel, `${rootPath}value`)] });
baseContext.validation = baseValidationArray; baseContext.validation = baseValidationArray;
return baseContext; return baseContext;
} }

View File

@ -24,9 +24,8 @@ export class NotificationTemplateEditorResolver extends BaseEditorResolver {
nameof<NotificationTemplate>(x => x.notificationType), nameof<NotificationTemplate>(x => x.notificationType),
nameof<NotificationTemplate>(x => x.kind), nameof<NotificationTemplate>(x => x.kind),
[nameof<NotificationTemplate>(x => x.language),nameof<Language>(x => x.id)].join('.'), nameof<NotificationTemplate>(x => x.languageCode),
[nameof<NotificationTemplate>(x => x.language),nameof<Language>(x => x.code)].join('.'),
[nameof<NotificationTemplate>(x => x.value),nameof<NotificationTemplateValue>(x => x.subjectText)].join('.'), [nameof<NotificationTemplate>(x => x.value),nameof<NotificationTemplateValue>(x => x.subjectText)].join('.'),
[nameof<NotificationTemplate>(x => x.value),nameof<NotificationTemplateValue>(x => x.subjectKey)].join('.'), [nameof<NotificationTemplate>(x => x.value),nameof<NotificationTemplateValue>(x => x.subjectKey)].join('.'),
[nameof<NotificationTemplate>(x => x.value),nameof<NotificationTemplateValue>(x => x.subjectFieldOptions), nameof<NotificationFieldOptions>(x => x.mandatory)].join('.'), [nameof<NotificationTemplate>(x => x.value),nameof<NotificationTemplateValue>(x => x.subjectFieldOptions), nameof<NotificationFieldOptions>(x => x.mandatory)].join('.'),

View File

@ -3,7 +3,7 @@ package gr.cite.notification.web;
import gr.cite.notification.web.scope.tenant.TenantInterceptor; import gr.cite.notification.web.scope.tenant.TenantInterceptor;
import gr.cite.notification.web.scope.tenant.TenantScopeClaimInterceptor; import gr.cite.notification.web.scope.tenant.TenantScopeClaimInterceptor;
import gr.cite.notification.web.scope.tenant.TenantScopeHeaderInterceptor; import gr.cite.notification.web.scope.tenant.TenantScopeHeaderInterceptor;
import gr.cite.notification.web.interceptors.UserInterceptor; import gr.cite.notification.web.scope.user.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

View File

@ -26,6 +26,8 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen
import jakarta.servlet.Filter; import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -98,7 +100,7 @@ public class SecurityConfiguration {
//In the example below, the default client handler will be ignored by the resolver //In the example below, the default client handler will be ignored by the resolver
@Override @Override
public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() { public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() {
return List.of(PermissionClientAuthorizationHandler.class); return new ArrayList<>();
} }
}; };
} }

View File

@ -75,17 +75,4 @@ public class PrincipalController {
} }
@GetMapping("my-tenants")
public List<String> myTenants() {
logger.debug("my-tenants");
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
List<String> tenants = this.claimExtractor.asStrings(principal, TenantScope.TenantCodesClaimName);
this.auditService.track(AuditableAction.Tenants_Lookup);
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return tenants == null ? null : tenants.stream().distinct().collect(Collectors.toList());
}
} }

View File

@ -1,103 +0,0 @@
package gr.cite.notification.web.interceptors;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.lock.LockByKeyManager;
import gr.cite.notification.common.scope.user.UserScope;
import gr.cite.notification.data.UserCredentialEntity;
import gr.cite.notification.model.UserCredential;
import gr.cite.notification.query.UserCredentialQuery;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.UUID;
@Component
public class UserInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserInterceptor.class));
private final UserScope userScope;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final PlatformTransactionManager transactionManager;
private final UserInterceptorCacheService userInterceptorCacheService;
private final JsonHandlingService jsonHandlingService;
private final QueryFactory queryFactory;
private final LockByKeyManager lockByKeyManager;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public UserInterceptor(
UserScope userScope,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
PlatformTransactionManager transactionManager,
UserInterceptorCacheService userInterceptorCacheService,
JsonHandlingService jsonHandlingService,
QueryFactory queryFactory,
LockByKeyManager lockByKeyManager) {
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.transactionManager = transactionManager;
this.userInterceptorCacheService = userInterceptorCacheService;
this.jsonHandlingService = jsonHandlingService;
this.queryFactory = queryFactory;
this.lockByKeyManager = lockByKeyManager;
}
@Override
public void preHandle(WebRequest request) {
UUID userId = null;
if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) {
String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal());
if (subjectId == null || subjectId.isBlank()) throw new MyForbiddenException("Empty subjects not allowed");
UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
if (cacheValue != null) {
userId = cacheValue.getUserId();
} else {
userId = this.findExistingUserFromDbForce(subjectId);
cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
this.userInterceptorCacheService.put(cacheValue);
}
}
this.userScope.setUserId(userId);
}
private UUID findExistingUserFromDbForce(String subjectId){
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user));
if (userCredential != null) {
return userCredential.getUserId();
} else {
throw new MyForbiddenException("User not created try again.");
}
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.userScope.setUserId(null);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -1,10 +0,0 @@
package gr.cite.notification.web.interceptors;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.user-by-subject-id")
public class UserInterceptorCacheOptions extends CacheOptions {
}

View File

@ -1,68 +0,0 @@
package gr.cite.notification.web.interceptors;
import gr.cite.notification.convention.ConventionService;
import gr.cite.tools.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@Service
public class UserInterceptorCacheService extends CacheService<UserInterceptorCacheService.UserInterceptorCacheValue> {
public static class UserInterceptorCacheValue {
public UserInterceptorCacheValue() {
}
public UserInterceptorCacheValue(String subjectId, UUID userId) {
this.subjectId = subjectId;
this.userId = userId;
}
private String subjectId;
public String getSubjectId() {
return subjectId;
}
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}
private UUID userId;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}
@Autowired
public UserInterceptorCacheService(UserInterceptorCacheOptions options) {
super(options);
}
@Override
protected Class<UserInterceptorCacheValue> valueClass() {
return UserInterceptorCacheValue.class;
}
@Override
public String keyOf(UserInterceptorCacheValue value) {
return this.buildKey(value.getSubjectId());
}
public String buildKey(String subject) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$subject$", subject);
return this.generateKey(keyParts);
}
}

View File

@ -13,64 +13,66 @@ import java.util.UUID;
@Service @Service
public class TenantByCodeCacheService extends CacheService<TenantByCodeCacheService.TenantByCodeCacheValue> { public class TenantByCodeCacheService extends CacheService<TenantByCodeCacheService.TenantByCodeCacheValue> {
public static class TenantByCodeCacheValue { public static class TenantByCodeCacheValue {
public TenantByCodeCacheValue() { public TenantByCodeCacheValue() {
} }
public TenantByCodeCacheValue(String tenantCode, UUID tenantId) { public TenantByCodeCacheValue(String tenantCode, UUID tenantId) {
this.tenantCode = tenantCode; this.tenantCode = tenantCode;
this.tenantId = tenantId; this.tenantId = tenantId;
} }
private String tenantCode; private String tenantCode;
public String getTenantCode() { public String getTenantCode() {
return tenantCode; return tenantCode;
} }
public void setTenantCode(String tenantCode) { public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode; this.tenantCode = tenantCode;
} }
private UUID tenantId; private UUID tenantId;
public UUID getTenantId() { public UUID getTenantId() {
return tenantId; return tenantId;
} }
public void setTenantId(UUID tenantId) { public void setTenantId(UUID tenantId) {
this.tenantId = tenantId; this.tenantId = tenantId;
} }
} }
private final ConventionService conventionService; private final ConventionService conventionService;
@Autowired @Autowired
public TenantByCodeCacheService(TenantByCodeCacheOptions options, ConventionService conventionService) { public TenantByCodeCacheService(TenantByCodeCacheOptions options, ConventionService conventionService) {
super(options); super(options);
this.conventionService = conventionService; this.conventionService = conventionService;
} }
@EventListener @EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) { public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantCode())); if (!this.conventionService.isNullOrEmpty(event.getTenantCode()))
if (!this.conventionService.isNullOrEmpty(event.getPreviousTenantCode())) this.evict(this.buildKey(event.getPreviousTenantCode())); this.evict(this.buildKey(event.getTenantCode()));
} if (!this.conventionService.isNullOrEmpty(event.getPreviousTenantCode()))
this.evict(this.buildKey(event.getPreviousTenantCode()));
}
@Override @Override
protected Class<TenantByCodeCacheValue> valueClass() { protected Class<TenantByCodeCacheValue> valueClass() {
return TenantByCodeCacheValue.class; return TenantByCodeCacheValue.class;
} }
@Override @Override
public String keyOf(TenantByCodeCacheValue value) { public String keyOf(TenantByCodeCacheValue value) {
return this.buildKey(value.getTenantCode()); return this.buildKey(value.getTenantCode());
} }
public String buildKey(String code) { public String buildKey(String code) {
return this.generateKey(new HashMap<>() {{ HashMap<String, String> keyParts = new HashMap<>();
put("$code$", code); keyParts.put("$code$", code);
}}); return this.generateKey(keyParts);
} }
} }

View File

@ -14,63 +14,64 @@ import java.util.UUID;
@Service @Service
public class TenantByIdCacheService extends CacheService<TenantByIdCacheService.TenantByIdCacheValue> { public class TenantByIdCacheService extends CacheService<TenantByIdCacheService.TenantByIdCacheValue> {
public static class TenantByIdCacheValue { public static class TenantByIdCacheValue {
public TenantByIdCacheValue() { public TenantByIdCacheValue() {
} }
public TenantByIdCacheValue(String tenantCode, UUID tenantId) { public TenantByIdCacheValue(String tenantCode, UUID tenantId) {
this.tenantCode = tenantCode; this.tenantCode = tenantCode;
this.tenantId = tenantId; this.tenantId = tenantId;
} }
private String tenantCode; private String tenantCode;
public String getTenantCode() { public String getTenantCode() {
return tenantCode; return tenantCode;
} }
public void setTenantCode(String tenantCode) { public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode; this.tenantCode = tenantCode;
} }
private UUID tenantId; private UUID tenantId;
public UUID getTenantId() { public UUID getTenantId() {
return tenantId; return tenantId;
} }
public void setTenantId(UUID tenantId) { public void setTenantId(UUID tenantId) {
this.tenantId = tenantId; this.tenantId = tenantId;
} }
} }
private final ConventionService conventionService; private final ConventionService conventionService;
@Autowired @Autowired
public TenantByIdCacheService(TenantByIdCacheOptions options, ConventionService conventionService) { public TenantByIdCacheService(TenantByIdCacheOptions options, ConventionService conventionService) {
super(options); super(options);
this.conventionService = conventionService; this.conventionService = conventionService;
} }
@EventListener @EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) { public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantId())); if (!this.conventionService.isNullOrEmpty(event.getTenantCode()))
} this.evict(this.buildKey(event.getTenantId()));
}
@Override @Override
protected Class<TenantByIdCacheValue> valueClass() { protected Class<TenantByIdCacheValue> valueClass() {
return TenantByIdCacheValue.class; return TenantByIdCacheValue.class;
} }
@Override @Override
public String keyOf(TenantByIdCacheValue value) { public String keyOf(TenantByIdCacheValue value) {
return this.buildKey(value.getTenantId()); return this.buildKey(value.getTenantId());
} }
public String buildKey(UUID id) { public String buildKey(UUID id) {
return this.generateKey(new HashMap<>() {{ HashMap<String, String> keyParts = new HashMap<>();
put("$tenantId$", id.toString().toLowerCase(Locale.ROOT)); keyParts.put("$tenantId$", id.toString().toLowerCase(Locale.ROOT));
}}); return this.generateKey(keyParts);
} }
} }

View File

@ -4,6 +4,7 @@ package gr.cite.notification.web.scope.tenant;
import gr.cite.commons.web.authz.service.AuthorizationService; import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.notification.authorization.ClaimNames;
import gr.cite.notification.authorization.Permission; import gr.cite.notification.authorization.Permission;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
@ -12,9 +13,17 @@ import gr.cite.notification.data.TenantUserEntity;
import gr.cite.notification.data.UserEntity; import gr.cite.notification.data.UserEntity;
import gr.cite.notification.data.tenant.TenantScopedBaseEntity; import gr.cite.notification.data.tenant.TenantScopedBaseEntity;
import gr.cite.notification.errorcode.ErrorThesaurusProperties; import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.notification.query.utils.BuildSubQueryInput;
import gr.cite.notification.query.utils.QueryUtilsService;
import gr.cite.tools.exception.MyForbiddenException; import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.hibernate.Session; import org.hibernate.Session;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -26,15 +35,9 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.context.request.WebRequestInterceptor;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID;
@Component @Component
public class TenantInterceptor implements WebRequestInterceptor { public class TenantInterceptor implements WebRequestInterceptor {
@ -45,11 +48,10 @@ public class TenantInterceptor implements WebRequestInterceptor {
private final CurrentPrincipalResolver currentPrincipalResolver; private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractor claimExtractor; private final ClaimExtractor claimExtractor;
private final ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errorThesaurusProperties;
private final TenantScopeProperties tenantScopeProperties; private final TenantScopeProperties tenantScopeProperties;
private final UserAllowedTenantCacheService userAllowedTenantCacheService; private final UserAllowedTenantCacheService userAllowedTenantCacheService;
private final ErrorThesaurusProperties errors; private final ErrorThesaurusProperties errors;
private final QueryUtilsService queryUtilsService;
@PersistenceContext @PersistenceContext
public EntityManager entityManager; public EntityManager entityManager;
@ -60,60 +62,70 @@ public class TenantInterceptor implements WebRequestInterceptor {
CurrentPrincipalResolver currentPrincipalResolver, CurrentPrincipalResolver currentPrincipalResolver,
ClaimExtractor claimExtractor, ClaimExtractor claimExtractor,
ApplicationContext applicationContext, ApplicationContext applicationContext,
ErrorThesaurusProperties errorThesaurusProperties,
TenantScopeProperties tenantScopeProperties, TenantScopeProperties tenantScopeProperties,
UserAllowedTenantCacheService userAllowedTenantCacheService, UserAllowedTenantCacheService userAllowedTenantCacheService,
ErrorThesaurusProperties errors) { ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService) {
this.tenantScope = tenantScope; this.tenantScope = tenantScope;
this.userScope = userScope; this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver; this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor; this.claimExtractor = claimExtractor;
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.errorThesaurusProperties = errorThesaurusProperties;
this.tenantScopeProperties = tenantScopeProperties; this.tenantScopeProperties = tenantScopeProperties;
this.userAllowedTenantCacheService = userAllowedTenantCacheService; this.userAllowedTenantCacheService = userAllowedTenantCacheService;
this.errors = errors; this.errors = errors;
this.queryUtilsService = queryUtilsService;
} }
@Override @Override
public void preHandle(WebRequest request) throws InvalidApplicationException { public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException, InterruptedException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return; if (!this.tenantScope.isMultitenant()) return;
boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant); boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant);
if (tenantScope.isSet() && this.entityManager != null) { if (tenantScope.isSet() && this.entityManager != null) {
List<String> currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), TenantScope.TenantCodesClaimName); List<String> currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), ClaimNames.TenantCodesClaimName);
if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) { if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) {
logger.warn("tenant not allowed {}", this.tenantScope.getTenant()); logger.warn("tenant not allowed {}", this.tenantScope.getTenant());
//throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage());
} }
boolean isUserAllowedTenant = false; boolean isUserAllowedTenant = false;
UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant())); if (this.tenantScope.isDefaultTenant()){
if (cacheValue != null) { isUserAllowedTenant = true;
isUserAllowedTenant = cacheValue.isAllowed();
} else { } else {
isUserAllowedTenant = this.isUserAllowedTenant(); UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant()));
this.userAllowedTenantCacheService.put(new UserAllowedTenantCacheService.UserAllowedTenantCacheValue(this.userScope.getUserId(), this.tenantScope.getTenant(), isUserAllowedTenant)); if (cacheValue != null) {
isUserAllowedTenant = cacheValue.isAllowed();
} else {
isUserAllowedTenant = this.isUserAllowedTenant();
this.userAllowedTenantCacheService.put(new UserAllowedTenantCacheService.UserAllowedTenantCacheValue(this.userScope.getUserId(), this.tenantScope.getTenant(), isUserAllowedTenant));
}
} }
if (isUserAllowedTenant) { if (isUserAllowedTenant) {
this.entityManager if(!tenantScope.isDefaultTenant()) {
.unwrap(Session.class) this.entityManager
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString()); .unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, tenantScope.getTenant().toString());
} else {
this.entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
} else { } else {
if (isAllowedNoTenant || this.isWhiteListedEndpoint(request)) { if (isAllowedNoTenant || this.isWhiteListedEndpoint(request)) {
tenantScope.setTenant(null, null); tenantScope.setTenant(null, null);
} else { } else {
logger.warn("tenant not allowed {}", this.tenantScope.getTenant()); logger.warn("tenant not allowed {}", this.tenantScope.getTenant());
//throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage()); throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage());
} }
} }
} else { } else {
if (!isAllowedNoTenant) { if (!isAllowedNoTenant) {
if (!this.isWhiteListedEndpoint(request)) { if (!this.isWhiteListedEndpoint(request)) {
logger.warn("tenant scope not provided"); logger.warn("tenant scope not provided");
throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage()); throw new MyForbiddenException(this.errors.getMissingTenant().getCode(), this.errors.getMissingTenant().getMessage());
} }
} }
} }
@ -131,26 +143,36 @@ public class TenantInterceptor implements WebRequestInterceptor {
return false; return false;
} }
private boolean isUserAllowedTenant() throws InvalidApplicationException { private boolean isUserAllowedTenant() throws InvalidApplicationException, InterruptedException {
if (userScope.isSet()) { if (userScope.isSet()) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<UserEntity> query = criteriaBuilder.createQuery(UserEntity.class);
Root<UserEntity> root = query.from(UserEntity.class); Root<UserEntity> root = query.from(UserEntity.class);
Subquery<TenantUserEntity> subQuery = query.subquery(TenantUserEntity.class);
Root<TenantUserEntity> subQueryRoot = subQuery.from(TenantUserEntity.class);
query.where(criteriaBuilder.and( query.where(criteriaBuilder.and(
criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active), criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active),
criteriaBuilder.in(root.get(UserEntity._id)).value(subQuery.where( criteriaBuilder.in(root.get(UserEntity._id)).value(queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(new BuildSubQueryInput.Builder<>(TenantUserEntity.class, UUID.class)
criteriaBuilder.and( .query(query)
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._tenantId), this.tenantScope.getTenant()), .criteriaBuilder(criteriaBuilder)
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._userId), this.userScope.getUserId()), .keyPathFunc((subQueryRoot) -> subQueryRoot.get(TenantUserEntity._userId))
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._isActive), IsActive.Active) .filterFunc((subQueryRoot, cb) ->
)).select(subQueryRoot.get(TenantUserEntity._userId)).distinct(true) {
try {
return cb.and(
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._tenantId), this.tenantScope.getTenant()),
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._userId), this.userScope.getUserId()),
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._isActive), IsActive.Active)
);
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
}
)
))
) )
)); ));
query.multiselect(root.get(UserEntity._id).alias(UserEntity._id)); query.multiselect(root.get(UserEntity._id).alias(UserEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<UserEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() > 0) return true; return !results.isEmpty();
} }
return false; return false;

View File

@ -5,6 +5,7 @@ import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext;
import gr.cite.notification.authorization.ClaimNames;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.convention.ConventionService; import gr.cite.notification.convention.ConventionService;
@ -12,6 +13,11 @@ import gr.cite.notification.data.TenantEntity;
import gr.cite.notification.errorcode.ErrorThesaurusProperties; import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyForbiddenException; import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -22,176 +28,157 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.context.request.WebRequestInterceptor;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@Component @Component
public class TenantScopeClaimInterceptor implements WebRequestInterceptor { public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeClaimInterceptor.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeClaimInterceptor.class));
private final TenantScope tenantScope; private final TenantScope tenantScope;
private final ConventionService conventionService; private final ConventionService conventionService;
private final TenantScopeProperties tenantScopeProperties; private final TenantScopeProperties tenantScopeProperties;
private final ErrorThesaurusProperties errorThesaurusProperties; private final ErrorThesaurusProperties errorThesaurusProperties;
private final ClaimExtractor claimExtractor; private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver; private final CurrentPrincipalResolver currentPrincipalResolver;
private final String clientTenantClaimName; private final String clientTenantClaimName;
private final ClaimExtractorContext claimExtractorContext; private final ClaimExtractorContext claimExtractorContext;
private final TenantByCodeCacheService tenantByCodeCacheService; private final TenantByCodeCacheService tenantByCodeCacheService;
private final TenantByIdCacheService tenantByIdCacheService; private final TenantByIdCacheService tenantByIdCacheService;
@PersistenceContext @PersistenceContext
public EntityManager entityManager; public EntityManager entityManager;
@Autowired @Autowired
public TenantScopeClaimInterceptor( public TenantScopeClaimInterceptor(
TenantScope tenantScope, TenantScope tenantScope,
ConventionService conventionService, ConventionService conventionService,
ClaimExtractor claimExtractor, ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver, CurrentPrincipalResolver currentPrincipalResolver,
ErrorThesaurusProperties errorThesaurusProperties, ErrorThesaurusProperties errorThesaurusProperties,
TenantScopeProperties tenantScopeProperties, TenantScopeProperties tenantScopeProperties,
ClaimExtractorContext claimExtractorContext, ClaimExtractorContext claimExtractorContext,
TenantByCodeCacheService tenantByCodeCacheService, TenantByCodeCacheService tenantByCodeCacheService,
TenantByIdCacheService tenantByIdCacheService TenantByIdCacheService tenantByIdCacheService
) { ) {
this.tenantScope = tenantScope; this.tenantScope = tenantScope;
this.conventionService = conventionService; this.conventionService = conventionService;
this.currentPrincipalResolver = currentPrincipalResolver; this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor; this.claimExtractor = claimExtractor;
this.errorThesaurusProperties = errorThesaurusProperties; this.errorThesaurusProperties = errorThesaurusProperties;
this.tenantScopeProperties = tenantScopeProperties; this.tenantScopeProperties = tenantScopeProperties;
this.claimExtractorContext = claimExtractorContext; this.claimExtractorContext = claimExtractorContext;
this.tenantByCodeCacheService = tenantByCodeCacheService; this.tenantByCodeCacheService = tenantByCodeCacheService;
this.tenantByIdCacheService = tenantByIdCacheService; this.tenantByIdCacheService = tenantByIdCacheService;
this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + TenantScope.TenantClaimName; this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + ClaimNames.TenantClaimName;
} }
@Override @Override
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException { public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return; if (!this.tenantScope.isMultitenant()) return;
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal(); MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) { if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) {
Boolean scoped = this.scopeByPrincipal(this.tenantScope, principal); boolean scoped = this.scopeByPrincipal(principal);
if (!scoped) scoped = this.scopeByClient(this.tenantScope, principal); if (!scoped) scoped = this.scopeByClient(principal);
if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant()) throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage()); if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant())
} throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage());
} }
}
private Boolean scopeByPrincipal(TenantScope scope, MyPrincipal principal) { private boolean scopeByPrincipal(MyPrincipal principal) {
String tenantCode = this.claimExtractor.tenantString(principal); String tenantCode = this.claimExtractor.tenantString(principal);
if (tenantCode == null || tenantCode.isBlank()) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName); if (this.conventionService.isNullOrEmpty(tenantCode)) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName);
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return false;
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode); if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
if (tenantId == null && tenantCode == null) return false; logger.debug("parsed tenant header and set tenant to default tenant");
if (tenantId == null) { this.tenantScope.setTenant(null, tenantCode);
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode)); this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
if (cacheValue != null) { return true;
tenantId = cacheValue.getTenantId(); }
} else {
tenantId = this.getTenantIdFromDatabase(tenantCode); UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId)); if (tenantId == null) {
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId)); TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
} if (cacheValue != null) {
} else { tenantId = cacheValue.getTenantId();
logger.debug("tenant claim was set to {}", tenantId); } else {
TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId)); tenantId = this.getTenantIdFromDatabase(tenantCode);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
}
} else {
logger.debug("tenant claim was set to {}", tenantId);
TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId));
if (cacheValue != null) { if (cacheValue != null) {
tenantCode = cacheValue.getTenantCode(); tenantCode = cacheValue.getTenantCode();
} else { } else {
tenantCode = this.getTenantCodeFromDatabase(tenantId); tenantCode = this.getTenantCodeFromDatabase(tenantId);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId)); this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId)); this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
} }
} }
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) { if (tenantId != null) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId); logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode); this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode); this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
} return true;
return tenantId != null; }
} return false;
}
private Boolean scopeByClient(TenantScope scope, MyPrincipal principal) throws InvalidApplicationException { private boolean scopeByClient(MyPrincipal principal) throws InvalidApplicationException {
String client = this.claimExtractor.client(principal); String client = this.claimExtractor.client(principal);
Boolean isWhiteListed = this.tenantScopeProperties.getWhiteListedClients() != null && !this.conventionService.isNullOrEmpty(client) && this.tenantScopeProperties.getWhiteListedClients().contains(client); Boolean isWhiteListed = this.tenantScopeProperties.getWhiteListedClients() != null && !this.conventionService.isNullOrEmpty(client) && this.tenantScopeProperties.getWhiteListedClients().contains(client);
logger.debug("client is whitelisted : {}, scope is set: {}, with value {}", isWhiteListed, scope.isSet(), (scope.isSet() ? scope.getTenant() : null)); logger.debug("client is whitelisted : {}, scope is set: {}, with value {}", isWhiteListed, this.tenantScope.isSet(), (this.tenantScope.isSet() ? this.tenantScope.getTenant() : null));
return isWhiteListed && scope.isSet(); return isWhiteListed && this.tenantScope.isSet();
} }
private UUID getTenantIdFromDatabase(String tenantCode) { private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode), criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getId();
try { }
o = results.get(0).get(TenantEntity._id); return null;
} catch (IllegalArgumentException e) { }
return null;
}
if (o == null) return null;
try {
return (UUID) o;
} catch (ClassCastException e) {
return null;
}
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) { private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._id), tenantId), criteriaBuilder.equal(root.get(TenantEntity._id), tenantId),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getCode();
try { }
o = results.get(0).get(TenantEntity._code); return null;
} catch (IllegalArgumentException e) { }
return null;
}
if (o == null) return null;
try {
return (String) o;
} catch (ClassCastException e) {
return null;
}
}
return null;
}
@Override @Override
public void postHandle(@NonNull WebRequest request, ModelMap model) { public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null); this.tenantScope.setTenant(null, null);
this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter); this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter);
} }
@Override @Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) { public void afterCompletion(@NonNull WebRequest request, Exception ex) {
} }
} }

View File

@ -3,11 +3,18 @@ package gr.cite.notification.web.scope.tenant;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext;
import gr.cite.notification.authorization.ClaimNames;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.convention.ConventionService; import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.data.TenantEntity; import gr.cite.notification.data.TenantEntity;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
@ -16,13 +23,6 @@ import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor; import org.springframework.web.context.request.WebRequestInterceptor;
import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -56,16 +56,22 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
} }
@Override @Override
public void preHandle(WebRequest request) throws InvalidApplicationException { public void preHandle(@NotNull WebRequest request) {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return; if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return; if (!this.tenantScope.isMultitenant()) return;
String tenantCode = request.getHeader(TenantScope.TenantClaimName); String tenantCode = request.getHeader(ClaimNames.TenantClaimName);
logger.debug("retrieved request tenant header is: {header}", tenantCode); logger.debug("retrieved request tenant header is: {}", tenantCode);
if (this.conventionService.isNullOrEmpty(tenantCode)) return; if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return;
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
logger.debug("parsed tenant header and set tenant to default tenant");
this.tenantScope.setTenant(null, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return;
}
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode); UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
if (tenantId == null && tenantCode == null) return;
if (tenantId == null) { if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode)); TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) { if (cacheValue != null) {
@ -86,8 +92,8 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
} }
} }
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) { if (tenantId != null) {
logger.debug("parsed tenant header and set tenant id to {tenant}", tenantId); logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode); this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode); this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
} }
@ -95,7 +101,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
private UUID getTenantIdFromDatabase(String tenantCode) { private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
@ -103,27 +109,16 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id)); ).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getId();
try {
o = results.get(0).get(TenantEntity._id);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return UUID.class.cast(o);
} catch (ClassCastException e) {
return null;
}
} }
return null; return null;
} }
private String getTenantCodeFromDatabase(UUID tenantId) { private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class); CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class); Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where( query = query.where(
criteriaBuilder.and( criteriaBuilder.and(
@ -131,20 +126,9 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active) criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
) )
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code)); ).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<Tuple> results = this.entityManager.createQuery(query).getResultList(); List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) { if (results.size() == 1) {
Object o; return results.getFirst().getCode();
try {
o = results.get(0).get(TenantEntity._code);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return String.class.cast(o);
} catch (ClassCastException e) {
return null;
}
} }
return null; return null;
} }

View File

@ -40,4 +40,5 @@ public class TenantScopeProperties {
public void setEnforceTrustedTenant(Boolean enforceTrustedTenant) { public void setEnforceTrustedTenant(Boolean enforceTrustedTenant) {
this.enforceTrustedTenant = enforceTrustedTenant; this.enforceTrustedTenant = enforceTrustedTenant;
} }
} }

View File

@ -8,3 +8,5 @@ import org.springframework.context.annotation.Configuration;
@ConfigurationProperties(prefix = "cache.user-allowed-tenant") @ConfigurationProperties(prefix = "cache.user-allowed-tenant")
public class UserAllowedTenantCacheOptions extends CacheOptions { public class UserAllowedTenantCacheOptions extends CacheOptions {
} }

View File

@ -14,77 +14,78 @@ import java.util.UUID;
@Service @Service
public class UserAllowedTenantCacheService extends CacheService<UserAllowedTenantCacheService.UserAllowedTenantCacheValue> { public class UserAllowedTenantCacheService extends CacheService<UserAllowedTenantCacheService.UserAllowedTenantCacheValue> {
public static class UserAllowedTenantCacheValue { public static class UserAllowedTenantCacheValue {
public UserAllowedTenantCacheValue() { public UserAllowedTenantCacheValue() {
} }
public UserAllowedTenantCacheValue(UUID userId, UUID tenantId, boolean isAllowed) { public UserAllowedTenantCacheValue(UUID userId, UUID tenantId, boolean isAllowed) {
this.userId = userId; this.userId = userId;
this.tenantId = tenantId; this.tenantId = tenantId;
this.isAllowed = isAllowed; this.isAllowed = isAllowed;
} }
private UUID userId; private UUID userId;
public UUID getUserId() { public UUID getUserId() {
return userId; return userId;
} }
public void setUserId(UUID userId) { public void setUserId(UUID userId) {
this.userId = userId; this.userId = userId;
} }
private UUID tenantId; private UUID tenantId;
public UUID getTenantId() { public UUID getTenantId() {
return tenantId; return tenantId;
} }
public void setTenantId(UUID tenantId) { public void setTenantId(UUID tenantId) {
this.tenantId = tenantId; this.tenantId = tenantId;
} }
private boolean isAllowed; private boolean isAllowed;
public boolean isAllowed() { public boolean isAllowed() {
return isAllowed; return isAllowed;
} }
public void setAllowed(boolean allowed) { public void setAllowed(boolean allowed) {
isAllowed = allowed; isAllowed = allowed;
} }
} }
@Autowired @Autowired
public UserAllowedTenantCacheService(UserAllowedTenantCacheOptions options) { public UserAllowedTenantCacheService(UserAllowedTenantCacheOptions options) {
super(options); super(options);
} }
@EventListener @EventListener
public void handleUserRemovedFromTenantEvent(UserRemovedFromTenantEvent event) { public void handleUserRemovedFromTenantEvent(UserRemovedFromTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId())); this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
} }
@EventListener @EventListener
public void handleUserAddedToTenantEvent(UserAddedToTenantEvent event) { public void handleUserAddedToTenantEvent(UserAddedToTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId())); this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
} }
@Override @Override
protected Class<UserAllowedTenantCacheValue> valueClass() { protected Class<UserAllowedTenantCacheValue> valueClass() {
return UserAllowedTenantCacheValue.class; return UserAllowedTenantCacheValue.class;
} }
@Override @Override
public String keyOf(UserAllowedTenantCacheValue value) { public String keyOf(UserAllowedTenantCacheValue value) {
return this.buildKey(value.getUserId(), value.getTenantId()); return this.buildKey(value.getUserId(), value.getTenantId());
} }
public String buildKey(UUID userId, UUID tenantId) { public String buildKey(UUID userId, UUID tenantId) {
return this.generateKey(new HashMap<>() {{ HashMap<String, String> keyParts = new HashMap<>();
put("$user_id$", userId.toString().toLowerCase(Locale.ROOT)); keyParts.put("$user_id$", userId.toString().toLowerCase(Locale.ROOT));
put("$tenant_id$", tenantId.toString().toLowerCase(Locale.ROOT)); keyParts.put("$tenant_id$", tenantId.toString().toLowerCase(Locale.ROOT));
}}); return this.generateKey(keyParts);
} }
} }

View File

@ -1,154 +1,81 @@
//package gr.cite.notification.web.scope.user; package gr.cite.notification.web.scope.user;
//
//
//import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
//import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor; import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
//import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.scope.user.UserScope;
//import gr.cite.notification.common.scope.user.UserScope; import gr.cite.notification.data.UserCredentialEntity;
//import gr.cite.notification.data.UserEntity; import gr.cite.notification.model.UserCredential;
//import gr.cite.notification.locale.LocaleService; import gr.cite.notification.query.UserCredentialQuery;
//import gr.cite.tools.logging.LoggerService; import gr.cite.tools.data.query.QueryFactory;
//import org.slf4j.LoggerFactory; import gr.cite.tools.exception.MyForbiddenException;
//import org.springframework.beans.factory.annotation.Autowired; import gr.cite.tools.fieldset.BaseFieldSet;
//import org.springframework.lang.NonNull; import org.jetbrains.annotations.NotNull;
//import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.transaction.PlatformTransactionManager; import org.springframework.lang.NonNull;
//import org.springframework.transaction.TransactionDefinition; import org.springframework.stereotype.Component;
//import org.springframework.transaction.TransactionStatus; import org.springframework.ui.ModelMap;
//import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.web.context.request.WebRequest;
//import org.springframework.ui.ModelMap; import org.springframework.web.context.request.WebRequestInterceptor;
//import org.springframework.web.context.request.WebRequest;
//import org.springframework.web.context.request.WebRequestInterceptor; import java.util.UUID;
//
//import javax.management.InvalidApplicationException; @Component
//import jakarta.persistence.EntityManager; public class UserInterceptor implements WebRequestInterceptor {
//import jakarta.persistence.PersistenceContext; private final UserScope userScope;
//import jakarta.persistence.Tuple; private final ClaimExtractor claimExtractor;
//import jakarta.persistence.criteria.CriteriaBuilder; private final CurrentPrincipalResolver currentPrincipalResolver;
//import jakarta.persistence.criteria.CriteriaQuery; private final UserInterceptorCacheService userInterceptorCacheService;
//import jakarta.persistence.criteria.Root; private final QueryFactory queryFactory;
//import java.time.Instant;
//import java.util.List; @Autowired
//import java.util.UUID; public UserInterceptor(
// UserScope userScope,
//@Component ClaimExtractor claimExtractor,
//public class UserInterceptor implements WebRequestInterceptor { CurrentPrincipalResolver currentPrincipalResolver,
// private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserInterceptor.class)); UserInterceptorCacheService userInterceptorCacheService,
// private final UserScope userScope; QueryFactory queryFactory) {
// private final ClaimExtractor claimExtractor; this.userScope = userScope;
// private final CurrentPrincipalResolver currentPrincipalResolver; this.currentPrincipalResolver = currentPrincipalResolver;
// private final LocaleService localeService; this.claimExtractor = claimExtractor;
// private final PlatformTransactionManager transactionManager; this.userInterceptorCacheService = userInterceptorCacheService;
// private final UserInterceptorCacheService userInterceptorCacheService; this.queryFactory = queryFactory;
// @PersistenceContext }
// public EntityManager entityManager;
// @Override
// @Autowired public void preHandle(@NotNull WebRequest request) {
// public UserInterceptor( UUID userId = null;
// UserScope userScope, if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) {
// LocaleService localeService, String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal());
// ClaimExtractor claimExtractor, if (subjectId == null || subjectId.isBlank()) throw new MyForbiddenException("Empty subjects not allowed");
// CurrentPrincipalResolver currentPrincipalResolver,
// PlatformTransactionManager transactionManager, UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
// UserInterceptorCacheService userInterceptorCacheService if (cacheValue != null) {
// ) { userId = cacheValue.getUserId();
// this.userScope = userScope; } else {
// this.localeService = localeService; userId = this.findExistingUserFromDb(subjectId);
// this.currentPrincipalResolver = currentPrincipalResolver; if (userId != null) {
// this.claimExtractor = claimExtractor; cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
// this.transactionManager = transactionManager; this.userInterceptorCacheService.put(cacheValue);
// this.userInterceptorCacheService = userInterceptorCacheService; }
// } }
// }
// @Override this.userScope.setUserId(userId);
// public void preHandle(WebRequest request) throws InvalidApplicationException { }
// UUID userId = null; private UUID findExistingUserFromDb(String subjectId) {
// if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) { UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user));
// String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal()); if (userCredential != null) {
// return userCredential.getUserId();
// UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId)); }
// if (cacheValue != null) { return null;
// userId = cacheValue.getUserId(); }
// } else { @Override
// userId = this.getUserIdFromDatabase(subjectId); public void postHandle(@NonNull WebRequest request, ModelMap model) {
// if (userId == null) userId = this.createUser(subjectId); this.userScope.setUserId(null);
// }
// this.userInterceptorCacheService.put(new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId));
// } @Override
// } public void afterCompletion(@NonNull WebRequest request, Exception ex) {
// this.userScope.setUserId(userId); }
// } }
//
// private UUID getUserIdFromDatabase(String subjectId) throws InvalidApplicationException {
// CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
// CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
// Root<UserEntity> root = query.from(UserEntity.class);
// query.where(
// criteriaBuilder.and(
//// criteriaBuilder.equal(root.get(UserEntity._subjectId), subjectId),
// criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active)
// ));
//
// query.multiselect(root.get(UserEntity._id).alias(UserEntity._id));
//
// List<Tuple> results = this.entityManager.createQuery(query).getResultList();
// if (results.size() == 1) {
// Object o;
// try {
// o = results.get(0).get(UserEntity._id);
// } catch (IllegalArgumentException e) {
// return null;
// }
// if (o == null) return null;
// try {
// return UUID.class.cast(o);
// } catch (ClassCastException e) {
// return null;
// }
// }
// return null;
// }
//
// private UUID createUser(String subjectId) {
// String name = this.claimExtractor.name(this.currentPrincipalResolver.currentPrincipal());
// String familyName = this.claimExtractor.familyName(this.currentPrincipalResolver.currentPrincipal());
// if (name == null) name = subjectId;
// UserEntity user = new UserEntity();
// user.setId(UUID.randomUUID());
// user.setCreatedAt(Instant.now());
// user.setUpdatedAt(Instant.now());
// user.setName(name);
//// user.setLastName(familyName == null ? name : familyName);
// user.setIsActive(IsActive.Active);
//// user.setSubjectId(subjectId);
//// user.setCulture(this.localeService.cultureName());
//// user.setTimezone(this.localeService.timezoneName());
//// user.setLanguage(this.localeService.language());
//
// DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// definition.setName(UUID.randomUUID().toString());
// definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// TransactionStatus status = null;
// try {
// status = transactionManager.getTransaction(definition);
// this.entityManager.persist(user);
//
// this.entityManager.flush();
// transactionManager.commit(status);
// } catch (Exception ex) {
// if (status != null) transactionManager.rollback(status);
// throw ex;
// }
// return user.getId();
// }
//
// @Override
// public void postHandle(@NonNull WebRequest request, ModelMap model) {
// this.userScope.setUserId(null);
// }
//
// @Override
// public void afterCompletion(@NonNull WebRequest request, Exception ex) {
// }
//}

View File

@ -1,10 +1,10 @@
//package gr.cite.notification.web.scope.user; package gr.cite.notification.web.scope.user;
//
//import gr.cite.tools.cache.CacheOptions; import gr.cite.tools.cache.CacheOptions;
//import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
//
//@Configuration @Configuration
//@ConfigurationProperties(prefix = "cache.user-by-subject-id") @ConfigurationProperties(prefix = "cache.user-by-subject-id")
//public class UserInterceptorCacheOptions extends CacheOptions { public class UserInterceptorCacheOptions extends CacheOptions {
//} }

View File

@ -1,77 +1,67 @@
//package gr.cite.notification.web.scope.user; package gr.cite.notification.web.scope.user;
//
//import gr.cite.notification.convention.ConventionService; import gr.cite.notification.convention.ConventionService;
//import gr.cite.notification.event.UserTouchedEvent; import gr.cite.tools.cache.CacheService;
//import gr.cite.tools.cache.CacheService; import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
//import org.springframework.context.event.EventListener;
//import org.springframework.stereotype.Service; import java.util.HashMap;
// import java.util.UUID;
//import java.util.HashMap;
//import java.util.UUID; @Service
// public class UserInterceptorCacheService extends CacheService<UserInterceptorCacheService.UserInterceptorCacheValue> {
//@Service
//public class UserInterceptorCacheService extends CacheService<UserInterceptorCacheService.UserInterceptorCacheValue> { public static class UserInterceptorCacheValue {
//
// public static class UserInterceptorCacheValue { public UserInterceptorCacheValue() {
// }
// public UserInterceptorCacheValue() {
// } public UserInterceptorCacheValue(String subjectId, UUID userId) {
// this.subjectId = subjectId;
// public UserInterceptorCacheValue(String subjectId, UUID userId) { this.userId = userId;
// this.subjectId = subjectId; }
// this.userId = userId;
// }
// public String getSubjectId() {
// private String subjectId; return subjectId;
// }
// public String getSubjectId() {
// return subjectId; public void setSubjectId(String subjectId) {
// } this.subjectId = subjectId;
// }
// public void setSubjectId(String subjectId) {
// this.subjectId = subjectId; private String subjectId;
// } private UUID userId;
//
// private UUID userId; public UUID getUserId() {
// return userId;
// public UUID getUserId() { }
// return userId;
// } public void setUserId(UUID userId) {
// this.userId = userId;
// public void setUserId(UUID userId) { }
// this.userId = userId; }
// }
// }
// @Autowired
// private final ConventionService conventionService; public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) {
// super(options);
// @Autowired }
// public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) {
// super(options); @Override
// this.conventionService = conventionService; protected Class<UserInterceptorCacheValue> valueClass() {
// } return UserInterceptorCacheValue.class;
// }
// @EventListener
// public void handleUserTouchedEvent(UserTouchedEvent event) { @Override
// if (!this.conventionService.isNullOrEmpty(event.getSubjectId())) this.evict(this.buildKey(event.getSubjectId())); public String keyOf(UserInterceptorCacheValue value) {
// if (!this.conventionService.isNullOrEmpty(event.getPreviousSubjectId())) this.evict(this.buildKey(event.getPreviousSubjectId())); return this.buildKey(value.getSubjectId());
// } }
//
// @Override
// protected Class<UserInterceptorCacheValue> valueClass() { public String buildKey(String subject) {
// return UserInterceptorCacheValue.class; HashMap<String, String> keyParts = new HashMap<>();
// } keyParts.put("$subject$", subject);
// return this.generateKey(keyParts);
// @Override }
// public String keyOf(UserInterceptorCacheValue value) { }
// return this.buildKey(value.getSubjectId());
// }
//
//
// public String buildKey(String subject) {
// return this.generateKey(new HashMap<>() {{
// put("$subject$", subject);
// }});
// }
//}

View File

@ -26,8 +26,11 @@ error-thesaurus:
non-person-principal: non-person-principal:
code: 108 code: 108
message: the operation is available only to person users message: the operation is available only to person users
blocking-consent: tenant-not-allowed:
code: 113 code: 113
message: tenant not allowed
blocking-consent:
code: 114
message: user consents are not sufficient to complete the operation message: user consents are not sufficient to complete the operation
single-tenant-configuration-per-type-supported: single-tenant-configuration-per-type-supported:
code: 116 code: 116
@ -41,3 +44,6 @@ error-thesaurus:
overlapping-tenant-configuration-notifier-list: overlapping-tenant-configuration-notifier-list:
code: 119 code: 119
message: Overlapping Tenant Configuration Notifier List message: Overlapping Tenant Configuration Notifier List
tenant-tampering:
code: 123
message: Tenant tampering

View File

@ -20,11 +20,25 @@ idpclient:
Roles: Roles:
- type: resource_access - type: resource_access
path: dmp_web.roles path: dmp_web.roles
- type: tenant_roles
filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]"
GlobalRoles:
- type: resource_access
path: dmp_web.roles
TenantRoles:
- type: tenant_roles
filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]"
Scope: Scope:
- type: scope - type: scope
AccessToken: AccessToken:
- type: x-access-token - type: x-access-token
visibility: SENSITIVE visibility: SENSITIVE
Tenant:
- type: x-tenant
IssuedAt: IssuedAt:
- type: iat - type: iat
Issuer: Issuer:
@ -37,5 +51,8 @@ idpclient:
- type: azp - type: azp
Authorities: Authorities:
- type: authorities - type: authorities
ExternalProviderName: TenantCodes:
- type: identity_provider - type: tenant_roles
filterBy: "(.*):(.*)"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g2]]"

View File

@ -12,14 +12,14 @@ permissions:
EditTenant: EditTenant:
roles: roles:
- Admin - Admin
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
DeleteTenant: DeleteTenant:
roles: roles:
- Admin - Admin
claims: [ ] claims: [ ]
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
AllowNoTenant: AllowNoTenant:
@ -32,85 +32,85 @@ permissions:
# Users # Users
BrowseUser: BrowseUser:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
EditUser: EditUser:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
DeleteUser: DeleteUser:
roles: roles:
- Admin - TenantAdmin
claims: [ ] claims: [ ]
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
# UserContactInfo # UserContactInfo
BrowseUserContactInfo: BrowseUserContactInfo:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
EditUserContactInfo: EditUserContactInfo:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
DeleteUserContactInfo: DeleteUserContactInfo:
roles: roles:
- Admin - TenantAdmin
claims: [ ] claims: [ ]
clients: [ ] clients: [ "opendmp-api-dev" ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
#Notification #Notification
BrowseNotification: BrowseNotification:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
EditNotification: EditNotification:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
DeleteNotification: DeleteNotification:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
#Tenant Configuration #Tenant Configuration
BrowseTenantConfiguration: BrowseTenantConfiguration:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
EditTenantConfiguration: EditTenantConfiguration:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
#User Notification Preference #User Notification Preference
BrowseUserNotificationPreference: BrowseUserNotificationPreference:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: true allowAnonymous: true
allowAuthenticated: false allowAuthenticated: false
EditUserNotificationPreference: EditUserNotificationPreference:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
@ -118,25 +118,25 @@ permissions:
# ViewPage Permissions # ViewPage Permissions
ViewNotificationPage: ViewNotificationPage:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
ViewNotificationEventRulePage: ViewNotificationEventRulePage:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
ViewInAppNotificationPage: ViewInAppNotificationPage:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
ViewNotificationTemplatePage: ViewNotificationTemplatePage:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
@ -144,19 +144,19 @@ permissions:
# Notification Template Permissions # Notification Template Permissions
BrowseNotificationTemplate: BrowseNotificationTemplate:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
EditNotificationTemplate: EditNotificationTemplate:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
DeleteNotificationTemplate: DeleteNotificationTemplate:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
@ -164,13 +164,13 @@ permissions:
# In App Notification Permissions # In App Notification Permissions
BrowseInAppNotification: BrowseInAppNotification:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false
DeleteInAppNotification: DeleteInAppNotification:
roles: roles:
- Admin - TenantAdmin
clients: [ ] clients: [ ]
allowAnonymous: false allowAnonymous: false
allowAuthenticated: false allowAuthenticated: false

View File

@ -25,9 +25,6 @@ queue:
enable: false enable: false
options: options:
exchange: null exchange: null
forget-me-completed-topic: forgetme.completed
what-you-know-about-me-completed-topic: whatyouknowaboutme.completed
generate-file-topic: generate.file
rabbitmq: rabbitmq:
enable: false enable: false
interval-seconds: 30 interval-seconds: 30

View File

@ -0,0 +1,7 @@
tenant:
multitenancy:
is-multitenant: true
default-tenant-code: default
interceptor:
client-claims-prefix: client_
enforce-trusted-tenant: false

View File

@ -2,7 +2,6 @@ tenant:
multitenancy: multitenancy:
is-multitenant: false is-multitenant: false
interceptor: interceptor:
client-claims-prefix: client_
white-listed-clients: [ ] white-listed-clients: [ ]
enforce-trusted-tenant: false enforce-trusted-tenant: false
white-listed-endpoints: [ '/api/principal/my-tenants', '/api/principal/me','/api/user/user-settings', '/error', '/api/tenant-request' ] white-listed-endpoints: [ '/api/principal/me' ]

View File

@ -0,0 +1,9 @@
package gr.cite.notification.authorization;
public class ClaimNames {
public static final String ExternalProviderName = "ExternalProviderName";
public static final String TenantCodesClaimName = "TenantCodes";
public static final String TenantClaimName = "x-tenant";
public static final String GlobalRolesClaimName = "GlobalRoles";
public static final String TenantRolesClaimName = "TenantRoles";
}

View File

@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "tenant.multitenancy") @ConfigurationProperties(prefix = "tenant.multitenancy")
public class MultitenancyProperties { public class MultitenancyProperties {
private boolean isMultitenant; private boolean isMultitenant;
private String defaultTenantCode;
public boolean isMultitenant() { public boolean isMultitenant() {
return isMultitenant; return isMultitenant;
@ -13,4 +14,16 @@ public class MultitenancyProperties {
public void setIsMultitenant(boolean multitenant) { public void setIsMultitenant(boolean multitenant) {
isMultitenant = multitenant; isMultitenant = multitenant;
} }
public void setMultitenant(boolean multitenant) {
isMultitenant = multitenant;
}
public String getDefaultTenantCode() {
return defaultTenantCode;
}
public void setDefaultTenantCode(String defaultTenantCode) {
this.defaultTenantCode = defaultTenantCode;
}
} }

View File

@ -1,34 +1,25 @@
package gr.cite.notification.common.scope.tenant; package gr.cite.notification.common.scope.tenant;
import gr.cite.notification.data.tenant.TenantScopedBaseEntity; import gr.cite.notification.data.tenant.TenantScopedBaseEntity;
import gr.cite.tools.logging.LoggerService; import jakarta.persistence.EntityManager;
import org.hibernate.Session; import org.hibernate.Session;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope; import org.springframework.web.context.annotation.RequestScope;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
@Component @Component
@RequestScope @RequestScope
public class TenantScope { public class TenantScope {
public static final String TenantReplaceParameter = "::TenantCode::"; public static final String TenantReplaceParameter = "::TenantCode::";
public static final String TenantCodesClaimName = "TenantCodes"; private final MultitenancyProperties multitenancy;
public static final String TenantClaimName = "x-tenant"; private final AtomicReference<UUID> tenant = new AtomicReference<>();
private final AtomicReference<String> tenantCode = new AtomicReference<>();
private MultitenancyProperties multitenancy; private final AtomicReference<UUID> initialTenant = new AtomicReference<>();
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScope.class)); private final AtomicReference<String> initialTenantCode = new AtomicReference<>();
private UUID tenant = null;
private String tenantCode = null;
private UUID initialTenant = null;
private String initialTenantCode = null;
@Autowired @Autowired
public TenantScope(MultitenancyProperties multitenancy) { public TenantScope(MultitenancyProperties multitenancy) {
@ -39,49 +30,79 @@ public class TenantScope {
return multitenancy.isMultitenant(); return multitenancy.isMultitenant();
} }
public String getDefaultTenantCode() {
return multitenancy.getDefaultTenantCode();
}
public Boolean isSet() { public Boolean isSet() {
if (!this.isMultitenant()) return true; if (!this.isMultitenant())
return this.tenant != null; return Boolean.TRUE;
return this.tenant.get() != null || this.isDefaultTenant();
}
public Boolean isDefaultTenant() {
if (!this.isMultitenant())
return Boolean.TRUE;
return this.multitenancy.getDefaultTenantCode().equalsIgnoreCase(this.tenantCode.get());
} }
public UUID getTenant() throws InvalidApplicationException { public UUID getTenant() throws InvalidApplicationException {
if (!this.isMultitenant()) return null; if (!this.isMultitenant())
if (this.tenant == null) throw new InvalidApplicationException("tenant not set"); return null;
return this.tenant; if (this.tenant.get() == null && !this.isDefaultTenant())
throw new InvalidApplicationException("tenant not set");
return this.isDefaultTenant() ? null : this.tenant.get();
} }
public String getTenantCode() throws InvalidApplicationException { public String getTenantCode() throws InvalidApplicationException {
if (!this.isMultitenant()) return null; if (!this.isMultitenant())
if (this.tenant == null) throw new InvalidApplicationException("tenant not set"); return null;
return this.tenantCode; if (this.tenantCode.get() == null)
throw new InvalidApplicationException("tenant not set");
return this.tenantCode.get();
} }
public void setTempTenant(EntityManager entityManager, UUID tenant, String tenantCode) { public void setTempTenant(EntityManager entityManager, UUID tenant, String tenantCode) {
this.tenant = tenant; this.tenant.set(tenant);
this.tenantCode.set(tenantCode);
if(this.tenant != null) { if (this.tenant.get() != null && !this.isDefaultTenant()) {
entityManager if(!this.isDefaultTenant()) {
.unwrap(Session.class) entityManager
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.tenant.toString()); .unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, this.tenant.get().toString());
} else {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
} }
} }
public void removeTempTenant(EntityManager entityManager) { public void removeTempTenant(EntityManager entityManager) {
this.tenant = this.initialTenant; this.tenant.set(this.initialTenant.get());
this.tenantCode = this.initialTenantCode; this.tenantCode.set(this.initialTenantCode.get());
if(this.initialTenant != null) { if (this.initialTenant.get() != null && !this.isDefaultTenant()) {
entityManager if(!this.isDefaultTenant()) {
.unwrap(Session.class) entityManager
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.initialTenant.toString()); .unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, this.tenant.get().toString());
} else {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
} }
} }
public void setTenant(UUID tenant, String tenantCode) { public void setTenant(UUID tenant, String tenantCode) {
if (this.isMultitenant()) { if (this.isMultitenant()) {
this.tenant = tenant; this.tenant.set(tenant);
this.initialTenant = tenant; this.initialTenant.set(tenant);
this.tenantCode = tenantCode; this.tenantCode.set(tenantCode);
this.initialTenantCode = tenantCode; this.initialTenantCode.set(tenantCode);
} }
} }
} }

View File

@ -1,94 +0,0 @@
package gr.cite.notification.data;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.conventers.IsActiveConverter;
import gr.cite.notification.data.tenant.TenantScopedBaseEntity;
import jakarta.persistence.*;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "\"Language\"")
public class LanguageEntity extends TenantScopedBaseEntity {
@Id
@Column(name = "id", columnDefinition = "uuid", updatable = false, nullable = false)
private UUID id;
public static final String _id = "id";
@Column(name = "code", length = 20, nullable = false)
private String code;
public static final String _code = "code";
@Column(name = "ordinal")
private Integer ordinal;
public static final String _ordinal = "ordinal";
@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";
@Column(name = "is_active", nullable = false)
@Convert(converter = IsActiveConverter.class)
private IsActive isActive;
public static final String _isActive = "isActive";
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Integer getOrdinal() {
return ordinal;
}
public void setOrdinal(Integer ordinal) {
this.ordinal = ordinal;
}
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 IsActive getIsActive() {
return isActive;
}
public void setIsActive(IsActive isActive) {
this.isActive = isActive;
}
}

View File

@ -39,10 +39,10 @@ public class NotificationTemplateEntity extends TenantScopedBaseEntity {
public static final String _kind = "kind"; public static final String _kind = "kind";
@Column(name = "\"language\"", columnDefinition = "uuid", nullable = false) @Column(name = "\"language_code\"", nullable = false, length = 200)
private UUID languageId; private String languageCode;
public static final String _languageId = "languageId"; public static final String _languageCode = "languageCode";
@Column(name = "\"value\"", nullable = false) @Column(name = "\"value\"", nullable = false)
private String value; private String value;
@ -97,12 +97,12 @@ public class NotificationTemplateEntity extends TenantScopedBaseEntity {
this.kind = kind; this.kind = kind;
} }
public UUID getLanguageId() { public String getLanguageCode() {
return languageId; return languageCode;
} }
public void setLanguageId(UUID languageId) { public void setLanguageCode(String languageCode) {
this.languageId = languageId; this.languageCode = languageCode;
} }
public String getValue() { public String getValue() {

View File

@ -0,0 +1,116 @@
package gr.cite.notification.data;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.scope.tenant.TenantScoped;
import gr.cite.notification.data.tenant.TenantScopedBaseEntity;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyForbiddenException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.PersistenceContext;
import org.hibernate.Session;
import org.springframework.stereotype.Service;
import org.springframework.web.context.annotation.RequestScope;
import javax.management.InvalidApplicationException;
@Service
@RequestScope
public class TenantEntityManager {
@PersistenceContext
private EntityManager entityManager;
private final TenantScope tenantScope;
private final ErrorThesaurusProperties errors;
public TenantEntityManager(TenantScope tenantScope, ErrorThesaurusProperties errors) {
this.tenantScope = tenantScope;
this.errors = errors;
}
public void persist(Object entity) {
this.entityManager.persist(entity);
}
public <T> T merge(T entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) {
if (!tenantScope.isDefaultTenant()) {
if (tenantScopedEntity.getTenantId() == null || !tenantScopedEntity.getTenantId().equals(tenantScope.getTenant())) throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
} else if (tenantScopedEntity.getTenantId() != null) {
throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
}
}
return this.entityManager.merge(entity);
}
public void remove(Object entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) {
if (!tenantScope.isDefaultTenant()) {
if (tenantScopedEntity.getTenantId() == null || !tenantScopedEntity.getTenantId().equals(tenantScope.getTenant())) throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
} else if (tenantScopedEntity.getTenantId() != null) {
throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
}
}
this.entityManager.remove(entity);
}
public <T> T find(Class<T> entityClass, Object primaryKey) throws InvalidApplicationException {
T entity = this.entityManager.find(entityClass, primaryKey);
if (tenantScope.isMultitenant() && (entity instanceof TenantScoped tenantScopedEntity)) {
if (tenantScopedEntity.getTenantId() != null && !tenantScopedEntity.getTenantId().equals(tenantScope.getTenant())) return null;
}
return entity;
}
public void flush() {
this.entityManager.flush();
}
public void setFlushMode(FlushModeType flushMode) {
this.entityManager.setFlushMode(flushMode);
}
public FlushModeType getFlushMode() {
return this.entityManager.getFlushMode();
}
public void clear() {
this.entityManager.clear();
}
public void enableTenantFilters() throws InvalidApplicationException {
if (!tenantScope.isSet()) return;
if(!tenantScope.isDefaultTenant()) {
this.entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, tenantScope.getTenant().toString());
} else {
this.entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
}
public void disableTenantFilters(){
this.entityManager
.unwrap(Session.class)
.disableFilter(TenantScopedBaseEntity.TENANT_FILTER);
this.entityManager
.unwrap(Session.class)
.disableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
public EntityManager getEntityManager() {
return entityManager;
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
}

View File

@ -1,97 +0,0 @@
package gr.cite.notification.data;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.notification.authorization.Permission;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.scope.tenant.TenantScoped;
import gr.cite.tools.exception.MyForbiddenException;
import org.hibernate.Session;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import java.util.UUID;
@Service
@Scope
public class TenantScopedEntityManager {
@PersistenceContext
private EntityManager entityManager;
private final AuthorizationService authorizationService;
private final TenantScope tenantScope;
public TenantScopedEntityManager(AuthorizationService authorizationService, TenantScope tenantScope) {
this.authorizationService = authorizationService;
this.tenantScope = tenantScope;
}
public int getBulkSize() {
Session session = this.entityManager.unwrap(Session.class);
return session.getJdbcBatchSize();
}
public void setBulkSize(int size) {
Session session = this.entityManager.unwrap(Session.class);
session.setJdbcBatchSize(size);
}
public Query createQuery(String query){
return this.entityManager.createQuery(query);
}
public void persist(Object entity) {
this.entityManager.persist(entity);
}
public <T> T merge(T entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) {
boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant);
final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null;
if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering");
}
return this.entityManager.merge(entity);
}
public void remove(Object entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) {
final UUID tenantId = tenantScope.getTenant();
if (!tenantId.equals(((TenantScoped) entity).getTenantId())) throw new MyForbiddenException("tenant tampering");
}
this.entityManager.remove(entity);
}
public <T> T find(Class<T> entityClass, Object primaryKey) throws InvalidApplicationException {
T entity = this.entityManager.find(entityClass, primaryKey);
if (tenantScope.isMultitenant() && (entity instanceof TenantScoped)) {
boolean isAllowedNoTenant = authorizationService.authorize(Permission.AllowNoTenant);
final UUID tenantId = !isAllowedNoTenant ? tenantScope.getTenant() : null;
if (!isAllowedNoTenant && !tenantId.equals(((TenantScoped) entity).getTenantId())) return null;
}
return entity;
}
public void flush() {
this.entityManager.flush();
}
public void setFlushMode(FlushModeType flushMode) {
this.entityManager.setFlushMode(flushMode);
}
public FlushModeType getFlushMode() {
return this.entityManager.getFlushMode();
}
public void clear() {
this.entityManager.clear();
}
}

View File

@ -1,48 +1,43 @@
package gr.cite.notification.data.tenant; package gr.cite.notification.data.tenant;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import jakarta.persistence.EntityManager;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.hibernate.Session; import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
@Aspect @Aspect
@Component @Component
public class TenantFilterAspect { public class TenantFilterAspect {
private final TenantScope tenantScope; private final TenantScope tenantScope;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ApplicationContext applicationContext;
@Autowired @Autowired
public TenantFilterAspect( public TenantFilterAspect(
TenantScope tenantScope, TenantScope tenantScope
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
ApplicationContext applicationContext
) { ) {
this.tenantScope = tenantScope; this.tenantScope = tenantScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.applicationContext = applicationContext;
} }
@AfterReturning( @AfterReturning(
pointcut="bean(entityManagerFactory) && execution(* createEntityManager(..))", pointcut = "bean(entityManagerFactory) && execution(* createEntityManager(..))",
returning="retVal") returning = "retVal")
public void getSessionAfter(JoinPoint joinPoint, Object retVal) throws InvalidApplicationException { public void getSessionAfter(JoinPoint joinPoint, Object retVal) throws InvalidApplicationException {
if (retVal != null && EntityManager.class.isInstance(retVal) && tenantScope.isSet() && tenantScope.isMultitenant()) { if (retVal instanceof EntityManager && tenantScope.isSet()) {
Session session = ((EntityManager) retVal).unwrap(Session.class); Session session = ((EntityManager) retVal).unwrap(Session.class);
session.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString()); if(!tenantScope.isDefaultTenant()) {
session
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, tenantScope.getTenant().toString());
} else {
session
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
}
} }
} }

View File

@ -2,33 +2,44 @@ package gr.cite.notification.data.tenant;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.scope.tenant.TenantScoped; import gr.cite.notification.common.scope.tenant.TenantScoped;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyForbiddenException; import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.management.InvalidApplicationException; import javax.management.InvalidApplicationException;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import java.util.UUID; import java.util.UUID;
public class TenantListener { public class TenantListener {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantListener.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantListener.class));
private final TenantScope tenantScope; private final TenantScope tenantScope;
private final ErrorThesaurusProperties errors;
@Autowired @Autowired
public TenantListener( public TenantListener(
TenantScope tenantScope TenantScope tenantScope, ErrorThesaurusProperties errors
) { ) {
this.tenantScope = tenantScope; this.tenantScope = tenantScope;
this.errors = errors;
} }
@PrePersist @PrePersist
public void setTenantOnCreate(TenantScoped entity) throws InvalidApplicationException { public void setTenantOnCreate(TenantScoped entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant()) { if (tenantScope.isMultitenant()) {
final UUID tenantId = tenantScope.getTenant(); if (entity.getTenantId() != null && (this.tenantScope.isDefaultTenant() || entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) {
entity.setTenantId(tenantId); logger.error("somebody tried to set not login tenant");
throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
}
if (!tenantScope.isDefaultTenant()) {
final UUID tenantId = tenantScope.getTenant();
entity.setTenantId(tenantId);
}
} else { } else {
entity.setTenantId(null); entity.setTenantId(null);
} }
@ -38,22 +49,30 @@ public class TenantListener {
@PreRemove @PreRemove
public void setTenantOnUpdate(TenantScoped entity) throws InvalidApplicationException { public void setTenantOnUpdate(TenantScoped entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant()) { if (tenantScope.isMultitenant()) {
if (entity.getTenantId() == null) { if (!tenantScope.isDefaultTenant()) {
logger.error("somebody tried to set null tenant"); if (entity.getTenantId() == null) {
throw new MyForbiddenException("tenant tampering"); logger.error("somebody tried to set null tenant");
} throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
if (entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) { }
logger.error("somebody tried to change an entries tenant"); if (entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) {
throw new MyForbiddenException("tenant tampering"); logger.error("somebody tried to change an entries tenant");
} throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
}
final UUID tenantId = tenantScope.getTenant(); final UUID tenantId = tenantScope.getTenant();
entity.setTenantId(tenantId); entity.setTenantId(tenantId);
} else {
if (entity.getTenantId() != null) {
logger.error("somebody tried to set null tenant");
throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
}
}
} else { } else {
if (entity.getTenantId() != null) { if (entity.getTenantId() != null && (!this.tenantScope.isDefaultTenant() ||entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) {
logger.error("somebody tried to set non null tenant"); logger.error("somebody tried to change an entries tenant");
throw new MyForbiddenException("tenant tampering"); throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
} }
} }
} }
} }

View File

@ -1,15 +1,13 @@
package gr.cite.notification.data.tenant; package gr.cite.notification.data.tenant;
import gr.cite.notification.common.scope.tenant.TenantScoped; import gr.cite.notification.common.scope.tenant.TenantScoped;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef; import org.hibernate.annotations.ParamDef;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID; import java.util.UUID;
@ -17,23 +15,20 @@ import java.util.UUID;
//@Getter //@Getter
//@Setter //@Setter
//@NoArgsConstructor //@NoArgsConstructor
@FilterDef(name = TenantScopedBaseEntity.tenantFilter, parameters = {@ParamDef(name = TenantScopedBaseEntity.tenantFilterTenantParam, type = String.class)}) @FilterDef(name = TenantScopedBaseEntity.TENANT_FILTER, parameters = {@ParamDef(name = TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, type = String.class)})
@Filter(name = "tenantFilter", condition = "tenant = (cast(:tenantId as uuid))") @FilterDef(name = TenantScopedBaseEntity.DEFAULT_TENANT_FILTER)
@Filter(name = TenantScopedBaseEntity.DEFAULT_TENANT_FILTER, condition = "(tenant = tenant is null)")
@Filter(name = TenantScopedBaseEntity.TENANT_FILTER, condition = "(tenant = (cast(:tenantId as uuid)) or tenant is null)")
@EntityListeners(TenantListener.class) @EntityListeners(TenantListener.class)
public abstract class TenantScopedBaseEntity implements TenantScoped, Serializable { public abstract class TenantScopedBaseEntity implements TenantScoped, Serializable {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public static final String TENANT_FILTER = "tenantFilter";
public static final String DEFAULT_TENANT_FILTER = "defaultTenantFilter";
public static final String TENANT_FILTER_TENANT_PARAM = "tenantId";
public static final String tenantFilter = "tenantFilter"; @Column(name = "tenant", columnDefinition = "uuid", nullable = true)
public static final String tenantFilterTenantParam = "tenantId";
@Column(name = "tenant", columnDefinition = "uuid")
private UUID tenantId; private UUID tenantId;
public static final String _tenantId = "tenantId"; public static final String _tenantId = "tenantId";
public UUID getTenantId() { public UUID getTenantId() {
return tenantId; return tenantId;
} }
@ -42,5 +37,5 @@ public abstract class TenantScopedBaseEntity implements TenantScoped, Serializab
public void setTenantId(UUID tenantId) { public void setTenantId(UUID tenantId) {
this.tenantId = tenantId; this.tenantId = tenantId;
} }
} }

View File

@ -15,6 +15,8 @@ public class ErrorThesaurusProperties {
private ErrorDescription singleTenantConfigurationPerTypeSupported; private ErrorDescription singleTenantConfigurationPerTypeSupported;
private ErrorDescription incompatibleTenantConfigurationTypes; private ErrorDescription incompatibleTenantConfigurationTypes;
private ErrorDescription overlappingTenantConfigurationNotifierList; private ErrorDescription overlappingTenantConfigurationNotifierList;
private ErrorDescription tenantNotAllowed;
private ErrorDescription tenantTampering;
public ErrorDescription getHashConflict() { public ErrorDescription getHashConflict() {
return hashConflict; return hashConflict;
@ -89,4 +91,19 @@ public class ErrorThesaurusProperties {
this.overlappingTenantConfigurationNotifierList = overlappingTenantConfigurationNotifierList; this.overlappingTenantConfigurationNotifierList = overlappingTenantConfigurationNotifierList;
} }
public ErrorDescription getTenantNotAllowed() {
return tenantNotAllowed;
}
public void setTenantNotAllowed(ErrorDescription tenantNotAllowed) {
this.tenantNotAllowed = tenantNotAllowed;
}
public ErrorDescription getTenantTampering() {
return tenantTampering;
}
public void setTenantTampering(ErrorDescription tenantTampering) {
this.tenantTampering = tenantTampering;
}
} }

View File

@ -1,5 +1,6 @@
package gr.cite.notification.integrationevent; package gr.cite.notification.integrationevent;
import gr.cite.notification.data.QueueOutboxEntity;
import gr.cite.notification.integrationevent.outbox.OutboxProperties; import gr.cite.notification.integrationevent.outbox.OutboxProperties;
import gr.cite.notification.integrationevent.outbox.OutboxRepositoryImpl; import gr.cite.notification.integrationevent.outbox.OutboxRepositoryImpl;
import gr.cite.queueoutbox.IntegrationEventContextCreator; import gr.cite.queueoutbox.IntegrationEventContextCreator;
@ -56,7 +57,11 @@ public class OutboxIntegrationEventConfigurer extends OutboxConfigurer {
@Bean @Bean
public IntegrationEventContextCreator integrationEventContextCreator() { public IntegrationEventContextCreator integrationEventContextCreator() {
return (message) -> new IntegrationEventContextImpl(); return (message) -> {
IntegrationEventContextImpl integrationEventContext = new IntegrationEventContextImpl();
if (message instanceof QueueOutboxEntity) integrationEventContext.setTenant(((QueueOutboxEntity)message).getTenantId());
return integrationEventContext;
};
} }
@Bean @Bean

View File

@ -1,6 +1,7 @@
package gr.cite.notification.integrationevent.inbox; package gr.cite.notification.integrationevent.inbox;
import gr.cite.commons.web.oidc.principal.MyPrincipal; import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtClaimNames;
@ -22,12 +23,15 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
this.isAuthenticated = isAuthenticated; this.isAuthenticated = isAuthenticated;
} }
public static InboxPrincipal build(IntegrationEventProperties properties) { public static InboxPrincipal build(IntegrationEventProperties properties, ClaimExtractorProperties claimExtractorProperties) {
InboxPrincipal inboxPrincipal = new InboxPrincipal(true, "IntegrationEventQueueAppId"); InboxPrincipal inboxPrincipal = new InboxPrincipal(true, "IntegrationEventQueueAppId");
inboxPrincipal.put("client_id", properties.getAppId()); List<ClaimExtractorProperties.KeyPath> clientKey = claimExtractorProperties.getMapping().getOrDefault("Client", null);
inboxPrincipal.put(clientKey != null && clientKey.getFirst() != null ? clientKey.getFirst().getType() : "client_id", properties.getAppId());
inboxPrincipal.put("active", "true"); inboxPrincipal.put("active", "true");
inboxPrincipal.put("nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString()); List<ClaimExtractorProperties.KeyPath> notBeforeKey = claimExtractorProperties.getMapping().getOrDefault("NotBefore", null);
inboxPrincipal.put("exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString()); inboxPrincipal.put(notBeforeKey != null && notBeforeKey.getFirst() != null ? notBeforeKey.getFirst().getType() :"nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString());
List<ClaimExtractorProperties.KeyPath> expiresAt = claimExtractorProperties.getMapping().getOrDefault("ExpiresAt", null);
inboxPrincipal.put(expiresAt != null && expiresAt.getFirst() != null ? expiresAt.getFirst().getType() :"exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString());
return inboxPrincipal; return inboxPrincipal;
} }
@ -45,7 +49,10 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
public List<String> getClaimAsStringList(String claim) { public List<String> getClaimAsStringList(String claim) {
if (claims == null) if (claims == null)
return null; return null;
return this.getClaimAsStringList(claim); if (this.claims.containsKey(claim)){
return List.of(this.claims.get(claim).toString());
}
return null;
} }
@Override @Override

View File

@ -4,6 +4,7 @@ import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.fake.FakeRequestScope; import gr.cite.notification.common.scope.fake.FakeRequestScope;
import gr.cite.notification.data.QueueInboxEntity; import gr.cite.notification.data.QueueInboxEntity;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.integrationevent.TrackedEvent; import gr.cite.notification.integrationevent.TrackedEvent;
import gr.cite.notification.integrationevent.inbox.notify.NotifyIntegrationEventHandler; import gr.cite.notification.integrationevent.inbox.notify.NotifyIntegrationEventHandler;
import gr.cite.notification.integrationevent.inbox.tenantremoval.TenantRemovalIntegrationEventHandler; import gr.cite.notification.integrationevent.inbox.tenantremoval.TenantRemovalIntegrationEventHandler;
@ -16,6 +17,7 @@ import gr.cite.queueinbox.entity.QueueInboxStatus;
import gr.cite.queueinbox.repository.CandidateInfo; import gr.cite.queueinbox.repository.CandidateInfo;
import gr.cite.queueinbox.repository.InboxRepository; import gr.cite.queueinbox.repository.InboxRepository;
import gr.cite.queueinbox.task.MessageOptions; import gr.cite.queueinbox.task.MessageOptions;
import gr.cite.rabbitmq.IntegrationEventMessageConstants;
import gr.cite.rabbitmq.consumer.InboxCreatorParams; import gr.cite.rabbitmq.consumer.InboxCreatorParams;
import gr.cite.tools.data.query.Ordering; import gr.cite.tools.data.query.Ordering;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
@ -241,7 +243,14 @@ public class InboxRepositoryImpl implements InboxRepository {
QueueInboxEntity queueMessage = new QueueInboxEntity(); QueueInboxEntity queueMessage = new QueueInboxEntity();
queueMessage.setId(UUID.randomUUID()); queueMessage.setId(UUID.randomUUID());
queueMessage.setTenantId(null); Object tenantId = inboxCreatorParams.getHeaders() != null ? inboxCreatorParams.getHeaders().getOrDefault(IntegrationEventMessageConstants.TENANT, null) : null;
if (tenantId instanceof UUID) queueMessage.setTenantId((UUID) tenantId);
else if (tenantId instanceof String) {
try {
queueMessage.setTenantId(UUID.fromString((String) tenantId));
} catch (Exception e) {
}
}
queueMessage.setExchange(this.inboxProperties.getExchange()); queueMessage.setExchange(this.inboxProperties.getExchange());
queueMessage.setRoute(inboxCreatorParams.getRoutingKey()); queueMessage.setRoute(inboxCreatorParams.getRoutingKey());
queueMessage.setQueue(inboxCreatorParams.getQueueName()); queueMessage.setQueue(inboxCreatorParams.getQueueName());
@ -269,6 +278,10 @@ public class InboxRepositoryImpl implements InboxRepository {
entityManager = entityManagerFactory.createEntityManager(); entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction(); transaction = entityManager.getTransaction();
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
transaction.begin(); transaction.begin();
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
@ -278,7 +291,7 @@ public class InboxRepositoryImpl implements InboxRepository {
logger.warn("Could not lookup queue inbox {} to process. Continuing...", candidateInfo.getId()); logger.warn("Could not lookup queue inbox {} to process. Continuing...", candidateInfo.getId());
} else { } else {
EventProcessingStatus status = this.processMessage(queueInboxMessage.getRoute(), queueInboxMessage.getMessageId().toString(), queueInboxMessage.getApplicationId(), queueInboxMessage.getMessage()); EventProcessingStatus status = this.processMessage(queueInboxMessage);
switch (status) { switch (status) {
case Success: { case Success: {
queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL); queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL);
@ -321,38 +334,39 @@ public class InboxRepositoryImpl implements InboxRepository {
return success; return success;
} }
private EventProcessingStatus processMessage(String routingKey, String messageId, String appId, String message) { private EventProcessingStatus processMessage(QueueInboxEntity queueInboxMessage) {
IntegrationEventHandler handler; IntegrationEventHandler handler;
logger.debug("Processing message with routing key '{}'", routingKey); logger.debug("Processing message with routing key '{}'", queueInboxMessage.getRoute());
if (this.routingKeyMatched(routingKey, this.inboxProperties.getNotifyTopic())) if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getTenantRemovalTopic()))
handler = this.applicationContext.getBean(NotifyIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getTenantRemovalTopic()))
handler = this.applicationContext.getBean(TenantRemovalIntegrationEventHandler.class); handler = this.applicationContext.getBean(TenantRemovalIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getTenantTouchedTopic())) else if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getTenantTouchedTopic()))
handler = this.applicationContext.getBean(TenantTouchedIntegrationEventHandler.class); handler = this.applicationContext.getBean(TenantTouchedIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserRemovalTopic())) else if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getUserRemovalTopic()))
handler = this.applicationContext.getBean(UserRemovalIntegrationEventHandler.class); handler = this.applicationContext.getBean(UserRemovalIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserTouchedTopic())) else if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getUserTouchedTopic()))
handler = this.applicationContext.getBean(UserTouchedIntegrationEventHandler.class); handler = this.applicationContext.getBean(UserTouchedIntegrationEventHandler.class);
else if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getNotifyTopic()))
handler = this.applicationContext.getBean(NotifyIntegrationEventHandler.class);
else { else {
logger.error("No handler found for message routing key '{}'. Discarding.", routingKey); logger.error("No handler found for message routing key '{}'. Discarding.", queueInboxMessage.getRoute());
handler = null; handler = null;
} }
if (handler == null) if (handler == null)
return EventProcessingStatus.Discard; return EventProcessingStatus.Discard;
IntegrationEventProperties properties = new IntegrationEventProperties(); IntegrationEventProperties properties = new IntegrationEventProperties();
properties.setAppId(appId); properties.setAppId(queueInboxMessage.getApplicationId());
properties.setMessageId(messageId); properties.setMessageId(queueInboxMessage.getMessageId().toString());
properties.setTenantId(queueInboxMessage.getTenantId());
TrackedEvent event = this.jsonHandlingService.fromJsonSafe(TrackedEvent.class, message); TrackedEvent event = this.jsonHandlingService.fromJsonSafe(TrackedEvent.class, queueInboxMessage.getMessage());
// using (LogContext.PushProperty(this._logTrackingConfig.LogTrackingContextName, @event.TrackingContextTag)) // using (LogContext.PushProperty(this._logTrackingConfig.LogTrackingContextName, @event.TrackingContextTag))
// { // {
try { try {
return handler.handle(properties, message); return handler.handle(properties, queueInboxMessage.getMessage());
} catch (Exception ex) { } catch (Exception ex) {
logger.error("problem handling event from routing key " + routingKey + ". Setting nack and continuing...", ex); logger.error("problem handling event from routing key " + queueInboxMessage.getRoute() + ". Setting nack and continuing...", ex);
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
} }
// } // }

View File

@ -1,10 +1,13 @@
package gr.cite.notification.integrationevent.inbox; package gr.cite.notification.integrationevent.inbox;
import java.util.UUID;
public class IntegrationEventProperties { public class IntegrationEventProperties {
private String messageId; private String messageId;
private String appId; private String appId;
private UUID tenantId;
public String getMessageId() { public String getMessageId() {
return messageId; return messageId;
@ -22,4 +25,11 @@ public class IntegrationEventProperties {
this.appId = appId; this.appId = appId;
} }
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
} }

View File

@ -9,8 +9,6 @@ public class NotifyIntegrationEvent extends TrackedEvent {
private UUID userId; private UUID userId;
private UUID tenantId;
private UUID notificationType; private UUID notificationType;
private NotificationContactType contactTypeHint; private NotificationContactType contactTypeHint;
@ -32,14 +30,6 @@ public class NotifyIntegrationEvent extends TrackedEvent {
this.userId = userId; this.userId = userId;
} }
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
public UUID getNotificationType() { public UUID getNotificationType() {
return notificationType; return notificationType;
} }

View File

@ -1,6 +1,7 @@
package gr.cite.notification.integrationevent.inbox.notify; package gr.cite.notification.integrationevent.inbox.notify;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.notification.audit.AuditableAction; import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.common.JsonHandlingService; import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.enums.NotificationNotifyState; import gr.cite.notification.common.enums.NotificationNotifyState;
@ -9,6 +10,7 @@ import gr.cite.notification.common.enums.NotificationTrackingState;
import gr.cite.notification.common.scope.fake.FakeRequestScope; import gr.cite.notification.common.scope.fake.FakeRequestScope;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.data.TenantEntity; import gr.cite.notification.data.TenantEntity;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.errorcode.ErrorThesaurusProperties; import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; import gr.cite.notification.integrationevent.inbox.EventProcessingStatus;
import gr.cite.notification.integrationevent.inbox.InboxPrincipal; import gr.cite.notification.integrationevent.inbox.InboxPrincipal;
@ -46,17 +48,30 @@ public class NotifyIntegrationEventHandlerImpl implements NotifyIntegrationEvent
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errors; private final ErrorThesaurusProperties errors;
private final MessageSource messageSource; private final MessageSource messageSource;
private final QueryFactory queryFactory;
public NotifyIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext, ErrorThesaurusProperties errors, MessageSource messageSource) { private final TenantScope tenantScope;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final NotifyConsistencyHandler notifyConsistencyHandler;
private final NotificationService notificationService;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
public NotifyIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ErrorThesaurusProperties errors, MessageSource messageSource, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, NotifyConsistencyHandler notifyConsistencyHandler, NotificationService notificationService, AuditService auditService, TenantEntityManager tenantEntityManager) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
this.errors = errors; this.errors = errors;
this.messageSource = messageSource; this.messageSource = messageSource;
this.queryFactory = queryFactory;
this.tenantScope = tenantScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.notifyConsistencyHandler = notifyConsistencyHandler;
this.notificationService = notificationService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
} }
@Override @Override
@ -84,72 +99,42 @@ public class NotifyIntegrationEventHandlerImpl implements NotifyIntegrationEvent
model.setProvenanceRef(event.getProvenanceRef()); model.setProvenanceRef(event.getProvenanceRef());
model.setNotifiedAt(Instant.now()); model.setNotifiedAt(Instant.now());
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
try { TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); if (tenant == null) {
TenantScope scope = this.applicationContext.getBean(TenantScope.class);
if (scope.isMultitenant() && event.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(event.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
scope.setTenant(event.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
logger.error("missing tenant from event message"); logger.error("missing tenant from event message");
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
} }
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); } else if (this.tenantScope.isMultitenant()) {
currentPrincipalResolver.push(InboxPrincipal.build(properties)); // logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
NotifyConsistencyHandler notifyConsistencyHandler = this.applicationContext.getBean(NotifyConsistencyHandler.class); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
if (!(notifyConsistencyHandler.isConsistent(new NotifyConsistencyPredicates(event.getUserId(), event.getContactTypeHint(), event.getContactHint()))))
return EventProcessingStatus.Postponed;
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
try {
NotificationService notificationService = this.applicationContext.getBean(NotificationService.class);
notificationService.persist(model, new BaseFieldSet());
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.Notification_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getUserId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
if (!(notifyConsistencyHandler.isConsistent(new NotifyConsistencyPredicates(event.getUserId(), event.getContactTypeHint(), event.getContactHint())))) {
status = EventProcessingStatus.Postponed;
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
return status;
}
notificationService.persist(model, new BaseFieldSet());
auditService.track(AuditableAction.Notification_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getUserId())
));
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
} }
return EventProcessingStatus.Success; return status;
} }
} }

View File

@ -1,5 +1,6 @@
package gr.cite.notification.integrationevent.inbox.tenantremoval; package gr.cite.notification.integrationevent.inbox.tenantremoval;
import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler; import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler;
public interface TenantRemovalIntegrationEventHandler extends IntegrationEventHandler { public interface TenantRemovalIntegrationEventHandler extends IntegrationEventHandler {

View File

@ -1,24 +1,18 @@
package gr.cite.notification.integrationevent.inbox.tenantremoval; package gr.cite.notification.integrationevent.inbox.tenantremoval;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.notification.audit.AuditableAction; import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.common.JsonHandlingService; import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.scope.fake.FakeRequestScope; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; import gr.cite.notification.integrationevent.inbox.EventProcessingStatus;
import gr.cite.notification.integrationevent.inbox.InboxPrincipal; import gr.cite.notification.integrationevent.inbox.InboxPrincipal;
import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties; import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.notification.service.tenant.TenantService; import gr.cite.notification.service.tenant.TenantService;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -32,18 +26,20 @@ public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIn
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantRemovalIntegrationEventHandlerImpl.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantRemovalIntegrationEventHandlerImpl.class));
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ApplicationContext applicationContext; private final ClaimExtractorProperties claimExtractorProperties;
private final TenantService tenantService;
private final ErrorThesaurusProperties errors; private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
private final MessageSource messageSource; private final TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler;
public TenantRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, TenantService tenantService, AuditService auditService, TenantEntityManager tenantEntityManager, TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler) {
public TenantRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext, ErrorThesaurusProperties errors, MessageSource messageSource) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext; this.currentPrincipalResolver = currentPrincipalResolver;
this.errors = errors; this.claimExtractorProperties = claimExtractorProperties;
this.messageSource = messageSource; this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.tenantRemovalConsistencyHandler = tenantRemovalConsistencyHandler;
} }
@Override @Override
@ -52,60 +48,34 @@ public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIn
if (event == null) if (event == null)
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler = this.applicationContext.getBean(TenantRemovalConsistencyHandler.class); currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId()))))
return EventProcessingStatus.Postponed;
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId())))) {
entityManager = entityManagerFactory.createEntityManager(); status = EventProcessingStatus.Postponed;
currentPrincipalResolver.pop();
transaction = entityManager.getTransaction(); return status;
transaction.begin();
try {
TenantService tenantService = this.applicationContext.getBean(TenantService.class);
tenantService.deleteAndSave(event.getId());
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.Tenant_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
transaction.commit();
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
tenantEntityManager.disableTenantFilters();
tenantService.deleteAndSave(event.getId());
auditService.track(AuditableAction.Tenant_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
} }
return null; return status;
} }
} }

View File

@ -1,5 +1,6 @@
package gr.cite.notification.integrationevent.inbox.tenanttouched; package gr.cite.notification.integrationevent.inbox.tenanttouched;
import gr.cite.notification.integrationevent.TrackedEvent; import gr.cite.notification.integrationevent.TrackedEvent;
import java.util.UUID; import java.util.UUID;

View File

@ -1,9 +1,10 @@
package gr.cite.notification.integrationevent.inbox.tenanttouched; package gr.cite.notification.integrationevent.inbox.tenanttouched;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.notification.audit.AuditableAction; import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.common.JsonHandlingService; import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.scope.fake.FakeRequestScope; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; import gr.cite.notification.integrationevent.inbox.EventProcessingStatus;
import gr.cite.notification.integrationevent.inbox.InboxPrincipal; import gr.cite.notification.integrationevent.inbox.InboxPrincipal;
import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties; import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties;
@ -12,13 +13,8 @@ import gr.cite.notification.service.tenant.TenantService;
import gr.cite.tools.auditing.AuditService; import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.validation.ValidatorFactory; import gr.cite.tools.validation.ValidatorFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -31,15 +27,23 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantTouchedIntegrationEventHandlerImpl.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantTouchedIntegrationEventHandlerImpl.class));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final ValidatorFactory validatorFactory; private final ValidatorFactory validatorFactory;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final TenantService tenantService;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
public TenantTouchedIntegrationEventHandlerImpl(ApplicationContext applicationContext, JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory) { public TenantTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, TenantService tenantService, AuditService auditService, TenantEntityManager tenantEntityManager) {
this.applicationContext = applicationContext;
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.validatorFactory = validatorFactory; this.validatorFactory = validatorFactory;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
} }
@Override @Override
@ -53,55 +57,25 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
model.setCode(event.getCode()); model.setCode(event.getCode());
this.validatorFactory.validator(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.class).validateForce(model); this.validatorFactory.validator(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.class).validateForce(model);
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; tenantEntityManager.disableTenantFilters();
try (FakeRequestScope ignored = new FakeRequestScope()) { try {
try { currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class); tenantService.persist(model, null);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction(); auditService.track(AuditableAction.Tenant_Persist, Map.ofEntries(
transaction.begin(); new AbstractMap.SimpleEntry<String, Object>("model", model)
));
try {
TenantService tenantService = this.applicationContext.getBean(TenantService.class);
tenantService.persist(model, null);
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.Tenant_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model)
));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
transaction.commit();
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
} }
return null; return status;
} }
} }

View File

@ -1,5 +1,6 @@
package gr.cite.notification.integrationevent.inbox.userremoval; package gr.cite.notification.integrationevent.inbox.userremoval;
import gr.cite.notification.integrationevent.inbox.ConsistencyPredicates; import gr.cite.notification.integrationevent.inbox.ConsistencyPredicates;
import java.util.UUID; import java.util.UUID;

View File

@ -8,7 +8,6 @@ public class UserRemovalIntegrationEvent extends TrackedEvent {
private UUID userId; private UUID userId;
private UUID tenant;
public UUID getUserId() { public UUID getUserId() {
return userId; return userId;
@ -17,12 +16,4 @@ public class UserRemovalIntegrationEvent extends TrackedEvent {
public void setUserId(UUID userId) { public void setUserId(UUID userId) {
this.userId = userId; this.userId = userId;
} }
public UUID getTenant() {
return tenant;
}
public void setTenant(UUID tenant) {
this.tenant = tenant;
}
} }

View File

@ -1,5 +1,6 @@
package gr.cite.notification.integrationevent.inbox.userremoval; package gr.cite.notification.integrationevent.inbox.userremoval;
import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler; import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler;
public interface UserRemovalIntegrationEventHandler extends IntegrationEventHandler { public interface UserRemovalIntegrationEventHandler extends IntegrationEventHandler {

View File

@ -1,11 +1,12 @@
package gr.cite.notification.integrationevent.inbox.userremoval; package gr.cite.notification.integrationevent.inbox.userremoval;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.notification.audit.AuditableAction; import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.common.JsonHandlingService; import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.scope.fake.FakeRequestScope;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.data.TenantEntity; import gr.cite.notification.data.TenantEntity;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.errorcode.ErrorThesaurusProperties; import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; import gr.cite.notification.integrationevent.inbox.EventProcessingStatus;
import gr.cite.notification.integrationevent.inbox.InboxPrincipal; import gr.cite.notification.integrationevent.inbox.InboxPrincipal;
@ -18,13 +19,8 @@ import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyValidationException; import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
@ -41,22 +37,35 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errors; private final ErrorThesaurusProperties errors;
private final MessageSource messageSource; private final MessageSource messageSource;
private final QueryFactory queryFactory;
private final TenantScope tenantScope;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final UserRemovalConsistencyHandler userRemovalConsistencyHandler;
private final UserService userService;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
public UserRemovalIntegrationEventHandlerImpl( public UserRemovalIntegrationEventHandlerImpl(
JsonHandlingService jsonHandlingService, JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext, ErrorThesaurusProperties errors,
ErrorThesaurusProperties errors, MessageSource messageSource, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, UserRemovalConsistencyHandler userRemovalConsistencyHandler, UserService userService, AuditService auditService, TenantEntityManager tenantEntityManager
MessageSource messageSource
) { ) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
this.errors = errors; this.errors = errors;
this.messageSource = messageSource; this.messageSource = messageSource;
this.queryFactory = queryFactory;
this.tenantScope = tenantScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.userRemovalConsistencyHandler = userRemovalConsistencyHandler;
this.userService = userService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
} }
@Override @Override
@ -69,73 +78,44 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
logger.debug("Handling {}", UserRemovalIntegrationEvent.class.getSimpleName()); logger.debug("Handling {}", UserRemovalIntegrationEvent.class.getSimpleName());
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
try { TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); if (tenant == null) {
TenantScope scope = this.applicationContext.getBean(TenantScope.class);
if (scope.isMultitenant() && event.getTenant() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(event.getTenant()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
scope.setTenant(event.getTenant(), tenant.getCode());
} else if (scope.isMultitenant()) {
logger.error("missing tenant from event message"); logger.error("missing tenant from event message");
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
} }
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); } else if (this.tenantScope.isMultitenant()) {
currentPrincipalResolver.push(InboxPrincipal.build(properties)); // logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
UserRemovalConsistencyHandler userRemovalConsistencyHandler = this.applicationContext.getBean(UserRemovalConsistencyHandler.class); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
if (!(userRemovalConsistencyHandler.isConsistent(new UserRemovalConsistencyPredicates(event.getUserId()))))
return EventProcessingStatus.Postponed;
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
try {
UserService userService = this.applicationContext.getBean(UserService.class);
userService.deleteAndSave(event.getUserId());
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.User_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getUserId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
transaction.commit();
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
if (!(userRemovalConsistencyHandler.isConsistent(new UserRemovalConsistencyPredicates(event.getUserId())))) {
status = EventProcessingStatus.Postponed;
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
return status;
}
userService.deleteAndSave(event.getUserId());
auditService.track(AuditableAction.User_Delete, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", event.getUserId())
));
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
} }
return EventProcessingStatus.Success;
return status;
} }
} }

View File

@ -5,7 +5,7 @@ import gr.cite.notification.common.validation.BaseValidator;
import gr.cite.notification.convention.ConventionService; import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.errorcode.ErrorThesaurusProperties; import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.notification.integrationevent.TrackedEvent; import gr.cite.notification.integrationevent.TrackedEvent;
import gr.cite.notification.model.persist.UserTouchedIntegrationEventPersist; import gr.cite.tools.validation.ValidatorFactory;
import gr.cite.tools.validation.specification.Specification; import gr.cite.tools.validation.specification.Specification;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
@ -23,22 +23,24 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
public static final String _id = "id"; public static final String _id = "id";
private UUID tenant;
private String name; private String name;
public static final String _name = "name"; public static final String _name = "name";
public static final int _nameLength = 200; public static final int _nameLength = 200;
private String subjectId;
public static final String _subjectId = "subjectId";
private UserProfile profile; private UserProfile profile;
private List<UserContactInfo> userContactInfo; private List<UserContactInfo> userContactInfo;
public static final String _userContactInfo = "userContactInfo";
private List<TenantUser> tenantUsers;
public static final String _tenantUsers = "tenantUsers";
private List<UserCredential> credentials;
public static final String _credentials = "credentials";
public UUID getId() { public UUID getId() {
return id; return id;
} }
@ -47,14 +49,6 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
this.id = id; this.id = id;
} }
public UUID getTenant() {
return tenant;
}
public void setTenant(UUID tenant) {
this.tenant = tenant;
}
public String getName() { public String getName() {
return name; return name;
} }
@ -63,12 +57,20 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
this.name = name; this.name = name;
} }
public String getSubjectId() { public List<TenantUser> getTenantUsers() {
return subjectId; return tenantUsers;
} }
public void setSubjectId(String subjectId) { public void setTenantUsers(List<TenantUser> tenantUsers) {
this.subjectId = subjectId; this.tenantUsers = tenantUsers;
}
public List<UserCredential> getCredentials() {
return credentials;
}
public void setCredentials(List<UserCredential> credentials) {
this.credentials = credentials;
} }
public UserProfile getProfile() { public UserProfile getProfile() {
@ -124,9 +126,15 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
private ContactInfoType type; private ContactInfoType type;
public static final String _type = "type";
private String value; private String value;
private int ordinal; public static final String _value = "value";
private Integer ordinal;
public static final String _ordinal = "ordinal";
public ContactInfoType getType() { public ContactInfoType getType() {
return type; return type;
@ -144,16 +152,135 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
this.value = value; this.value = value;
} }
public int getOrdinal() { public Integer getOrdinal() {
return ordinal; return ordinal;
} }
public void setOrdinal(int ordinal) { public void setOrdinal(Integer ordinal) {
this.ordinal = ordinal; this.ordinal = ordinal;
} }
@Component(UserTouchedIntegrationUserContactInfoEventValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class UserTouchedIntegrationUserContactInfoEventValidator extends BaseValidator<UserContactInfo> {
public static final String ValidatorName = "UserTouchedIntegrationUserContactInfoEventValidator";
private final MessageSource messageSource;
protected UserTouchedIntegrationUserContactInfoEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
super(conventionService, errors);
this.messageSource = messageSource;
}
@Override
protected Class<UserContactInfo> modelClass() {
return UserContactInfo.class;
}
@Override
protected List<Specification> specifications(UserContactInfo item) {
return Arrays.asList(
this.spec()
.must(() -> !this.isEmpty(item.getValue()))
.failOn(UserContactInfo._value).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserContactInfo._value}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isNull(item.getType()))
.failOn(UserContactInfo._type).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserContactInfo._type}, LocaleContextHolder.getLocale())),
this.spec()
.must(() -> !this.isNull(item.getOrdinal()))
.failOn(UserContactInfo._ordinal).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserContactInfo._ordinal}, LocaleContextHolder.getLocale()))
);
}
}
} }
@Component(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.ValidatorName) public static class UserCredential {
private String subjectId;
public static final String _subjectId = "subjectId";
public String getSubjectId() {
return subjectId;
}
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}
@Component(UserTouchedIntegrationUserCredentialEventValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class UserTouchedIntegrationUserCredentialEventValidator extends BaseValidator<UserCredential> {
public static final String ValidatorName = "UserTouchedIntegrationUserCredentialEventValidator";
private final MessageSource messageSource;
protected UserTouchedIntegrationUserCredentialEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
super(conventionService, errors);
this.messageSource = messageSource;
}
@Override
protected Class<UserCredential> modelClass() {
return UserCredential.class;
}
@Override
protected List<Specification> specifications(UserCredential item) {
return Arrays.asList(
this.spec()
.must(() -> !this.isEmpty(item.getSubjectId()))
.failOn(UserCredential._subjectId).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserCredential._subjectId}, LocaleContextHolder.getLocale()))
);
}
}
}
public static class TenantUser {
private UUID tenant;
public static final String _tenant = "tenant";
public UUID getTenant() {
return tenant;
}
public void setTenant(UUID tenant) {
this.tenant = tenant;
}
@Component(UserTouchedIntegrationTenantUserEventValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class UserTouchedIntegrationTenantUserEventValidator extends BaseValidator<TenantUser> {
public static final String ValidatorName = "UserTouchedIntegrationTenantUserEventValidator";
private final MessageSource messageSource;
protected UserTouchedIntegrationTenantUserEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
super(conventionService, errors);
this.messageSource = messageSource;
}
@Override
protected Class<TenantUser> modelClass() {
return TenantUser.class;
}
@Override
protected List<Specification> specifications(TenantUser item) {
return Arrays.asList(
this.spec()
.must(() -> !this.isNull(item.getTenant()))
.failOn(TenantUser._tenant).failWith(messageSource.getMessage("Validation_Required", new Object[]{TenantUser._tenant}, LocaleContextHolder.getLocale()))
);
}
}
}
@Component(UserTouchedIntegrationEventValidator.ValidatorName)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class UserTouchedIntegrationEventValidator extends BaseValidator<UserTouchedIntegrationEvent> { public static class UserTouchedIntegrationEventValidator extends BaseValidator<UserTouchedIntegrationEvent> {
@ -161,9 +288,12 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
private final MessageSource messageSource; private final MessageSource messageSource;
protected UserTouchedIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) { private final ValidatorFactory validatorFactory;
protected UserTouchedIntegrationEventValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource, ValidatorFactory validatorFactory) {
super(conventionService, errors); super(conventionService, errors);
this.messageSource = messageSource; this.messageSource = messageSource;
this.validatorFactory = validatorFactory;
} }
@Override @Override
@ -183,12 +313,14 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
.failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._name}, LocaleContextHolder.getLocale())), .failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._name}, LocaleContextHolder.getLocale())),
this.spec() this.spec()
.iff(() -> !this.isEmpty(item.getName())) .iff(() -> !this.isEmpty(item.getName()))
.must(() -> this.lessEqualLength(item.getName(), UserTouchedIntegrationEventPersist._nameLength)) .must(() -> this.lessEqualLength(item.getName(), UserTouchedIntegrationEvent._nameLength))
.failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_MaxLength", new Object[]{UserTouchedIntegrationEvent._name}, LocaleContextHolder.getLocale())), .failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_MaxLength", new Object[]{UserTouchedIntegrationEvent._name}, LocaleContextHolder.getLocale())),
this.spec() this.navSpec()
.must(() -> !this.isEmpty(item.getSubjectId())) .iff(() -> !this.isListNullOrEmpty(item.getUserContactInfo()))
.failOn(UserTouchedIntegrationEvent._subjectId).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._subjectId}, LocaleContextHolder.getLocale())) .on(UserTouchedIntegrationEvent._userContactInfo)
); .over(item.getUserContactInfo())
.using((i) -> this.validatorFactory.validator(UserContactInfo.UserTouchedIntegrationUserContactInfoEventValidator.class))
);
} }
} }
} }

View File

@ -1,5 +1,6 @@
package gr.cite.notification.integrationevent.inbox.usertouched; package gr.cite.notification.integrationevent.inbox.usertouched;
import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler; import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler;
public interface UserTouchedIntegrationEventHandler extends IntegrationEventHandler { public interface UserTouchedIntegrationEventHandler extends IntegrationEventHandler {

View File

@ -1,11 +1,12 @@
package gr.cite.notification.integrationevent.inbox.usertouched; package gr.cite.notification.integrationevent.inbox.usertouched;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver; import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorProperties;
import gr.cite.notification.audit.AuditableAction; import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.common.JsonHandlingService; import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.scope.fake.FakeRequestScope;
import gr.cite.notification.common.scope.tenant.TenantScope; import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.data.TenantEntity; import gr.cite.notification.data.TenantEntity;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.integrationevent.inbox.EventProcessingStatus; import gr.cite.notification.integrationevent.inbox.EventProcessingStatus;
import gr.cite.notification.integrationevent.inbox.InboxPrincipal; import gr.cite.notification.integrationevent.inbox.InboxPrincipal;
import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties; import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties;
@ -17,13 +18,8 @@ import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.fieldset.BaseFieldSet; import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.validation.ValidatorFactory; import gr.cite.tools.validation.ValidatorFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -36,19 +32,30 @@ public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegr
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserTouchedIntegrationEventHandlerImpl.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserTouchedIntegrationEventHandlerImpl.class));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final ValidatorFactory validatorFactory; private final ValidatorFactory validatorFactory;
private final QueryFactory queryFactory;
private final TenantScope tenantScope;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final UserService userService;
private final AuditService auditService;
private final TenantEntityManager tenantEntityManager;
public UserTouchedIntegrationEventHandlerImpl( public UserTouchedIntegrationEventHandlerImpl(
JsonHandlingService jsonHandlingService, JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext, ValidatorFactory validatorFactory, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, UserService userService, AuditService auditService, TenantEntityManager tenantEntityManager) {
ValidatorFactory validatorFactory) {
this.jsonHandlingService = jsonHandlingService; this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
this.validatorFactory = validatorFactory; this.validatorFactory = validatorFactory;
this.queryFactory = queryFactory;
this.tenantScope = tenantScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.userService = userService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
} }
@Override @Override
@ -61,69 +68,37 @@ public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegr
this.validatorFactory.validator(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.class).validateForce(event); this.validatorFactory.validator(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null; EventProcessingStatus status = EventProcessingStatus.Success;
EntityTransaction transaction = null; try {
try (FakeRequestScope ignored = new FakeRequestScope()) { if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
try { TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class); if (tenant == null) {
TenantScope scope = this.applicationContext.getBean(TenantScope.class);
if (scope.isMultitenant() && event.getTenant() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(event.getTenant()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
scope.setTenant(event.getTenant(), tenant.getCode());
} else if (scope.isMultitenant()) {
logger.error("missing tenant from event message"); logger.error("missing tenant from event message");
return EventProcessingStatus.Error; return EventProcessingStatus.Error;
} }
// this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
// ValidationService validator = this.applicationContext.getBean(ValidationService.class); } else if (this.tenantScope.isMultitenant()) {
// validator.validateForce(model); // logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class); this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
currentPrincipalResolver.push(InboxPrincipal.build(properties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
try {
UserService userService = this.applicationContext.getBean(UserService.class);
userService.persist(event, null);
AuditService auditService = this.applicationContext.getBean(AuditService.class);
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
currentPrincipalResolver.pop();
}
} catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null)
entityManager.close();
} }
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
userService.persist(event, null);
auditService.track(AuditableAction.User_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", event)
));
} catch (Exception ex) { } catch (Exception ex) {
status = EventProcessingStatus.Error;
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
} finally {
currentPrincipalResolver.pop();
tenantScope.removeTempTenant(this.tenantEntityManager.getEntityManager());
} }
return EventProcessingStatus.Success;
return status;
} }
} }

View File

@ -7,52 +7,13 @@ public class OutboxProperties {
private final String exchange; private final String exchange;
private final String tenantTouchTopic;
private final String tenantRemovalTopic; public OutboxProperties(String exchange
private final String userTouchTopic;
private final String userRemovalTopic;
private final String notifyTopic;
public OutboxProperties(String exchange,
String tenantTouchTopic,
String tenantRemovalTopic,
String userTouchTopic,
String userRemovalTopic,
String notifyTopic
) { ) {
this.exchange = exchange; this.exchange = exchange;
this.tenantTouchTopic = tenantTouchTopic;
this.tenantRemovalTopic = tenantRemovalTopic;
this.userTouchTopic = userTouchTopic;
this.userRemovalTopic = userRemovalTopic;
this.notifyTopic = notifyTopic;
} }
public String getExchange() { public String getExchange() {
return exchange; return exchange;
} }
public String getTenantTouchTopic() {
return tenantTouchTopic;
}
public String getTenantRemovalTopic() {
return tenantRemovalTopic;
}
public String getUserTouchTopic() {
return userTouchTopic;
}
public String getUserRemovalTopic() {
return userRemovalTopic;
}
public String getNotifyTopic() {
return notifyTopic;
}
} }

View File

@ -20,11 +20,9 @@ import jakarta.persistence.EntityTransaction;
import jakarta.persistence.OptimisticLockException; import jakarta.persistence.OptimisticLockException;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -32,9 +30,11 @@ import java.util.stream.Collectors;
public class OutboxRepositoryImpl implements OutboxRepository { public class OutboxRepositoryImpl implements OutboxRepository {
protected final ApplicationContext applicationContext; protected final ApplicationContext applicationContext;
private final Random random = new Random();
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OutboxRepositoryImpl.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OutboxRepositoryImpl.class));
private final JsonHandlingService jsonHandlingService; private final JsonHandlingService jsonHandlingService;
private final OutboxProperties outboxProperties; private final OutboxProperties outboxProperties;
public OutboxRepositoryImpl( public OutboxRepositoryImpl(
@ -88,14 +88,17 @@ public class OutboxRepositoryImpl implements OutboxRepository {
} catch (OptimisticLockException ex) { } catch (OptimisticLockException ex) {
// we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working // we get this if/when someone else already modified the notifications. We want to essentially ignore this, and keep working
logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage()); logger.debug("Concurrency exception getting queue outbox. Skipping: {} ", ex.getMessage());
if (transaction != null) transaction.rollback(); if (transaction != null)
transaction.rollback();
candidate = null; candidate = null;
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null) transaction.rollback(); if (transaction != null)
transaction.rollback();
candidate = null; candidate = null;
} finally { } finally {
if (entityManager != null) entityManager.close(); if (entityManager != null)
entityManager.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex); logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
@ -137,10 +140,12 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit(); transaction.commit();
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
if (transaction != null) transaction.rollback(); if (transaction != null)
transaction.rollback();
success = false; success = false;
} finally { } finally {
if (entityManager != null) entityManager.close(); if (entityManager != null)
entityManager.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
@ -182,10 +187,12 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit(); transaction.commit();
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
if (transaction != null) transaction.rollback(); if (transaction != null)
transaction.rollback();
success = false; success = false;
} finally { } finally {
if (entityManager != null) entityManager.close(); if (entityManager != null)
entityManager.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
@ -237,10 +244,12 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit(); transaction.commit();
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
if (transaction != null) transaction.rollback(); if (transaction != null)
transaction.rollback();
success = false; success = false;
} finally { } finally {
if (entityManager != null) entityManager.close(); if (entityManager != null)
entityManager.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
@ -266,7 +275,7 @@ public class OutboxRepositoryImpl implements OutboxRepository {
List<QueueOutboxEntity> queueOutboxMessages = queryFactory.query(QueueOutboxQuery.class).ids(confirmedMessages).collect(); List<QueueOutboxEntity> queueOutboxMessages = queryFactory.query(QueueOutboxQuery.class).ids(confirmedMessages).collect();
if (queueOutboxMessages == null) { if (queueOutboxMessages == null) {
logger.warn("Could not lookup messages {} to process. Continuing...", String.join(",", confirmedMessages.stream().map(x -> x.toString()).collect(Collectors.toList()))); logger.warn("Could not lookup messages {} to process. Continuing...", confirmedMessages.stream().map(UUID::toString).collect(Collectors.joining(",")));
} else { } else {
for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) { for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) {
@ -281,9 +290,11 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit(); transaction.commit();
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
if (transaction != null) transaction.rollback(); if (transaction != null)
transaction.rollback();
} finally { } finally {
if (entityManager != null) entityManager.close(); if (entityManager != null)
entityManager.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
@ -308,7 +319,7 @@ public class OutboxRepositoryImpl implements OutboxRepository {
List<QueueOutboxEntity> queueOutboxMessages = queryFactory.query(QueueOutboxQuery.class).ids(nackedMessages).collect(); List<QueueOutboxEntity> queueOutboxMessages = queryFactory.query(QueueOutboxQuery.class).ids(nackedMessages).collect();
if (queueOutboxMessages == null) { if (queueOutboxMessages == null) {
logger.warn("Could not lookup messages {} to process. Continuing...", String.join(",", nackedMessages.stream().map(x -> x.toString()).collect(Collectors.toList()))); logger.warn("Could not lookup messages {} to process. Continuing...", nackedMessages.stream().map(UUID::toString).collect(Collectors.joining(",")));
} else { } else {
for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) { for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) {
@ -323,9 +334,11 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit(); transaction.commit();
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
if (transaction != null) transaction.rollback(); if (transaction != null)
transaction.rollback();
} finally { } finally {
if (entityManager != null) entityManager.close(); if (entityManager != null)
entityManager.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
@ -336,7 +349,6 @@ public class OutboxRepositoryImpl implements OutboxRepository {
public QueueOutbox create(IntegrationEvent item) { public QueueOutbox create(IntegrationEvent item) {
EntityTransaction transaction = null; EntityTransaction transaction = null;
EntityManager entityManager = null; EntityManager entityManager = null;
boolean success = false;
QueueOutboxEntity queueMessage = null; QueueOutboxEntity queueMessage = null;
try (FakeRequestScope ignored = new FakeRequestScope()) { try (FakeRequestScope ignored = new FakeRequestScope()) {
try { try {
@ -354,10 +366,11 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit(); transaction.commit();
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
if (transaction != null) transaction.rollback(); if (transaction != null)
success = false; transaction.rollback();
} finally { } finally {
if (entityManager != null) entityManager.close(); if (entityManager != null)
entityManager.close();
} }
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex); logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
@ -375,13 +388,14 @@ public class OutboxRepositoryImpl implements OutboxRepository {
} }
// UUID correlationId = UUID.randomUUID(); // UUID correlationId = UUID.randomUUID();
// if (event.getEvent() != null) event.getEvent().setTrackingContextTag(correlationId.toString()); // if (event.getEvent() != null)
// event.getEvent().setTrackingContextTag(correlationId.toString());
// event.setMessage(this.jsonHandlingService.toJsonSafe(event.getEvent())); // event.setMessage(this.jsonHandlingService.toJsonSafe(event.getEvent()));
// //this._logTrackingService.Trace(correlationId.ToString(), $"Correlating current tracking context with new correlationId {correlationId}"); // //this._logTrackingService.Trace(correlationId.ToString(), $"Correlating current tracking context with new correlationId {correlationId}");
// //
// QueueOutboxEntity queueMessage = new QueueOutboxEntity(); // QueueOutboxEntity queueMessage = new QueueOutboxEntity();
// queueMessage.setId(UUID.randomUUID()); // queueMessage.setId(UUID.randomUUID());
// queueMessage.setTenantId(null); // queueMessage.setTenantId(event.getTenantId());
// queueMessage.setExchange(this.outboxProperties.getExchange()); // queueMessage.setExchange(this.outboxProperties.getExchange());
// queueMessage.setRoute(routingKey); // queueMessage.setRoute(routingKey);
// queueMessage.setMessageId(event.getMessageId()); // queueMessage.setMessageId(event.getMessageId());

View File

@ -0,0 +1,5 @@
package gr.cite.notification.integrationevent.outbox;
public interface OutboxService {
void publish(OutboxIntegrationEvent event);
}

View File

@ -0,0 +1,34 @@
package gr.cite.notification.integrationevent.outbox;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope
public class OutboxServiceImpl implements OutboxService {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OutboxServiceImpl.class));
private final ApplicationEventPublisher eventPublisher;
public OutboxServiceImpl(
ApplicationEventPublisher eventPublisher
) {
this.eventPublisher = eventPublisher;
}
@Override
public void publish(OutboxIntegrationEvent event) {
try {
eventPublisher.publishEvent(event);
} catch (Exception ex) {
logger.error(new MapLogEntry(String.format("Could not save message ", event.getMessage())).And("message", event.getMessage()).And("ex", ex));
//Still want to skip it from processing
}
}
}

View File

@ -1,93 +0,0 @@
package gr.cite.notification.model;
import gr.cite.notification.common.enums.IsActive;
import java.time.Instant;
import java.util.UUID;
public class Language {
private UUID id;
public static final String _id = "id";
private String code;
public static final String _code = "code";
private Integer ordinal;
public static final String _ordinal = "ordinal";
private Instant createdAt;
public static final String _createdAt = "createdAt";
private Instant updatedAt;
public static final String _updatedAt = "updatedAt";
private IsActive isActive;
public static final String _isActive = "isActive";
private String hash;
public static final String _hash = "hash";
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Integer getOrdinal() {
return ordinal;
}
public void setOrdinal(Integer ordinal) {
this.ordinal = ordinal;
}
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 IsActive getIsActive() {
return isActive;
}
public void setIsActive(IsActive isActive) {
this.isActive = isActive;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
}

View File

@ -23,8 +23,8 @@ public class NotificationTemplate {
private NotificationTemplateKind kind; private NotificationTemplateKind kind;
public static final String _kind = "kind"; public static final String _kind = "kind";
private Language language; private String languageCode;
public static final String _language = "language"; public static final String _languageCode = "languageCode";
private NotificationTemplateValue value; private NotificationTemplateValue value;
public static final String _value = "value"; public static final String _value = "value";
@ -76,12 +76,12 @@ public class NotificationTemplate {
this.kind = kind; this.kind = kind;
} }
public Language getLanguage() { public String getLanguageCode() {
return language; return languageCode;
} }
public void setLanguage(Language language) { public void setLanguageCode(String languageCode) {
this.language = language; this.languageCode = languageCode;
} }
public NotificationTemplateValue getValue() { public NotificationTemplateValue getValue() {

View File

@ -0,0 +1,98 @@
package gr.cite.notification.model;
import gr.cite.notification.common.enums.IsActive;
import java.time.Instant;
import java.util.UUID;
public class TenantUser {
private UUID id;
public final static String _id = "id";
private User user;
public final static String _user = "user";
private Tenant tenant;
public final static String _tenant = "tenant";
private IsActive isActive;
public final static String _isActive = "isActive";
private Instant createdAt;
public final static String _createdAt = "createdAt";
private Instant updatedAt;
public final static String _updatedAt = "updatedAt";
private String hash;
public final static 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 User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
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

@ -1,65 +0,0 @@
package gr.cite.notification.model.builder;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.data.LanguageEntity;
import gr.cite.notification.model.Language;
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.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 LanguageBuilder extends BaseBuilder<Language, LanguageEntity> {
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
@Autowired
public LanguageBuilder(
ConventionService conventionService) {
super(conventionService, new LoggerService(LoggerFactory.getLogger(LanguageBuilder.class)));
}
public LanguageBuilder authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
@Override
public List<Language> build(FieldSet fields, List<LanguageEntity> 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<Language> models = new ArrayList<>();
for (LanguageEntity d : data) {
Language m = new Language();
if (fields.hasField(this.asIndexer(Language._id)))
m.setId(d.getId());
if (fields.hasField(this.asIndexer(Language._code)))
m.setCode(d.getCode());
if (fields.hasField(this.asIndexer(Language._ordinal)))
m.setOrdinal(d.getOrdinal());
if (fields.hasField(this.asIndexer(Language._createdAt)))
m.setCreatedAt(d.getCreatedAt());
if (fields.hasField(this.asIndexer(Language._updatedAt)))
m.setUpdatedAt(d.getUpdatedAt());
if (fields.hasField(this.asIndexer(Language._isActive)))
m.setIsActive(d.getIsActive());
if (fields.hasField(this.asIndexer(Language._hash)))
m.setHash(this.hashValue(d.getUpdatedAt()));
models.add(m);
}
this.logger.debug("build {} items", Optional.of(models).map(List::size).orElse(0));
return models;
}
}

View File

@ -5,12 +5,9 @@ import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.types.notificationtemplate.NotificationTemplateValueEntity; import gr.cite.notification.common.types.notificationtemplate.NotificationTemplateValueEntity;
import gr.cite.notification.convention.ConventionService; import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.data.NotificationTemplateEntity; import gr.cite.notification.data.NotificationTemplateEntity;
import gr.cite.notification.model.Language;
import gr.cite.notification.model.NotificationTemplate; import gr.cite.notification.model.NotificationTemplate;
import gr.cite.notification.model.Tenant; import gr.cite.notification.model.Tenant;
import gr.cite.notification.model.builder.notificationtemplate.NotificationTemplateValueBuilder; import gr.cite.notification.model.builder.notificationtemplate.NotificationTemplateValueBuilder;
import gr.cite.notification.model.notificationtemplate.NotificationTemplateValue;
import gr.cite.notification.query.LanguageQuery;
import gr.cite.notification.query.TenantQuery; import gr.cite.notification.query.TenantQuery;
import gr.cite.tools.data.builder.BuilderFactory; import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
@ -59,9 +56,6 @@ public class NotificationTemplateBuilder extends BaseBuilder<NotificationTemplat
FieldSet tenantFields = fields.extractPrefixed(this.asPrefix(NotificationTemplate._tenant)); FieldSet tenantFields = fields.extractPrefixed(this.asPrefix(NotificationTemplate._tenant));
Map<UUID, Tenant> tenantMap = this.collectTenants(tenantFields, data); Map<UUID, Tenant> tenantMap = this.collectTenants(tenantFields, data);
FieldSet languageFields = fields.extractPrefixed(this.asPrefix(NotificationTemplate._language));
Map<UUID, Language> languageMap = this.collectLanguages(languageFields, data);
List<NotificationTemplate> models = new ArrayList<>(); List<NotificationTemplate> models = new ArrayList<>();
for(NotificationTemplateEntity d : data){ for(NotificationTemplateEntity d : data){
NotificationTemplate m = new NotificationTemplate(); NotificationTemplate m = new NotificationTemplate();
@ -69,7 +63,7 @@ public class NotificationTemplateBuilder extends BaseBuilder<NotificationTemplat
if(fields.hasField(this.asIndexer(NotificationTemplate._channel))) m.setChannel(d.getChannel()); if(fields.hasField(this.asIndexer(NotificationTemplate._channel))) m.setChannel(d.getChannel());
if(fields.hasField(this.asIndexer(NotificationTemplate._notificationType))) m.setNotificationType(d.getNotificationType()); if(fields.hasField(this.asIndexer(NotificationTemplate._notificationType))) m.setNotificationType(d.getNotificationType());
if(fields.hasField(this.asIndexer(NotificationTemplate._kind))) m.setKind(d.getKind()); if(fields.hasField(this.asIndexer(NotificationTemplate._kind))) m.setKind(d.getKind());
if (!languageFields.isEmpty() && languageMap != null && languageMap.containsKey(d.getLanguageId())) m.setLanguage(languageMap.get(d.getLanguageId())); if(fields.hasField(this.asIndexer(NotificationTemplate._languageCode))) m.setLanguageCode(d.getLanguageCode());
if (!valueFields.isEmpty() && d.getValue() != null){ if (!valueFields.isEmpty() && d.getValue() != null){
NotificationTemplateValueEntity value = this.jsonHandlingService.fromJsonSafe(NotificationTemplateValueEntity.class, d.getValue()); NotificationTemplateValueEntity value = this.jsonHandlingService.fromJsonSafe(NotificationTemplateValueEntity.class, d.getValue());
m.setValue(this.builderFactory.builder(NotificationTemplateValueBuilder.class).authorize(this.authorize).build(valueFields, value)); m.setValue(this.builderFactory.builder(NotificationTemplateValueBuilder.class).authorize(this.authorize).build(valueFields, value));
@ -85,35 +79,6 @@ public class NotificationTemplateBuilder extends BaseBuilder<NotificationTemplat
return models; return models;
} }
private Map<UUID, Language> collectLanguages(FieldSet fields, List<NotificationTemplateEntity> datas) throws MyApplicationException {
if (fields.isEmpty() || datas.isEmpty()) return null;
this.logger.debug("checking related - {}", NotificationTemplate.class.getSimpleName());
Map<UUID, Language> itemMap = null;
if (!fields.hasOtherField(this.asIndexer(Language._id))) {
itemMap = this.asEmpty(
datas.stream().map(x -> x.getLanguageId()).distinct().collect(Collectors.toList()),
x -> {
Language item = new Language();
item.setId(x);
return item;
},
x -> x.getId());
} else {
FieldSet clone = new BaseFieldSet(fields.getFields()).ensure(Language._id);
LanguageQuery q = this.queryFactory.query(LanguageQuery.class).authorize(this.authorize).ids(datas.stream().map(x -> x.getLanguageId()).distinct().collect(Collectors.toList()));
itemMap = this.builderFactory.builder(LanguageBuilder.class).authorize(this.authorize).asForeignKey(q, clone, x -> x.getId());
}
if (!fields.hasField(Tenant._id)) {
itemMap.values().stream().filter(x -> x != null).map(x -> {
x.setId(null);
return x;
}).collect(Collectors.toList());
}
return itemMap;
}
private Map<UUID, Tenant> collectTenants(FieldSet fields, List<NotificationTemplateEntity> datas) throws MyApplicationException { private Map<UUID, Tenant> collectTenants(FieldSet fields, List<NotificationTemplateEntity> datas) throws MyApplicationException {
if (fields.isEmpty() || datas.isEmpty()) return null; if (fields.isEmpty() || datas.isEmpty()) return null;
this.logger.debug("checking related - {}", NotificationTemplate.class.getSimpleName()); this.logger.debug("checking related - {}", NotificationTemplate.class.getSimpleName());

View File

@ -2,7 +2,7 @@ package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.InAppNotificationEntity; import gr.cite.notification.data.InAppNotificationEntity;
import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.InAppNotificationQuery; import gr.cite.notification.query.InAppNotificationQuery;
import gr.cite.tools.data.deleter.Deleter; import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,19 +27,16 @@ import java.util.stream.Collectors;
public class InAppNotificationDeleter implements Deleter { public class InAppNotificationDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationDeleter.class));
private final TenantScopedEntityManager entityManager; private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory; protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory;
@Autowired @Autowired
public InAppNotificationDeleter( public InAppNotificationDeleter(
TenantScopedEntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory, QueryFactory queryFactory
DeleterFactory deleterFactory
) { ) {
this.entityManager = entityManager; this.entityManager = entityManager;
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
this.deleterFactory = deleterFactory;
} }
public void deleteAndSaveByIds(List<UUID> ids) throws InvalidApplicationException { public void deleteAndSaveByIds(List<UUID> ids) throws InvalidApplicationException {

View File

@ -2,7 +2,7 @@ package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.NotificationEntity; import gr.cite.notification.data.NotificationEntity;
import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.NotificationQuery; import gr.cite.notification.query.NotificationQuery;
import gr.cite.tools.data.deleter.Deleter; import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,13 +27,13 @@ import java.util.stream.Collectors;
public class NotificationDeleter implements Deleter { public class NotificationDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationDeleter.class));
private final TenantScopedEntityManager entityManager; private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory; protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory; private final DeleterFactory deleterFactory;
@Autowired @Autowired
public NotificationDeleter( public NotificationDeleter(
TenantScopedEntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory, QueryFactory queryFactory,
DeleterFactory deleterFactory DeleterFactory deleterFactory
) { ) {

View File

@ -2,7 +2,7 @@ package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.NotificationTemplateEntity; import gr.cite.notification.data.NotificationTemplateEntity;
import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.NotificationTemplateQuery; import gr.cite.notification.query.NotificationTemplateQuery;
import gr.cite.tools.data.deleter.Deleter; import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,13 +27,13 @@ import java.util.stream.Collectors;
public class NotificationTemplateDeleter implements Deleter { public class NotificationTemplateDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationTemplateDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationTemplateDeleter.class));
private final TenantScopedEntityManager entityManager; private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory; protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory; private final DeleterFactory deleterFactory;
@Autowired @Autowired
public NotificationTemplateDeleter( public NotificationTemplateDeleter(
TenantScopedEntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory, QueryFactory queryFactory,
DeleterFactory deleterFactory DeleterFactory deleterFactory
) { ) {

View File

@ -2,7 +2,7 @@ package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.TenantConfigurationEntity; import gr.cite.notification.data.TenantConfigurationEntity;
import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.TenantConfigurationQuery; import gr.cite.notification.query.TenantConfigurationQuery;
import gr.cite.tools.data.deleter.Deleter; import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,13 +27,13 @@ import java.util.stream.Collectors;
public class TenantConfigurationDeleter implements Deleter { public class TenantConfigurationDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationDeleter.class));
private final TenantScopedEntityManager entityManager; private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory; protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory; private final DeleterFactory deleterFactory;
@Autowired @Autowired
public TenantConfigurationDeleter( public TenantConfigurationDeleter(
TenantScopedEntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory, QueryFactory queryFactory,
DeleterFactory deleterFactory DeleterFactory deleterFactory
) { ) {

View File

@ -2,7 +2,7 @@ package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.TenantEntity; import gr.cite.notification.data.TenantEntity;
import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.TenantQuery; import gr.cite.notification.query.TenantQuery;
import gr.cite.tools.data.deleter.Deleter; import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory; import gr.cite.tools.data.deleter.DeleterFactory;
@ -26,13 +26,13 @@ import java.util.UUID;
public class TenantDeleter implements Deleter { public class TenantDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantDeleter.class));
private final TenantScopedEntityManager entityManager; private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory; protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory; private final DeleterFactory deleterFactory;
@Autowired @Autowired
public TenantDeleter( public TenantDeleter(
TenantScopedEntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory, QueryFactory queryFactory,
DeleterFactory deleterFactory DeleterFactory deleterFactory
) { ) {

View File

@ -0,0 +1,71 @@
package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.data.TenantUserEntity;
import gr.cite.notification.query.TenantUserQuery;
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.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 TenantUserDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantUserDeleter.class));
private final TenantEntityManager entityManager;
private final QueryFactory queryFactory;
@Autowired
public TenantUserDeleter(
TenantEntityManager entityManager,
QueryFactory queryFactory
) {
this.entityManager = entityManager;
this.queryFactory = queryFactory;
}
public void deleteAndSaveByIds(List<UUID> ids) throws InvalidApplicationException {
logger.debug(new MapLogEntry("collecting to delete").And("count", Optional.ofNullable(ids).map(e -> e.size()).orElse(0)).And("ids", ids));
List<TenantUserEntity> datas = this.queryFactory.query(TenantUserQuery.class).ids(ids).collect();
logger.trace("retrieved {} items", Optional.ofNullable(datas).map(e -> e.size()).orElse(0));
this.deleteAndSave(datas);
}
public void deleteAndSave(List<TenantUserEntity> datas) throws InvalidApplicationException {
logger.debug("will delete {} items", Optional.ofNullable(datas).map(e -> e.size()).orElse(0));
this.delete(datas);
logger.trace("saving changes");
this.entityManager.flush();
logger.trace("changes saved");
}
public void delete(List<TenantUserEntity> datas) throws InvalidApplicationException {
logger.debug("will delete {} items", Optional.ofNullable(datas).map(x -> x.size()).orElse(0));
if (datas == null || datas.isEmpty()) return;
Instant now = Instant.now();
for (TenantUserEntity item : datas) {
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

@ -1,7 +1,7 @@
package gr.cite.notification.model.deleter; package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.data.UserContactInfoEntity; import gr.cite.notification.data.UserContactInfoEntity;
import gr.cite.notification.query.UserContactInfoQuery; import gr.cite.notification.query.UserContactInfoQuery;
import gr.cite.tools.data.deleter.Deleter; import gr.cite.tools.data.deleter.Deleter;
@ -27,13 +27,13 @@ public class UserContactInfoDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserContactInfoDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserContactInfoDeleter.class));
private final TenantScopedEntityManager entityManager; private final TenantEntityManager entityManager;
private final QueryFactory queryFactory; private final QueryFactory queryFactory;
@Autowired @Autowired
public UserContactInfoDeleter( public UserContactInfoDeleter(
TenantScopedEntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory QueryFactory queryFactory
) { ) {
this.entityManager = entityManager; this.entityManager = entityManager;

View File

@ -1,7 +1,7 @@
package gr.cite.notification.model.deleter; package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.TenantScopedEntityManager; import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.data.UserCredentialEntity; import gr.cite.notification.data.UserCredentialEntity;
import gr.cite.notification.query.UserCredentialQuery; import gr.cite.notification.query.UserCredentialQuery;
import gr.cite.tools.data.deleter.Deleter; import gr.cite.tools.data.deleter.Deleter;
@ -26,13 +26,13 @@ public class UserCredentialDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserCredentialDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserCredentialDeleter.class));
private final TenantScopedEntityManager entityManager; private final TenantEntityManager entityManager;
private final QueryFactory queryFactory; private final QueryFactory queryFactory;
@Autowired @Autowired
public UserCredentialDeleter( public UserCredentialDeleter(
TenantScopedEntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory QueryFactory queryFactory
) { ) {
this.entityManager = entityManager; this.entityManager = entityManager;

View File

@ -1,6 +1,7 @@
package gr.cite.notification.model.deleter; package gr.cite.notification.model.deleter;
import gr.cite.notification.common.enums.IsActive; import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.data.UserContactInfoEntity; import gr.cite.notification.data.UserContactInfoEntity;
import gr.cite.notification.data.UserCredentialEntity; import gr.cite.notification.data.UserCredentialEntity;
import gr.cite.notification.data.UserEntity; import gr.cite.notification.data.UserEntity;
@ -12,7 +13,6 @@ import gr.cite.tools.data.deleter.DeleterFactory;
import gr.cite.tools.data.query.QueryFactory; import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.logging.LoggerService; import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry; import gr.cite.tools.logging.MapLogEntry;
import jakarta.persistence.EntityManager;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@ -32,7 +32,7 @@ public class UserDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserDeleter.class)); private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserDeleter.class));
private final EntityManager entityManager; private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory; protected final QueryFactory queryFactory;
@ -40,7 +40,7 @@ public class UserDeleter implements Deleter {
@Autowired @Autowired
public UserDeleter( public UserDeleter(
EntityManager entityManager, TenantEntityManager entityManager,
QueryFactory queryFactory, QueryFactory queryFactory,
DeleterFactory deleterFactory DeleterFactory deleterFactory
) { ) {

View File

@ -32,7 +32,7 @@ public class NotificationTemplatePersist {
private NotificationTemplateKind kind; private NotificationTemplateKind kind;
public static final String _kind = "kind"; public static final String _kind = "kind";
private UUID languageId; private String languageCode;
public static final String _languageId = "languageId"; public static final String _languageId = "languageId";
private NotificationTemplateValuePersist value; private NotificationTemplateValuePersist value;
@ -73,12 +73,12 @@ public class NotificationTemplatePersist {
this.kind = kind; this.kind = kind;
} }
public UUID getLanguageId() { public String getLanguageCode() {
return languageId; return languageCode;
} }
public void setLanguageId(UUID languageId) { public void setLanguageCode(String languageCode) {
this.languageId = languageId; this.languageCode = languageCode;
} }
public NotificationTemplateValuePersist getValue() { public NotificationTemplateValuePersist getValue() {
@ -130,7 +130,7 @@ public class NotificationTemplatePersist {
.must(() -> !this.isValidHash(item.getHash())) .must(() -> !this.isValidHash(item.getHash()))
.failOn(NotificationTemplatePersist._hash).failWith(messageSource.getMessage("Validation_OverPosting", new Object[]{}, LocaleContextHolder.getLocale())), .failOn(NotificationTemplatePersist._hash).failWith(messageSource.getMessage("Validation_OverPosting", new Object[]{}, LocaleContextHolder.getLocale())),
this.spec() this.spec()
.must(() -> !this.isNull(item.getLanguageId())) .must(() -> !this.isNull(item.getLanguageCode()))
.failOn(NotificationTemplatePersist._languageId).failWith(messageSource.getMessage("Validation_Required", new Object[]{NotificationTemplatePersist._languageId}, LocaleContextHolder.getLocale())), .failOn(NotificationTemplatePersist._languageId).failWith(messageSource.getMessage("Validation_Required", new Object[]{NotificationTemplatePersist._languageId}, LocaleContextHolder.getLocale())),
this.spec() this.spec()
.must(() -> !this.isNull(item.getNotificationType())) .must(() -> !this.isNull(item.getNotificationType()))

View File

@ -1,190 +0,0 @@
package gr.cite.notification.query;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.data.LanguageEntity;
import gr.cite.notification.model.Language;
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 LanguageQuery extends QueryBase<LanguageEntity> {
private String like;
private Collection<UUID> ids;
private Collection<IsActive> isActives;
private Collection<String> codes;
private Collection<UUID> excludedIds;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
public LanguageQuery like(String value) {
this.like = value;
return this;
}
public LanguageQuery ids(UUID value) {
this.ids = List.of(value);
return this;
}
public LanguageQuery ids(UUID... value) {
this.ids = Arrays.asList(value);
return this;
}
public LanguageQuery ids(Collection<UUID> values) {
this.ids = values;
return this;
}
public LanguageQuery isActive(IsActive value) {
this.isActives = List.of(value);
return this;
}
public LanguageQuery isActive(IsActive... value) {
this.isActives = Arrays.asList(value);
return this;
}
public LanguageQuery isActive(Collection<IsActive> values) {
this.isActives = values;
return this;
}
public LanguageQuery codes(String value) {
this.codes = List.of(value);
return this;
}
public LanguageQuery codes(String... value) {
this.codes = Arrays.asList(value);
return this;
}
public LanguageQuery codes(Collection<String> values) {
this.codes = values;
return this;
}
public LanguageQuery excludedIds(Collection<UUID> values) {
this.excludedIds = values;
return this;
}
public LanguageQuery excludedIds(UUID value) {
this.excludedIds = List.of(value);
return this;
}
public LanguageQuery excludedIds(UUID... value) {
this.excludedIds = Arrays.asList(value);
return this;
}
public LanguageQuery authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
public LanguageQuery(
) {
}
@Override
protected Class<LanguageEntity> entityClass() {
return LanguageEntity.class;
}
@Override
protected Boolean isFalseQuery() {
return this.isEmpty(this.ids) || this.isEmpty(this.isActives) || this.isEmpty(this.excludedIds) || this.isEmpty(this.codes);
}
@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(LanguageEntity._id));
for (UUID item : this.ids)
inClause.value(item);
predicates.add(inClause);
}
if (this.like != null && !this.like.isEmpty()) {
predicates.add(queryContext.CriteriaBuilder.like(queryContext.Root.get(LanguageEntity._code), this.like));
}
if (this.isActives != null) {
CriteriaBuilder.In<IsActive> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(LanguageEntity._isActive));
for (IsActive item : this.isActives)
inClause.value(item);
predicates.add(inClause);
}
if (this.codes != null) {
CriteriaBuilder.In<String> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(LanguageEntity._code));
for (String item : this.codes)
inClause.value(item);
predicates.add(inClause);
}
if (this.excludedIds != null) {
CriteriaBuilder.In<UUID> notInClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(LanguageEntity._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 LanguageEntity convert(Tuple tuple, Set<String> columns) {
LanguageEntity item = new LanguageEntity();
item.setId(QueryBase.convertSafe(tuple, columns, LanguageEntity._id, UUID.class));
item.setCode(QueryBase.convertSafe(tuple, columns, LanguageEntity._code, String.class));
item.setOrdinal(QueryBase.convertSafe(tuple, columns, LanguageEntity._ordinal, Integer.class));
item.setCreatedAt(QueryBase.convertSafe(tuple, columns, LanguageEntity._createdAt, Instant.class));
item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, LanguageEntity._updatedAt, Instant.class));
item.setIsActive(QueryBase.convertSafe(tuple, columns, LanguageEntity._isActive, IsActive.class));
return item;
}
@Override
protected String fieldNameOf(FieldResolver item) {
if (item.match(Language._id))
return LanguageEntity._id;
else if (item.match(Language._code))
return LanguageEntity._code;
else if (item.match(Language._ordinal))
return LanguageEntity._ordinal;
else if (item.match(Language._createdAt))
return LanguageEntity._createdAt;
else if (item.match(Language._updatedAt))
return LanguageEntity._updatedAt;
else if (item.match(Language._hash))
return LanguageEntity._updatedAt;
else if (item.match(Language._isActive))
return LanguageEntity._isActive;
else
return null;
}
}

View File

@ -2,7 +2,6 @@ package gr.cite.notification.query;
import gr.cite.notification.authorization.AuthorizationFlags; import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.common.enums.*; import gr.cite.notification.common.enums.*;
import gr.cite.notification.data.LanguageEntity;
import gr.cite.notification.data.NotificationTemplateEntity; import gr.cite.notification.data.NotificationTemplateEntity;
import gr.cite.notification.model.NotificationTemplate; import gr.cite.notification.model.NotificationTemplate;
import gr.cite.tools.data.query.FieldResolver; import gr.cite.tools.data.query.FieldResolver;
@ -15,7 +14,7 @@ import org.springframework.stereotype.Component;
import jakarta.persistence.Tuple; import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Subquery;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
@ -28,8 +27,7 @@ public class NotificationTemplateQuery extends QueryBase<NotificationTemplateEnt
private Collection<UUID> excludedIds; private Collection<UUID> excludedIds;
private Collection<IsActive> isActives; private Collection<IsActive> isActives;
private Collection<UUID> notificationTypes; private Collection<UUID> notificationTypes;
private Collection<UUID> languages; private Collection<String> languageCodes;
private LanguageQuery languageQuery;
private List<NotificationTemplateChannel> channels; private List<NotificationTemplateChannel> channels;
private List<NotificationTemplateKind> kinds; private List<NotificationTemplateKind> kinds;
@ -80,25 +78,21 @@ public class NotificationTemplateQuery extends QueryBase<NotificationTemplateEnt
return this; return this;
} }
public NotificationTemplateQuery languages(UUID value) { public NotificationTemplateQuery languageCodes(String value) {
this.languages = List.of(value); this.languageCodes = List.of(value);
return this; return this;
} }
public NotificationTemplateQuery languages(UUID... value) { public NotificationTemplateQuery languageCodes(String... value) {
this.languages = Arrays.asList(value); this.languageCodes = Arrays.asList(value);
return this; return this;
} }
public NotificationTemplateQuery languages(Collection<UUID> values) { public NotificationTemplateQuery languageCodes(Collection<String> values) {
this.languages = values; this.languageCodes = values;
return this; return this;
} }
public NotificationTemplateQuery languageSubQuery(LanguageQuery subQuery) {
this.languageQuery = subQuery;
return this;
}
public NotificationTemplateQuery channels(NotificationTemplateChannel... channels) { public NotificationTemplateQuery channels(NotificationTemplateChannel... channels) {
this.channels = List.of(channels); this.channels = List.of(channels);
@ -157,7 +151,7 @@ public class NotificationTemplateQuery extends QueryBase<NotificationTemplateEnt
&& this.isNullOrEmpty(this.isActives) && this.isNullOrEmpty(this.isActives)
&& this.isNullOrEmpty(this.kinds) && this.isNullOrEmpty(this.kinds)
&& this.isNullOrEmpty(this.notificationTypes) && this.isNullOrEmpty(this.notificationTypes)
&& this.isNullOrEmpty(this.languages) && this.isNullOrEmpty(this.languageCodes)
&& this.isNullOrEmpty(this.channels); && this.isNullOrEmpty(this.channels);
} }
@ -190,18 +184,13 @@ public class NotificationTemplateQuery extends QueryBase<NotificationTemplateEnt
predicates.add(inClause); predicates.add(inClause);
} }
if (this.languages != null) { if (this.languageCodes != null) {
CriteriaBuilder.In<UUID> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(NotificationTemplateEntity._languageId)); CriteriaBuilder.In<String> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(NotificationTemplateEntity._languageCode));
for (UUID item : this.languages) for (String item : this.languageCodes)
inClause.value(item); inClause.value(item);
predicates.add(inClause); predicates.add(inClause);
} }
if (this.languageQuery != null) {
QueryContext<LanguageEntity, UUID> subQuery = this.applySubQuery(this.languageQuery, queryContext, UUID.class, root -> root.get(NotificationTemplateEntity._id));
predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(NotificationTemplateEntity._languageId)).value(subQuery.Query));
}
if (this.channels != null) { if (this.channels != null) {
CriteriaBuilder.In<NotificationTemplateChannel> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(NotificationTemplateEntity._channel)); CriteriaBuilder.In<NotificationTemplateChannel> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(NotificationTemplateEntity._channel));
for (NotificationTemplateChannel item : this.channels) for (NotificationTemplateChannel item : this.channels)
@ -237,9 +226,8 @@ public class NotificationTemplateQuery extends QueryBase<NotificationTemplateEnt
else if (item.match(NotificationTemplate._channel)) return NotificationTemplateEntity._channel; else if (item.match(NotificationTemplate._channel)) return NotificationTemplateEntity._channel;
else if (item.match(NotificationTemplate._kind)) return NotificationTemplateEntity._kind; else if (item.match(NotificationTemplate._kind)) return NotificationTemplateEntity._kind;
else if (item.match(NotificationTemplate._notificationType)) return NotificationTemplateEntity._notificationType; else if (item.match(NotificationTemplate._notificationType)) return NotificationTemplateEntity._notificationType;
else if (item.match(NotificationTemplate._languageCode)) return NotificationTemplateEntity._languageCode;
else if (item.prefix(NotificationTemplate._value)) return NotificationTemplateEntity._value; else if (item.prefix(NotificationTemplate._value)) return NotificationTemplateEntity._value;
else if (item.match(NotificationTemplate._language)) return NotificationTemplateEntity._languageId;
else if (item.prefix(NotificationTemplate._language)) return NotificationTemplateEntity._languageId;
else if (item.match(NotificationTemplate._createdAt)) return NotificationTemplateEntity._createdAt; else if (item.match(NotificationTemplate._createdAt)) return NotificationTemplateEntity._createdAt;
else if (item.match(NotificationTemplate._updatedAt)) return NotificationTemplateEntity._updatedAt; else if (item.match(NotificationTemplate._updatedAt)) return NotificationTemplateEntity._updatedAt;
else if (item.match(NotificationTemplate._isActive)) return NotificationTemplateEntity._isActive; else if (item.match(NotificationTemplate._isActive)) return NotificationTemplateEntity._isActive;
@ -255,7 +243,7 @@ public class NotificationTemplateQuery extends QueryBase<NotificationTemplateEnt
item.setKind(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._kind, NotificationTemplateKind.class)); item.setKind(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._kind, NotificationTemplateKind.class));
item.setNotificationType(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._notificationType, UUID.class)); item.setNotificationType(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._notificationType, UUID.class));
item.setValue(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._value, String.class)); item.setValue(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._value, String.class));
item.setLanguageId(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._languageId, UUID.class)); item.setLanguageCode(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._languageCode, String.class));
item.setCreatedAt(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._createdAt, Instant.class)); item.setCreatedAt(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._createdAt, Instant.class));
item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._updatedAt, Instant.class)); item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._updatedAt, Instant.class));
item.setIsActive(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._isActive, IsActive.class)); item.setIsActive(QueryBase.convertSafe(tuple, columns, NotificationTemplateEntity._isActive, IsActive.class));

View File

@ -0,0 +1,209 @@
package gr.cite.notification.query;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.authorization.Permission;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.user.UserScope;
import gr.cite.notification.data.UserEntity;
import gr.cite.notification.model.Tenant;
import gr.cite.notification.model.TenantUser;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.notification.data.TenantUserEntity;
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 TenantUserQuery extends QueryBase<TenantUserEntity> {
private Collection<UUID> ids;
private Collection<UUID> userIds;
private Collection<UUID> tenantIds;
private Collection<IsActive> isActives;
private UserQuery userQuery;
private EnumSet<AuthorizationFlags> authorize = EnumSet.of(AuthorizationFlags.None);
private final UserScope userScope;
private final AuthorizationService authService;
public TenantUserQuery(
UserScope userScope,
AuthorizationService authService
) {
this.userScope = userScope;
this.authService = authService;
}
public TenantUserQuery ids(UUID value) {
this.ids = List.of(value);
return this;
}
public TenantUserQuery ids(UUID... value) {
this.ids = Arrays.asList(value);
return this;
}
public TenantUserQuery ids(Collection<UUID> values) {
this.ids = values;
return this;
}
public TenantUserQuery userIds(UUID value) {
this.userIds = List.of(value);
return this;
}
public TenantUserQuery userIds(UUID... value) {
this.userIds = Arrays.asList(value);
return this;
}
public TenantUserQuery userIds(Collection<UUID> values) {
this.userIds = values;
return this;
}
public TenantUserQuery tenantIds(UUID value) {
this.tenantIds = List.of(value);
return this;
}
public TenantUserQuery tenantIds(UUID... value) {
this.tenantIds = Arrays.asList(value);
return this;
}
public TenantUserQuery tenantIds(Collection<UUID> values) {
this.tenantIds = values;
return this;
}
public TenantUserQuery isActive(IsActive value) {
this.isActives = List.of(value);
return this;
}
public TenantUserQuery isActive(IsActive... value) {
this.isActives = Arrays.asList(value);
return this;
}
public TenantUserQuery isActive(Collection<IsActive> values) {
this.isActives = values;
return this;
}
public TenantUserQuery userSubQuery(UserQuery subQuery) {
this.userQuery = subQuery;
return this;
}
public TenantUserQuery authorize(EnumSet<AuthorizationFlags> values) {
this.authorize = values;
return this;
}
@Override
protected Class<TenantUserEntity> entityClass() {
return TenantUserEntity.class;
}
@Override
protected Boolean isFalseQuery() {
return this.isEmpty(this.ids) || this.isEmpty(this.userIds) || this.isEmpty(this.tenantIds) || this.isEmpty(this.isActives) || this.isFalseQuery(this.userQuery);
}
@Override
protected <X, Y> Predicate applyAuthZ(QueryContext<X, Y> queryContext) {
if (this.authorize.contains(AuthorizationFlags.None)) return null;
if (this.authorize.contains(AuthorizationFlags.Permission) && this.authService.authorize(Permission.BrowseTenant)) return null;
UUID ownerId = null;
if (this.authorize.contains(AuthorizationFlags.Owner)) ownerId = this.userScope.getUserIdSafe();
List<Predicate> predicates = new ArrayList<>();
if (ownerId != null) {
predicates.add(queryContext.CriteriaBuilder.equal(queryContext.Root.get(TenantUserEntity._userId), ownerId));
}
if (!predicates.isEmpty()) {
Predicate[] predicatesArray = predicates.toArray(new Predicate[0]);
return queryContext.CriteriaBuilder.and(predicatesArray);
} else {
return queryContext.CriteriaBuilder.or(); //Creates a false query
}
}
@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(TenantUserEntity._id));
for (UUID item : this.ids) inClause.value(item);
predicates.add(inClause);
}
if (this.userIds != null) {
CriteriaBuilder.In<UUID> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(TenantUserEntity._userId));
for (UUID item : this.userIds) inClause.value(item);
predicates.add(inClause);
}
if (this.tenantIds != null) {
CriteriaBuilder.In<UUID> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(TenantUserEntity._tenantId));
for (UUID item : this.tenantIds) inClause.value(item);
predicates.add(inClause);
}
if (this.isActives != null) {
CriteriaBuilder.In<IsActive> inClause = queryContext.CriteriaBuilder.in(queryContext.Root.get(TenantUserEntity._isActive));
for (IsActive item : this.isActives) inClause.value(item);
predicates.add(inClause);
}
if (this.userQuery != null) {
QueryContext<UserEntity, UUID> subQuery = this.applySubQuery(this.userQuery, queryContext, UUID.class, root -> root.get(TenantUserEntity._userId));
predicates.add(queryContext.CriteriaBuilder.in(queryContext.Root.get(TenantUserEntity._userId)).value(subQuery.Query));
}
if (!predicates.isEmpty()) {
Predicate[] predicatesArray = predicates.toArray(new Predicate[0]);
return queryContext.CriteriaBuilder.and(predicatesArray);
} else {
return null;
}
}
@Override
protected TenantUserEntity convert(Tuple tuple, Set<String> columns) {
TenantUserEntity item = new TenantUserEntity();
item.setId(QueryBase.convertSafe(tuple, columns, TenantUserEntity._id, UUID.class));
item.setTenantId(QueryBase.convertSafe(tuple, columns, TenantUserEntity._tenantId, UUID.class));
item.setUserId(QueryBase.convertSafe(tuple, columns, TenantUserEntity._userId, UUID.class));
item.setTenantId(QueryBase.convertSafe(tuple, columns, TenantUserEntity._tenantId, UUID.class));
item.setCreatedAt(QueryBase.convertSafe(tuple, columns, TenantUserEntity._createdAt, Instant.class));
item.setUpdatedAt(QueryBase.convertSafe(tuple, columns, TenantUserEntity._updatedAt, Instant.class));
item.setIsActive(QueryBase.convertSafe(tuple, columns, TenantUserEntity._isActive, IsActive.class));
return item;
}
@Override
protected String fieldNameOf(FieldResolver item) {
if (item.match(TenantUser._id)) return TenantUserEntity._id;
else if (item.match(TenantUser._tenant, Tenant._id)) return TenantUserEntity._tenantId;
else if (item.prefix(TenantUser._tenant)) return TenantUserEntity._tenantId;
else if (item.match(TenantUser._isActive)) return TenantUserEntity._isActive;
else if (item.match(TenantUser._createdAt)) return TenantUserEntity._createdAt;
else if (item.match(TenantUser._updatedAt)) return TenantUserEntity._updatedAt;
else if (item.match(TenantUser._hash)) return TenantUserEntity._updatedAt;
else if (item.match(TenantUser._user, UserEntity._id)) return TenantUserEntity._userId;
else if (item.prefix(TenantUser._user)) return TenantUserEntity._userId;
else if (item.match(TenantUser._belongsToCurrentTenant)) return TenantUserEntity._tenantId;
else return null;
}
}

View File

@ -1,76 +0,0 @@
package gr.cite.notification.query.lookup;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.query.LanguageQuery;
import gr.cite.tools.data.query.Lookup;
import gr.cite.tools.data.query.QueryFactory;
import java.util.List;
import java.util.UUID;
public class LanguageLookup extends Lookup {
private String like;
private List<IsActive> isActive;
private List<String> codes;
private List<UUID> ids;
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<UUID> getExcludedIds() {
return excludedIds;
}
public void setExcludedIds(List<UUID> excludeIds) {
this.excludedIds = excludeIds;
}
public List<String> getCodes() {
return codes;
}
public void setCodes(List<String> codes) {
this.codes = codes;
}
public LanguageQuery enrich(QueryFactory queryFactory) {
LanguageQuery query = queryFactory.query(LanguageQuery.class);
if (this.like != null) query.like(this.like);
if (this.isActive != null) query.isActive(this.isActive);
if (this.codes != null) query.codes(this.codes);
if (this.ids != null) query.ids(this.ids);
if (this.excludedIds != null) query.excludedIds(this.excludedIds);
this.enrichCommon(query);
return query;
}
}

View File

@ -0,0 +1,90 @@
package gr.cite.notification.query.utils;
import gr.cite.tools.data.query.QueryContext;
import jakarta.persistence.criteria.*;
import java.util.function.BiFunction;
import java.util.function.Function;
public class BuildSubQueryInput<Entity, Key> {
private final AbstractQuery<?> query;
private final CriteriaBuilder criteriaBuilder;
private final Class<Entity> entityType;
private final Class<Key> keyType;
private final Function<Root<Entity>, Expression<Key>> keyPathFunc;
private final BiFunction<Root<Entity>, CriteriaBuilder, Predicate> filterFunc;
public BuildSubQueryInput(Builder<Entity, Key> builder) {
query = builder.query;
criteriaBuilder = builder.criteriaBuilder;
entityType = builder.entityType;
keyType = builder.keyType;
keyPathFunc = builder.keyPathFunc;
filterFunc = builder.filterFunc;
}
public AbstractQuery<?> getQuery() {
return query;
}
public CriteriaBuilder getCriteriaBuilder() {
return criteriaBuilder;
}
public Class<Entity> getEntityType() {
return entityType;
}
public Class<Key> getKeyType() {
return keyType;
}
public Function<Root<Entity>, Expression<Key>> getKeyPathFunc() {
return keyPathFunc;
}
public BiFunction<Root<Entity>, CriteriaBuilder, Predicate> getFilterFunc() {
return filterFunc;
}
public static class Builder<Entity, Key> {
private final Class<Entity> entityType;
private final Class<Key> keyType;
private AbstractQuery<?> query;
private CriteriaBuilder criteriaBuilder;
private Function<Root<Entity>, Expression<Key>> keyPathFunc;
private BiFunction<Root<Entity>, CriteriaBuilder, Predicate> filterFunc;
public Builder(Class<Entity> entityType, Class<Key> keyType) {
this.entityType = entityType;
this.keyType = keyType;
}
public Builder(Class<Entity> entityType, Class<Key> keyType, QueryContext<?, ?> queryContext) {
this.entityType = entityType;
this.keyType = keyType;
this.query = queryContext.Query;
this.criteriaBuilder = queryContext.CriteriaBuilder;
}
public Builder<Entity, Key> query(AbstractQuery<?> query) {
this.query = query;
return this;
}
public Builder<Entity, Key> criteriaBuilder(CriteriaBuilder criteriaBuilder) {
this.criteriaBuilder = criteriaBuilder;
return this;
}
public Builder<Entity, Key> keyPathFunc(Function<Root<Entity>, Expression<Key>> keyPathFunc) {
this.keyPathFunc = keyPathFunc;
return this;
}
public Builder<Entity, Key> filterFunc(BiFunction<Root<Entity>, CriteriaBuilder, Predicate> filterFunc) {
this.filterFunc = filterFunc;
return this;
}
}
}

View File

@ -0,0 +1,7 @@
package gr.cite.notification.query.utils;
import jakarta.persistence.criteria.Subquery;
public interface QueryUtilsService {
<Key, D> Subquery<Key> buildSubQuery(BuildSubQueryInput<D, Key> parameters);
}

View File

@ -0,0 +1,18 @@
package gr.cite.notification.query.utils;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import org.springframework.stereotype.Component;
@Component
public class QueryUtilsServiceImpl implements QueryUtilsService {
@Override
public <Key, D> Subquery<Key> buildSubQuery(BuildSubQueryInput<D, Key> parameters){
Subquery<Key> subQuery = parameters.getQuery().subquery(parameters.getKeyType());
Root<D> subQueryRoot = subQuery.from(parameters.getEntityType());
subQuery.select(parameters.getKeyPathFunc().apply(subQueryRoot)).distinct(true);
subQuery.where(parameters.getFilterFunc().apply(subQueryRoot, parameters.getCriteriaBuilder()));
return subQuery;
}
}

Some files were not shown because too many files have changed in this diff Show More