Merge branch 'dmp-refactoring' of https://code-repo.d4science.org/MaDgiK-CITE/argos into dmp-refactoring

This commit is contained in:
Sofia Papacharalampous 2024-04-04 17:03:03 +03:00
commit d94759ff5d
124 changed files with 2880 additions and 2984 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.scope.fake.FakeRequestScope;
import gr.cite.annotation.data.QueueInboxEntity;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.annotation.integrationevent.TrackedEvent;
import gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval.AnnotationEntitiesRemovalIntegrationEventHandler;
import gr.cite.annotation.integrationevent.inbox.annotationentitiestouch.AnnotationEntitiesTouchedIntegrationEventHandler;
@ -278,6 +279,10 @@ public class InboxRepositoryImpl implements InboxRepository {
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
transaction.begin();
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.stereotype.Component;
import javax.management.InvalidApplicationException;
import java.util.*;
@Component
@ -44,15 +45,26 @@ public class AnnotationEntitiesRemovalIntegrationEventHandlerImpl implements Ann
private final ValidatorFactory validatorFactory;
private final ApplicationContext applicationContext;
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.validatorFactory = validatorFactory;
this.applicationContext = applicationContext;
this.queryFactory = queryFactory;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.deleterFactory = deleterFactory;
this.tenantScope = tenantScope;
}
@Override
@ -65,83 +77,53 @@ public class AnnotationEntitiesRemovalIntegrationEventHandlerImpl implements Ann
this.validatorFactory.validator(AnnotationEntitiesRemovalIntegrationEvent.AnnotationEntitiesRemovalIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
TenantScope scope = this.applicationContext.getBean(TenantScope.class);
if (scope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.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(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
EventProcessingStatus status = EventProcessingStatus.Success;
try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
// logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
scope.setTenant(null, scope.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();
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
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) {
status = EventProcessingStatus.Error;
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.query.EntityUserQuery;
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.extractor.ClaimExtractorProperties;
import gr.cite.tools.auditing.AuditService;
@ -32,6 +33,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.management.InvalidApplicationException;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
@ -46,15 +48,24 @@ public class AnnotationEntitiesTouchedIntegrationEventHandlerImpl implements Ann
private final ValidatorFactory validatorFactory;
private final ApplicationContext applicationContext;
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.validatorFactory = validatorFactory;
this.applicationContext = applicationContext;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.queryFactory = queryFactory;
this.deleterFactory = deleterFactory;
this.tenantScope = tenantScope;
}
@Override
@ -67,102 +78,72 @@ public class AnnotationEntitiesTouchedIntegrationEventHandlerImpl implements Ann
this.validatorFactory.validator(AnnotationEntitiesTouchedIntegrationEvent.AnnotationEntitiesTouchedIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
TenantScope scope = this.applicationContext.getBean(TenantScope.class);
if (scope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.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(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
EventProcessingStatus status = EventProcessingStatus.Success;
try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
// logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
scope.setTenant(null, scope.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();
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
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) {
status = EventProcessingStatus.Error;
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;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.annotation.audit.AuditableAction;
import gr.cite.annotation.common.JsonHandlingService;
import gr.cite.annotation.common.scope.fake.FakeRequestScope;
import gr.cite.annotation.errorcode.ErrorThesaurusProperties;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.annotation.integrationevent.inbox.EventProcessingStatus;
import gr.cite.annotation.integrationevent.inbox.InboxPrincipal;
import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
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.tools.auditing.AuditService;
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.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope;
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 final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
public TenantRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext) {
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final TenantService tenantService;
private final AuditService auditService;
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.applicationContext = applicationContext;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.tenantRemovalConsistencyHandler = tenantRemovalConsistencyHandler;
}
@Override
@ -49,65 +48,34 @@ public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIn
if (event == null)
return EventProcessingStatus.Error;
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
EventProcessingStatus status = EventProcessingStatus.Success;
try {
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()))))
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);
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();
if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId())))) {
status = EventProcessingStatus.Postponed;
currentPrincipalResolver.pop();
return status;
}
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) {
status = EventProcessingStatus.Error;
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;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.annotation.audit.AuditableAction;
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.InboxPrincipal;
import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.persist.TenantTouchedIntegrationEventPersist;
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.tools.auditing.AuditService;
import gr.cite.tools.logging.LoggerService;
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.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
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));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService;
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) {
this.applicationContext = applicationContext;
public TenantTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, TenantService tenantService, AuditService auditService, TenantEntityManager tenantEntityManager) {
this.jsonHandlingService = jsonHandlingService;
this.validatorFactory = validatorFactory;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
}
@Override
@ -56,59 +57,25 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
model.setCode(event.getCode());
this.validatorFactory.validator(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.class).validateForce(model);
EntityManager entityManager = null;
EntityTransaction transaction = null;
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));
EventProcessingStatus status = EventProcessingStatus.Success;
tenantEntityManager.disableTenantFilters();
try {
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
tenantService.persist(model, null);
transaction = entityManager.getTransaction();
transaction.begin();
auditService.track(AuditableAction.Tenant_Persist, Map.ofEntries(
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) {
status = EventProcessingStatus.Error;
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;
import gr.cite.annotation.data.TenantEntityManager;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.annotation.audit.AuditableAction;
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.data.TenantEntity;
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.InboxPrincipal;
@ -14,19 +12,15 @@ import gr.cite.annotation.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.annotation.model.Tenant;
import gr.cite.annotation.query.TenantQuery;
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.tools.auditing.AuditService;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.BaseFieldSet;
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.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.i18n.LocaleContextHolder;
@ -43,22 +37,35 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
private final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errors;
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(
JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext,
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.applicationContext = applicationContext;
this.errors = errors;
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
@ -71,77 +78,44 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
logger.debug("Handling {}", UserRemovalIntegrationEvent.class.getSimpleName());
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
TenantScope scope = this.applicationContext.getBean(TenantScope.class);
if (scope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.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(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
EventProcessingStatus status = EventProcessingStatus.Success;
try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
// logger.error("missing tenant from event message");
scope.setTenant(null, scope.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();
// return EventProcessingStatus.Error;
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
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) {
status = EventProcessingStatus.Error;
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.annotation.audit.AuditableAction;
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.data.TenantEntity;
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.logging.LoggerService;
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.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
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));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService;
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(
JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext,
ValidatorFactory validatorFactory) {
ValidatorFactory validatorFactory, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, UserService userService, AuditService auditService, TenantEntityManager tenantEntityManager) {
this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
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
@ -63,71 +68,37 @@ public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegr
this.validatorFactory.validator(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
TenantScope scope = this.applicationContext.getBean(TenantScope.class);
if (scope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.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(properties.getTenantId(), tenant.getCode());
} else if (scope.isMultitenant()) {
EventProcessingStatus status = EventProcessingStatus.Success;
try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
// logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
scope.setTenant(null, scope.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();
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
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) {
status = EventProcessingStatus.Error;
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 DeleteLanguage = "DeleteLanguage";
//NotificationTemplate
public static String BrowseNotificationTemplate = "BrowseNotificationTemplate";
public static String EditNotificationTemplate = "EditNotificationTemplate";
public static String DeleteNotificationTemplate = "DeleteNotificationTemplate";
//Language
public static String BrowseStatistics = "BrowseStatistics";
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.fake.FakeRequestScope;
import eu.eudat.data.QueueInboxEntity;
import eu.eudat.data.TenantEntityManager;
import eu.eudat.integrationevent.TrackedEvent;
import eu.eudat.query.QueueInboxQuery;
import gr.cite.queueinbox.entity.QueueInbox;
@ -28,12 +29,10 @@ import java.util.List;
import java.util.UUID;
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;
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InboxRepositoryImpl.class));
private final JsonHandlingService jsonHandlingService;
private final InboxProperties inboxProperties;
@ -234,10 +233,17 @@ public class InboxRepositoryImpl implements InboxRepository {
}
private QueueInboxEntity createQueueInboxEntity(InboxCreatorParams inboxCreatorParams) {
QueueInboxEntity queueMessage = new QueueInboxEntity();
queueMessage.setId(UUID.randomUUID());
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.setRoute(inboxCreatorParams.getRoutingKey());
queueMessage.setQueue(inboxCreatorParams.getQueueName());
@ -265,6 +271,10 @@ public class InboxRepositoryImpl implements InboxRepository {
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
transaction.begin();
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());
} else {
EventProcessingStatus status = this.processMessage(queueInboxMessage.getRoute(), queueInboxMessage.getMessageId().toString(), queueInboxMessage.getApplicationId(), queueInboxMessage.getMessage());
EventProcessingStatus status = this.processMessage(queueInboxMessage);
switch (status) {
case Success: {
queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL);
@ -317,29 +327,39 @@ public class InboxRepositoryImpl implements InboxRepository {
return success;
}
private EventProcessingStatus processMessage(String routingKey, String messageId, String appId, String message) {
private EventProcessingStatus processMessage(QueueInboxEntity queueInboxMessage) {
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)
return EventProcessingStatus.Discard;
IntegrationEventProperties properties = new IntegrationEventProperties();
properties.setAppId(appId);
properties.setMessageId(messageId);
properties.setAppId(queueInboxMessage.getApplicationId());
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))
// {
try {
return handler.handle(properties, message);
return handler.handle(properties, queueInboxMessage.getMessage());
} 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;
}
// }
}
private Boolean RoutingKeyMatched(String routingKey, List<String> topics) {
private Boolean routingKeyMatched(String routingKey, List<String> topics) {
if (topics == null || topics.isEmpty())
return false;
return topics.stream().anyMatch(x -> x.equals(routingKey));

View File

@ -1,10 +1,13 @@
package eu.eudat.integrationevent.inbox;
import java.util.UUID;
public class IntegrationEventProperties {
private String messageId;
private String appId;
private UUID tenantId;
public String getMessageId() {
return messageId;
@ -22,4 +25,11 @@ public class IntegrationEventProperties {
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();
message.setMessageId(UUID.randomUUID());
message.setType(OutboxIntegrationEvent.TENANT_TOUCH);
message.setTenantId(event.getId());
// message.setTenantId(event.getId()); //Hack Can not Queue inbox before tenant created
message.setEvent(event);
this.outboxService.publish(message);

View File

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

View File

@ -6,8 +6,10 @@ import eu.eudat.commons.enums.IsActive;
import eu.eudat.data.TenantEntity;
import eu.eudat.data.TenantEntityManager;
import eu.eudat.data.UserEntity;
import eu.eudat.integrationevent.outbox.tenantremoval.TenantRemovalIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEvent;
import eu.eudat.integrationevent.outbox.tenanttouched.TenantTouchedIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.userremoval.UserRemovalIntegrationEventHandler;
import eu.eudat.integrationevent.outbox.usertouched.UserTouchedIntegrationEventHandler;
import eu.eudat.query.TenantQuery;
import eu.eudat.query.UserQuery;
@ -44,8 +46,12 @@ public class MaintenanceController {
private final UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler;
private final UserRemovalIntegrationEventHandler userRemovalIntegrationEventHandler;
private final TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler;
private final TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler;
@Autowired
public MaintenanceController(
AuthorizationService authorizationService,
@ -54,14 +60,18 @@ public class MaintenanceController {
QueryFactory queryFactory,
TenantEntityManager entityManager,
UserTouchedIntegrationEventHandler userTouchedIntegrationEventHandler,
TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler) {
UserRemovalIntegrationEventHandler userRemovalIntegrationEventHandler,
TenantTouchedIntegrationEventHandler tenantTouchedIntegrationEventHandler,
TenantRemovalIntegrationEventHandler tenantRemovalIntegrationEventHandler) {
this.authorizationService = authorizationService;
this.elasticService = elasticService;
this.auditService = auditService;
this.queryFactory = queryFactory;
this.entityManager = entityManager;
this.userTouchedIntegrationEventHandler = userTouchedIntegrationEventHandler;
this.userRemovalIntegrationEventHandler = userRemovalIntegrationEventHandler;
this.tenantTouchedIntegrationEventHandler = tenantTouchedIntegrationEventHandler;
this.tenantRemovalIntegrationEventHandler = tenantRemovalIntegrationEventHandler;
}
@RequestMapping(method = RequestMethod.POST, value = {"/index/elastic"})
@ -92,13 +102,19 @@ public class MaintenanceController {
this.authorizationService.authorizeForce(Permission.ManageQueueEvents);
this.entityManager.disableTenantFilters();
UserQuery userQuery = queryFactory.query(UserQuery.class).isActive(IsActive.Active);
List<UserEntity> users = userQuery.collect();
UserQuery userQuery = queryFactory.query(UserQuery.class);
userQuery.isActive(IsActive.Active);
List<UserEntity> activeUsers = userQuery.collect();
userQuery.isActive(IsActive.Inactive);
List<UserEntity> inactiveUsers = userQuery.collect();
this.entityManager.enableTenantFilters();
for(UserEntity user : users)
for(UserEntity user : activeUsers)
this.userTouchedIntegrationEventHandler.handle(user.getId());
for(UserEntity user : inactiveUsers)
this.userRemovalIntegrationEventHandler.handle(user.getId());
this.auditService.track(AuditableAction.Maintenance_SendUserTouchEvents);
}
@ -108,17 +124,24 @@ public class MaintenanceController {
this.authorizationService.authorizeForce(Permission.ManageQueueEvents);
this.entityManager.disableTenantFilters();
TenantQuery tenantQuery = queryFactory.query(TenantQuery.class).isActive(IsActive.Active);
List<TenantEntity> tenants = tenantQuery.collect();
TenantQuery tenantQuery = queryFactory.query(TenantQuery.class);
tenantQuery.isActive(IsActive.Active);
List<TenantEntity> activeTenants = tenantQuery.collect();
tenantQuery.isActive(IsActive.Inactive);
List<TenantEntity> inactiveTenants = tenantQuery.collect();
this.entityManager.enableTenantFilters();
for (TenantEntity tenant : tenants) {
for (TenantEntity tenant : activeTenants) {
TenantTouchedIntegrationEvent event = new TenantTouchedIntegrationEvent();
event.setId(tenant.getId());
event.setCode(tenant.getCode());
this.tenantTouchedIntegrationEventHandler.handle(event);
}
for (TenantEntity tenant : inactiveTenants) {
this.tenantRemovalIntegrationEventHandler.handle(tenant.getId());
}
this.auditService.track(AuditableAction.Maintenance_SendTenantTouchEvents);
}
}

View File

@ -110,7 +110,25 @@ permissions:
clients: [ ]
allowAnonymous: 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
BrowseLanguage:
roles: [ ]

View File

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

View File

@ -26,11 +26,11 @@ BEGIN
notified_with smallint,
CONSTRAINT "ntf_Notification_pkey" PRIMARY KEY (id),
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 DELETE NO ACTION,
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 DELETE NO ACTION
);

View File

@ -10,20 +10,15 @@ BEGIN
channel smallint NOT NULL,
notification_type uuid 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,
is_active smallint NOT NULL,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
tenant uuid,
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)
REFERENCES public."Tenant" (id) MATCH SIMPLE
REFERENCES public."ntf_Tenant" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID

View File

@ -21,7 +21,7 @@ BEGIN
is_active smallint NOT NULL,
CONSTRAINT "ntf_QueryInbox_pkey" PRIMARY KEY (id),
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 DELETE NO ACTION
NOT VALID

View File

@ -21,7 +21,7 @@ BEGIN
is_active smallint NOT NULL,
CONSTRAINT "ntf_QueueOutbox_pkey" PRIMARY KEY (id),
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 DELETE NO ACTION
);

View File

@ -21,11 +21,11 @@ BEGIN
is_active smallint NOT NULL,
CONSTRAINT "ntf_InAppNotification_pkey" PRIMARY KEY (id),
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 DELETE NO ACTION,
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 DELETE NO ACTION
);

View File

@ -329,17 +329,6 @@ const appRoutes: Routes = [
})
},
},
{
path: 'index-managment',
loadChildren: () => import('./ui/admin/index-managment/index-managment.module').then(m => m.IndexManagmentModule),
data: {
authContext: {
permissions: [AppPermission.ViewMaintenancePage]
},
breadcrumb: true,
title: 'GENERAL.TITLES.INDEX-MANAGMENT'
},
},
{
path: 'maintenance-tasks',
loadChildren: () => import('./ui/admin/maintenance-tasks/maintenance-tasks.module').then(m => m.MaintenanceTasksModule),
@ -347,7 +336,8 @@ const appRoutes: Routes = [
authContext: {
permissions: [AppPermission.ViewMaintenancePage]
},
breadcrumb: true
breadcrumb: true,
title: 'GENERAL.TITLES.MAINTENANCE-TASKS'
},
},
{

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

View File

@ -28,4 +28,18 @@ export class MaintenanceService extends BaseService {
.delete<any>(url).pipe(
catchError((error: any) => throwError(error)));
}
sendUserTouchEvents(): Observable<any> {
const url = `${this.apiBase}/events/users/touch`;
return this.http
.delete<any>(url).pipe(
catchError((error: any) => throwError(error)));
}
sendTenantTouchEvents(): Observable<any> {
const url = `${this.apiBase}/events/tenants/touch`;
return this.http
.delete<any>(url).pipe(
catchError((error: any) => throwError(error)));
}
}

View File

@ -1,15 +0,0 @@
<div class="container-fluid">
<div class="row root">
<div class="col-md-10 offset-md-1">
<div class="mt-4 mb-4"></div>
<mat-card class="p-2">
<div class="mt-2">
<div style="color: red;">Warning: Danger zone. It might delete Dataset tags if not careful</div>
<button mat-raised-button color="primary" (click)="generateIndex($event)" class="lightblue-btn button">Generate Index</button>
<button mat-raised-button color="primary" (click)="clearIndex($event)" class="lightblue-btn button">Clear Index</button>
</div>
</mat-card>
</div>
</div>
</div>

View File

@ -1,7 +0,0 @@
.root {
// padding-bottom: 2em;
.button {
margin: 5px;
}
}

View File

@ -1,87 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '@common/base/base.component';
import { takeUntil } from 'rxjs/operators';
import { UiNotificationService, SnackBarNotificationLevel } from '@app/core/services/notification/ui-notification-service';
import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router';
import { MaintenanceService } from '@app/core/services/maintenance/maintenance.service';
@Component({
selector: 'app-index-managment',
templateUrl: './index-managment.component.html',
styleUrls: ['./index-managment.component.scss']
})
export class IndexManagmentComponent extends BaseComponent implements OnInit {
constructor(
private uiNotificationService: UiNotificationService,
private translate: TranslateService,
private router: Router,
private maintenanceService: MaintenanceService
)
{
super();
}
ngOnInit() {
}
generateIndex(ev: Event) {
(ev.target as HTMLButtonElement).disabled = true;
// this.datasetService.generateIndex().pipe(takeUntil(this._destroyed)).subscribe(
// response => {
// (ev.target as HTMLButtonElement).disabled = false;
// this.onCallbackSuccess();
// },
// error => {
// (ev.target as HTMLButtonElement).disabled = false;
// this.onCallbackError(error);
// }
// );
this.maintenanceService.generateElasticIndex().pipe(takeUntil(this._destroyed)).subscribe(
response => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackSuccess();
},
error => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackError(error);
}
);
}
clearIndex(ev: Event) {
(ev.target as HTMLButtonElement).disabled = true;
// this.datasetService.clearIndex().pipe(takeUntil(this._destroyed)).subscribe(
// response => {
// (ev.target as HTMLButtonElement).disabled = false;
// this.onCallbackSuccess();
// },
// error => {
// (ev.target as HTMLButtonElement).disabled = false;
// this.onCallbackError(error);
// }
// );
this.maintenanceService.clearElasticIndex().pipe(takeUntil(this._destroyed)).subscribe(
response => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackSuccess();
},
error => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackError(error);
}
);
}
onCallbackSuccess(): void {
this.uiNotificationService.snackBarNotification( this.translate.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
this.router.navigate(['/reload']).then(() => this.router.navigate(['/index-managment']));
}
onCallbackError(error: any) {
this.uiNotificationService.snackBarNotification( error, SnackBarNotificationLevel.Error);
//this.validateAllFormFields(this.formGroup);
}
}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IndexManagmentRoutingModule } from './index-managment.routing';
import { IndexManagmentComponent } from './index-managment.component';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { ConfirmationDialogModule } from '@common/modules/confirmation-dialog/confirmation-dialog.module';
@NgModule({
declarations: [IndexManagmentComponent],
imports: [
CommonUiModule,
CommonFormsModule,
ConfirmationDialogModule,
IndexManagmentRoutingModule
]
})
export class IndexManagmentModule { }

View File

@ -1,15 +0,0 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { IndexManagmentComponent } from './index-managment.component';
import { AdminAuthGuard } from '@app/core/admin-auth-guard.service';
const routes: Routes = [
{ path: '', component: IndexManagmentComponent, canActivate: [AdminAuthGuard] },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class IndexManagmentRoutingModule { }

View File

@ -1,10 +1,47 @@
<!-- <div class="row root"> -->
<!-- <div class="row root">
<div class="d-flex justify-content-center">
<mat-card class="p-4 mt-4">
<div style="color: red;">Warning: Danger zone. Irreversible actions!</div>
<div>
<!-- <button mat-raised-button color="primary" (click)="migrateSemantics($event)" class="lightblue-btn button">Migrate semantics</button> -->
<!-- <button mat-raised-button color="primary" (click)="addRdaInSemantics($event)" class="lightblue-btn button">Add rda in semantics</button> -->
<button mat-raised-button color="primary" (click)="migrateSemantics($event)" class="lightblue-btn button">Migrate semantics</button>
<button mat-raised-button color="primary" (click)="addRdaInSemantics($event)" class="lightblue-btn button">Add rda in semantics</button>
</div>
</mat-card>
</div> -->
<div class="container-fluid">
<div class="row root">
<div class="col-md-10 offset-md-1">
<div class="row mt-4 mb-4"></div>
<mat-accordion multi>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
{{language.instant('MAINTENANCE-TASKS.SECTIONS.INDEXES.TITLE')}}
</mat-panel-title>
<mat-panel-description>
{{language.instant('MAINTENANCE-TASKS.SECTIONS.INDEXES.DESCRIPTION')}}
</mat-panel-description>
</mat-expansion-panel-header>
<div class="mt-2">
<button mat-raised-button color="primary" (click)="generateIndex($event)" class="lightblue-btn button">{{language.instant('MAINTENANCE-TASKS.SECTIONS.INDEXES.ACTIONS.GENERATE-INDEX')}}</button>
<button mat-raised-button color="primary" (click)="clearIndex($event)" class="lightblue-btn button">{{language.instant('MAINTENANCE-TASKS.SECTIONS.INDEXES.ACTIONS.CLEAR-INDEX')}}</button>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
{{language.instant('MAINTENANCE-TASKS.SECTIONS.EVENTS.TITLE')}}
</mat-panel-title>
<mat-panel-description>
{{language.instant('MAINTENANCE-TASKS.SECTIONS.EVENTS.DESCRIPTION')}}
</mat-panel-description>
</mat-expansion-panel-header>
<div class="mt-2">
<button mat-raised-button color="primary" (click)="sendUserTouchEvents($event)" class="lightblue-btn button">{{language.instant('MAINTENANCE-TASKS.SECTIONS.EVENTS.ACTIONS.SEND-USER-TOUCH')}}</button>
<button mat-raised-button color="primary" (click)="sendTenantTouchEvents($event)" class="lightblue-btn button">{{language.instant('MAINTENANCE-TASKS.SECTIONS.EVENTS.ACTIONS.SEND-TENANT-TOUCH')}}</button>
</div>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</div>

View File

@ -1,9 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { MaintenanceService } from '@app/core/services/maintenance/maintenance.service';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { BaseComponent } from '@common/base/base.component';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-maintenance-tasks',
@ -13,6 +16,8 @@ import { TranslateService } from '@ngx-translate/core';
export class MaintenanceTasksComponent extends BaseComponent implements OnInit {
constructor(
protected dialog: MatDialog,
protected language: TranslateService,
private maintenanceService: MaintenanceService,
private uiNotificationService: UiNotificationService,
private translate: TranslateService,
@ -53,6 +58,130 @@ export class MaintenanceTasksComponent extends BaseComponent implements OnInit {
// );
// }
generateIndex(ev: Event) {
this.dialog.open(ConfirmationDialogComponent, {
data: {
message: this.language.instant('MAINTENANCE-TASKS.CONFIRMATION.MESSAGE'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
},
maxWidth: '30em'
})
.afterClosed()
.subscribe(confirm => {
if (confirm) {
this.doGenerateIndex(ev);
}
});
}
private doGenerateIndex(ev: Event) {
(ev.target as HTMLButtonElement).disabled = true;
this.maintenanceService.generateElasticIndex().pipe(takeUntil(this._destroyed)).subscribe(
_ => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackSuccess();
},
error => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackError(error);
}
);
}
clearIndex(ev: Event) {
this.dialog.open(ConfirmationDialogComponent, {
data: {
message: this.language.instant('MAINTENANCE-TASKS.CONFIRMATION.MESSAGE'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
},
maxWidth: '30em'
})
.afterClosed()
.subscribe(confirm => {
if (confirm) {
this.doClearIndex(ev);
}
});
}
private doClearIndex(ev: Event) {
(ev.target as HTMLButtonElement).disabled = true;
this.maintenanceService.clearElasticIndex().pipe(takeUntil(this._destroyed)).subscribe(
_ => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackSuccess();
},
error => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackError(error);
}
);
}
sendUserTouchEvents(ev: Event) {
this.dialog.open(ConfirmationDialogComponent, {
data: {
message: this.language.instant('MAINTENANCE-TASKS.CONFIRMATION.MESSAGE'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
},
maxWidth: '30em'
})
.afterClosed()
.subscribe(confirm => {
if (confirm) {
this.doSendUserTouchEvents(ev);
}
});
}
private doSendUserTouchEvents(ev: Event) {
(ev.target as HTMLButtonElement).disabled = true;
this.maintenanceService.sendUserTouchEvents().pipe(takeUntil(this._destroyed)).subscribe(
_ => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackSuccess();
},
error => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackError(error);
}
);
}
sendTenantTouchEvents(ev: Event) {
this.dialog.open(ConfirmationDialogComponent, {
data: {
message: this.language.instant('MAINTENANCE-TASKS.CONFIRMATION.MESSAGE'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL')
},
maxWidth: '30em'
})
.afterClosed()
.subscribe(confirm => {
if (confirm) {
this.doSendTenantTouchEvents(ev);
}
});
}
private doSendTenantTouchEvents(ev: Event) {
(ev.target as HTMLButtonElement).disabled = true;
this.maintenanceService.sendTenantTouchEvents().pipe(takeUntil(this._destroyed)).subscribe(
_ => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackSuccess();
},
error => {
(ev.target as HTMLButtonElement).disabled = false;
this.onCallbackError(error);
}
);
}
onCallbackSuccess(): void {
this.uiNotificationService.snackBarNotification(this.translate.instant('GENERAL.SNACK-BAR.SUCCESSFUL-UPDATE'), SnackBarNotificationLevel.Success);
this.router.navigate(['/reload']).then(() => this.router.navigate(['/maintenance-tasks']));

View File

@ -45,13 +45,13 @@
<div class="col-12 col-lg-4">
<mat-form-field class="w-100">
<mat-label>{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.LANGUAGE' | translate}}</mat-label>
<mat-select [formControl]="formGroup.get('languageId')" name="language" required>
<mat-option *ngFor="let language of languages" [value]="language.id">
<mat-select [formControl]="formGroup.get('languageCode')" name="language" required>
<mat-option *ngFor="let language of languages" [value]="language.code">
{{language.code}}
</mat-option>
</mat-select>
<mat-error *ngIf="formGroup.get('languageId').hasError('backendError')">{{formGroup.get('languageId').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('backendError')">{{formGroup.get('languageCode').getError('backendError').message}}</mat-error>
<mat-error *ngIf="formGroup.get('languageCode').hasError('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
<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('required')">{{'GENERAL.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
</div>
</div>
<div class="col-12">
<mat-label>{{'NOTIFICATION-SERVICE.NOTIFICATION-TEMPLATE-EDITOR.FIELDS.BODY-TEXT' | translate}}</mat-label>
<editor class="w-100 mt-2" [init]="{
@ -143,7 +143,7 @@
toolbar:
'undo redo | formatselect | bold italic backcolor | \
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>
<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>
@ -191,7 +191,7 @@
<mat-icon>cancel</mat-icon>
</button>
</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"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true"
@ -225,7 +225,7 @@
<mat-icon>cancel</mat-icon>
</button>
</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"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true"
@ -259,7 +259,7 @@
<mat-icon>cancel</mat-icon>
</button>
</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"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[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-form-field>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
</form>

View File

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

View File

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

View File

@ -119,7 +119,7 @@ export class SidebarComponent implements OnInit {
if (this.authentication.hasPermission(AppPermission.ViewSupportiveMaterialPage)) this.adminItems.routes.push({ path: '/supportive-material', title: 'SIDE-BAR.SUPPORTIVE-MATERIAL', icon: 'help_center' });
if (this.authentication.hasPermission(AppPermission.ViewNotificationTemplatePage)) this.adminItems.routes.push({ path: '/notification-templates', title: 'SIDE-BAR.NOTIFICATION-TEMPLATES', icon: 'grid_guides' });
if (this.authentication.hasPermission(AppPermission.ViewNotificationPage)) this.adminItems.routes.push({ path: '/notifications', title: 'SIDE-BAR.NOTIFICATIONS', icon: 'notifications' });
if (this.authentication.hasPermission(AppPermission.ViewMaintenancePage)) this.adminItems.routes.push({ path: '/index-managment', title: 'SIDE-BAR.MAINTENANCE', icon: 'build' });
if (this.authentication.hasPermission(AppPermission.ViewMaintenancePage)) this.adminItems.routes.push({ path: '/maintenance-tasks', title: 'SIDE-BAR.MAINTENANCE', icon: 'build' });
this.groupMenuItems.push(this.adminItems);
this.infoItems = {

View File

@ -90,7 +90,8 @@
"USERS": "Users",
"PROFILE": "My Profile",
"LOGIN": "Login",
"DATASET-OVERVIEW": "Description Overview"
"DATASET-OVERVIEW": "Description Overview",
"MAINTENANCE-TASKS": "Maintenance"
},
"FILE-TRANSFORMER": {
"PDF": "PDF",
@ -271,6 +272,29 @@
"PRIVATE": "Hidden"
}
},
"MAINTENANCE-TASKS": {
"SECTIONS": {
"INDEXES": {
"TITLE": "Manage indexes",
"DESCRIPTION": "From here you can manage the Elastic indexes",
"ACTIONS": {
"GENERATE-INDEX": "Generate Index",
"CLEAR-INDEX": "Clear Index"
}
},
"EVENTS": {
"TITLE": "Manage events",
"DESCRIPTION": "From here you can manage the Message Queue events",
"ACTIONS": {
"SEND-USER-TOUCH": "Send user touch events",
"SEND-TENANT-TOUCH": "Send tenant touch events"
}
}
},
"CONFIRMATION": {
"MESSAGE": "Are you sure you want to perform this action?"
}
},
"DESCRIPTION-TEMPLATE-EDITOR": {
"TITLE": {
"NEW": "New API Client",

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.TenantScopeClaimInterceptor;
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.context.annotation.Configuration;
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.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
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
@Override
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
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) {
this.tenantCode = tenantCode;
this.tenantId = tenantId;
}
public TenantByCodeCacheValue(String tenantCode, UUID tenantId) {
this.tenantCode = tenantCode;
this.tenantId = tenantId;
}
private String tenantCode;
private String tenantCode;
public String getTenantCode() {
return tenantCode;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
private UUID tenantId;
private UUID tenantId;
public UUID getTenantId() {
return tenantId;
}
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
}
private final ConventionService conventionService;
private final ConventionService conventionService;
@Autowired
public TenantByCodeCacheService(TenantByCodeCacheOptions options, ConventionService conventionService) {
super(options);
this.conventionService = conventionService;
}
@Autowired
public TenantByCodeCacheService(TenantByCodeCacheOptions options, ConventionService conventionService) {
super(options);
this.conventionService = conventionService;
}
@EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantCode()));
if (!this.conventionService.isNullOrEmpty(event.getPreviousTenantCode())) this.evict(this.buildKey(event.getPreviousTenantCode()));
}
@EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode()))
this.evict(this.buildKey(event.getTenantCode()));
if (!this.conventionService.isNullOrEmpty(event.getPreviousTenantCode()))
this.evict(this.buildKey(event.getPreviousTenantCode()));
}
@Override
protected Class<TenantByCodeCacheValue> valueClass() {
return TenantByCodeCacheValue.class;
}
@Override
protected Class<TenantByCodeCacheValue> valueClass() {
return TenantByCodeCacheValue.class;
}
@Override
public String keyOf(TenantByCodeCacheValue value) {
return this.buildKey(value.getTenantCode());
}
@Override
public String keyOf(TenantByCodeCacheValue value) {
return this.buildKey(value.getTenantCode());
}
public String buildKey(String code) {
return this.generateKey(new HashMap<>() {{
put("$code$", code);
}});
}
public String buildKey(String code) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$code$", code);
return this.generateKey(keyParts);
}
}

View File

@ -14,63 +14,64 @@ import java.util.UUID;
@Service
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) {
this.tenantCode = tenantCode;
this.tenantId = tenantId;
}
public TenantByIdCacheValue(String tenantCode, UUID tenantId) {
this.tenantCode = tenantCode;
this.tenantId = tenantId;
}
private String tenantCode;
private String tenantCode;
public String getTenantCode() {
return tenantCode;
}
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
private UUID tenantId;
private UUID tenantId;
public UUID getTenantId() {
return tenantId;
}
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
}
private final ConventionService conventionService;
private final ConventionService conventionService;
@Autowired
public TenantByIdCacheService(TenantByIdCacheOptions options, ConventionService conventionService) {
super(options);
this.conventionService = conventionService;
}
@Autowired
public TenantByIdCacheService(TenantByIdCacheOptions options, ConventionService conventionService) {
super(options);
this.conventionService = conventionService;
}
@EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantId()));
}
@EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode()))
this.evict(this.buildKey(event.getTenantId()));
}
@Override
protected Class<TenantByIdCacheValue> valueClass() {
return TenantByIdCacheValue.class;
}
@Override
protected Class<TenantByIdCacheValue> valueClass() {
return TenantByIdCacheValue.class;
}
@Override
public String keyOf(TenantByIdCacheValue value) {
return this.buildKey(value.getTenantId());
}
@Override
public String keyOf(TenantByIdCacheValue value) {
return this.buildKey(value.getTenantId());
}
public String buildKey(UUID id) {
return this.generateKey(new HashMap<>() {{
put("$tenantId$", id.toString().toLowerCase(Locale.ROOT));
}});
}
public String buildKey(UUID id) {
HashMap<String, String> keyParts = new HashMap<>();
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.oidc.principal.CurrentPrincipalResolver;
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.common.enums.IsActive;
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.tenant.TenantScopedBaseEntity;
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.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.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@ -26,15 +35,9 @@ import org.springframework.web.context.request.WebRequest;
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 jakarta.persistence.criteria.Subquery;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
@Component
public class TenantInterceptor implements WebRequestInterceptor {
@ -45,11 +48,10 @@ public class TenantInterceptor implements WebRequestInterceptor {
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractor claimExtractor;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errorThesaurusProperties;
private final TenantScopeProperties tenantScopeProperties;
private final UserAllowedTenantCacheService userAllowedTenantCacheService;
private final ErrorThesaurusProperties errors;
private final QueryUtilsService queryUtilsService;
@PersistenceContext
public EntityManager entityManager;
@ -60,60 +62,70 @@ public class TenantInterceptor implements WebRequestInterceptor {
CurrentPrincipalResolver currentPrincipalResolver,
ClaimExtractor claimExtractor,
ApplicationContext applicationContext,
ErrorThesaurusProperties errorThesaurusProperties,
TenantScopeProperties tenantScopeProperties,
UserAllowedTenantCacheService userAllowedTenantCacheService,
ErrorThesaurusProperties errors) {
ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService) {
this.tenantScope = tenantScope;
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.applicationContext = applicationContext;
this.errorThesaurusProperties = errorThesaurusProperties;
this.tenantScopeProperties = tenantScopeProperties;
this.userAllowedTenantCacheService = userAllowedTenantCacheService;
this.errors = errors;
this.queryUtilsService = queryUtilsService;
}
@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.tenantScope.isMultitenant()) return;
boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant);
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) {
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;
UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant()));
if (cacheValue != null) {
isUserAllowedTenant = cacheValue.isAllowed();
if (this.tenantScope.isDefaultTenant()){
isUserAllowedTenant = true;
} else {
isUserAllowedTenant = this.isUserAllowedTenant();
this.userAllowedTenantCacheService.put(new UserAllowedTenantCacheService.UserAllowedTenantCacheValue(this.userScope.getUserId(), this.tenantScope.getTenant(), isUserAllowedTenant));
UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant()));
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) {
this.entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString());
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);
}
} else {
if (isAllowedNoTenant || this.isWhiteListedEndpoint(request)) {
tenantScope.setTenant(null, null);
} else {
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 {
if (!isAllowedNoTenant) {
if (!this.isWhiteListedEndpoint(request)) {
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;
}
private boolean isUserAllowedTenant() throws InvalidApplicationException {
private boolean isUserAllowedTenant() throws InvalidApplicationException, InterruptedException {
if (userScope.isSet()) {
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);
Subquery<TenantUserEntity> subQuery = query.subquery(TenantUserEntity.class);
Root<TenantUserEntity> subQueryRoot = subQuery.from(TenantUserEntity.class);
query.where(criteriaBuilder.and(
criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active),
criteriaBuilder.in(root.get(UserEntity._id)).value(subQuery.where(
criteriaBuilder.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)
)).select(subQueryRoot.get(TenantUserEntity._userId)).distinct(true)
criteriaBuilder.in(root.get(UserEntity._id)).value(queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(new BuildSubQueryInput.Builder<>(TenantUserEntity.class, UUID.class)
.query(query)
.criteriaBuilder(criteriaBuilder)
.keyPathFunc((subQueryRoot) -> subQueryRoot.get(TenantUserEntity._userId))
.filterFunc((subQueryRoot, cb) ->
{
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));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() > 0) return true;
List<UserEntity> results = this.entityManager.createQuery(query).getResultList();
return !results.isEmpty();
}
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.extractor.ClaimExtractor;
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.scope.tenant.TenantScope;
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.tools.exception.MyForbiddenException;
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.springframework.beans.factory.annotation.Autowired;
@ -22,176 +28,157 @@ import org.springframework.web.context.request.WebRequest;
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.UUID;
@Component
public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeClaimInterceptor.class));
private final TenantScope tenantScope;
private final ConventionService conventionService;
private final TenantScopeProperties tenantScopeProperties;
private final ErrorThesaurusProperties errorThesaurusProperties;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final String clientTenantClaimName;
private final ClaimExtractorContext claimExtractorContext;
private final TenantByCodeCacheService tenantByCodeCacheService;
private final TenantByIdCacheService tenantByIdCacheService;
@PersistenceContext
public EntityManager entityManager;
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeClaimInterceptor.class));
private final TenantScope tenantScope;
private final ConventionService conventionService;
private final TenantScopeProperties tenantScopeProperties;
private final ErrorThesaurusProperties errorThesaurusProperties;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final String clientTenantClaimName;
private final ClaimExtractorContext claimExtractorContext;
private final TenantByCodeCacheService tenantByCodeCacheService;
private final TenantByIdCacheService tenantByIdCacheService;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public TenantScopeClaimInterceptor(
TenantScope tenantScope,
ConventionService conventionService,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
ErrorThesaurusProperties errorThesaurusProperties,
TenantScopeProperties tenantScopeProperties,
ClaimExtractorContext claimExtractorContext,
TenantByCodeCacheService tenantByCodeCacheService,
TenantByIdCacheService tenantByIdCacheService
) {
this.tenantScope = tenantScope;
this.conventionService = conventionService;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.errorThesaurusProperties = errorThesaurusProperties;
this.tenantScopeProperties = tenantScopeProperties;
this.claimExtractorContext = claimExtractorContext;
this.tenantByCodeCacheService = tenantByCodeCacheService;
this.tenantByIdCacheService = tenantByIdCacheService;
this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + TenantScope.TenantClaimName;
}
@Autowired
public TenantScopeClaimInterceptor(
TenantScope tenantScope,
ConventionService conventionService,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
ErrorThesaurusProperties errorThesaurusProperties,
TenantScopeProperties tenantScopeProperties,
ClaimExtractorContext claimExtractorContext,
TenantByCodeCacheService tenantByCodeCacheService,
TenantByIdCacheService tenantByIdCacheService
) {
this.tenantScope = tenantScope;
this.conventionService = conventionService;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.errorThesaurusProperties = errorThesaurusProperties;
this.tenantScopeProperties = tenantScopeProperties;
this.claimExtractorContext = claimExtractorContext;
this.tenantByCodeCacheService = tenantByCodeCacheService;
this.tenantByIdCacheService = tenantByIdCacheService;
this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + ClaimNames.TenantClaimName;
}
@Override
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
@Override
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) {
Boolean scoped = this.scopeByPrincipal(this.tenantScope, principal);
if (!scoped) scoped = this.scopeByClient(this.tenantScope, principal);
if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant()) throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage());
}
}
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) {
boolean scoped = this.scopeByPrincipal(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());
}
}
private Boolean scopeByPrincipal(TenantScope scope, MyPrincipal principal) {
String tenantCode = this.claimExtractor.tenantString(principal);
if (tenantCode == null || tenantCode.isBlank()) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName);
private boolean scopeByPrincipal(MyPrincipal principal) {
String tenantCode = this.claimExtractor.tenantString(principal);
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 (tenantId == null && tenantCode == null) return false;
if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) {
tenantId = cacheValue.getTenantId();
} else {
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 (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 true;
}
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) {
tenantId = cacheValue.getTenantId();
} else {
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) {
tenantCode = cacheValue.getTenantCode();
} else {
tenantCode = this.getTenantCodeFromDatabase(tenantId);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
}
}
if (cacheValue != null) {
tenantCode = cacheValue.getTenantCode();
} else {
tenantCode = this.getTenantCodeFromDatabase(tenantId);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
}
}
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
}
return tenantId != null;
}
if (tenantId != null) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return true;
}
return false;
}
private Boolean scopeByClient(TenantScope scope, MyPrincipal principal) throws InvalidApplicationException {
String client = this.claimExtractor.client(principal);
private boolean scopeByClient(MyPrincipal principal) throws InvalidApplicationException {
String client = this.claimExtractor.client(principal);
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));
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, this.tenantScope.isSet(), (this.tenantScope.isSet() ? this.tenantScope.getTenant() : null));
return isWhiteListed && scope.isSet();
}
return isWhiteListed && this.tenantScope.isSet();
}
private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
Object o;
try {
o = results.get(0).get(TenantEntity._id);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return (UUID) o;
} catch (ClassCastException e) {
return null;
}
}
return null;
}
private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
return results.getFirst().getId();
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._id), tenantId),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
Object o;
try {
o = results.get(0).get(TenantEntity._code);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return (String) o;
} catch (ClassCastException e) {
return null;
}
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._id), tenantId),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
return results.getFirst().getCode();
}
return null;
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null);
this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter);
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null);
this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
@Override
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.extractor.ClaimExtractorContext;
import gr.cite.notification.authorization.ClaimNames;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.data.TenantEntity;
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.springframework.beans.factory.annotation.Autowired;
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.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.UUID;
@ -56,16 +56,22 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
}
@Override
public void preHandle(WebRequest request) throws InvalidApplicationException {
public void preHandle(@NotNull WebRequest request) {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
String tenantCode = request.getHeader(TenantScope.TenantClaimName);
logger.debug("retrieved request tenant header is: {header}", tenantCode);
if (this.conventionService.isNullOrEmpty(tenantCode)) return;
String tenantCode = request.getHeader(ClaimNames.TenantClaimName);
logger.debug("retrieved request tenant header is: {}", tenantCode);
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);
if (tenantId == null && tenantCode == null) return;
if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) {
@ -86,8 +92,8 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
}
}
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) {
logger.debug("parsed tenant header and set tenant id to {tenant}", tenantId);
if (tenantId != null) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
}
@ -95,7 +101,7 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
private UUID getTenantIdFromDatabase(String tenantCode) {
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);
query = query.where(
criteriaBuilder.and(
@ -103,27 +109,16 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).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) {
Object o;
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 results.getFirst().getId();
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) {
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);
query = query.where(
criteriaBuilder.and(
@ -131,20 +126,9 @@ public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).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) {
Object o;
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 results.getFirst().getCode();
}
return null;
}

View File

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

View File

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

View File

@ -14,77 +14,78 @@ import java.util.UUID;
@Service
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) {
this.userId = userId;
this.tenantId = tenantId;
this.isAllowed = isAllowed;
}
public UserAllowedTenantCacheValue(UUID userId, UUID tenantId, boolean isAllowed) {
this.userId = userId;
this.tenantId = tenantId;
this.isAllowed = isAllowed;
}
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;
}
private UUID tenantId;
private UUID tenantId;
public UUID getTenantId() {
return tenantId;
}
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
private boolean isAllowed;
private boolean isAllowed;
public boolean isAllowed() {
return isAllowed;
}
public boolean isAllowed() {
return isAllowed;
}
public void setAllowed(boolean allowed) {
isAllowed = allowed;
}
}
public void setAllowed(boolean allowed) {
isAllowed = allowed;
}
}
@Autowired
public UserAllowedTenantCacheService(UserAllowedTenantCacheOptions options) {
super(options);
}
@Autowired
public UserAllowedTenantCacheService(UserAllowedTenantCacheOptions options) {
super(options);
}
@EventListener
public void handleUserRemovedFromTenantEvent(UserRemovedFromTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
}
@EventListener
public void handleUserRemovedFromTenantEvent(UserRemovedFromTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
}
@EventListener
public void handleUserAddedToTenantEvent(UserAddedToTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
}
@EventListener
public void handleUserAddedToTenantEvent(UserAddedToTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
}
@Override
protected Class<UserAllowedTenantCacheValue> valueClass() {
return UserAllowedTenantCacheValue.class;
}
@Override
protected Class<UserAllowedTenantCacheValue> valueClass() {
return UserAllowedTenantCacheValue.class;
}
@Override
public String keyOf(UserAllowedTenantCacheValue value) {
return this.buildKey(value.getUserId(), value.getTenantId());
}
@Override
public String keyOf(UserAllowedTenantCacheValue value) {
return this.buildKey(value.getUserId(), value.getTenantId());
}
public String buildKey(UUID userId, UUID tenantId) {
return this.generateKey(new HashMap<>() {{
put("$user_id$", userId.toString().toLowerCase(Locale.ROOT));
put("$tenant_id$", tenantId.toString().toLowerCase(Locale.ROOT));
}});
}
public String buildKey(UUID userId, UUID tenantId) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$user_id$", userId.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;
//
//
//import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
//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.data.UserEntity;
//import gr.cite.notification.locale.LocaleService;
//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.transaction.TransactionDefinition;
//import org.springframework.transaction.TransactionStatus;
//import org.springframework.transaction.support.DefaultTransactionDefinition;
//import org.springframework.ui.ModelMap;
//import org.springframework.web.context.request.WebRequest;
//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.time.Instant;
//import java.util.List;
//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 LocaleService localeService;
// private final PlatformTransactionManager transactionManager;
// private final UserInterceptorCacheService userInterceptorCacheService;
// @PersistenceContext
// public EntityManager entityManager;
//
// @Autowired
// public UserInterceptor(
// UserScope userScope,
// LocaleService localeService,
// ClaimExtractor claimExtractor,
// CurrentPrincipalResolver currentPrincipalResolver,
// PlatformTransactionManager transactionManager,
// UserInterceptorCacheService userInterceptorCacheService
// ) {
// this.userScope = userScope;
// this.localeService = localeService;
// this.currentPrincipalResolver = currentPrincipalResolver;
// this.claimExtractor = claimExtractor;
// this.transactionManager = transactionManager;
// this.userInterceptorCacheService = userInterceptorCacheService;
// }
//
// @Override
// public void preHandle(WebRequest request) throws InvalidApplicationException {
// UUID userId = null;
// if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) {
// String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal());
//
// UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
// if (cacheValue != null) {
// userId = cacheValue.getUserId();
// } else {
// userId = this.getUserIdFromDatabase(subjectId);
// if (userId == null) userId = this.createUser(subjectId);
//
// this.userInterceptorCacheService.put(new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId));
// }
// }
// 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) {
// }
//}
package gr.cite.notification.web.scope.user;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
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 org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import java.util.UUID;
@Component
public class UserInterceptor implements WebRequestInterceptor {
private final UserScope userScope;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final UserInterceptorCacheService userInterceptorCacheService;
private final QueryFactory queryFactory;
@Autowired
public UserInterceptor(
UserScope userScope,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
UserInterceptorCacheService userInterceptorCacheService,
QueryFactory queryFactory) {
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.userInterceptorCacheService = userInterceptorCacheService;
this.queryFactory = queryFactory;
}
@Override
public void preHandle(@NotNull 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.findExistingUserFromDb(subjectId);
if (userId != null) {
cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
this.userInterceptorCacheService.put(cacheValue);
}
}
}
this.userScope.setUserId(userId);
}
private UUID findExistingUserFromDb(String subjectId) {
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user));
if (userCredential != null) {
return userCredential.getUserId();
}
return null;
}
@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;
//
//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 {
//}
package gr.cite.notification.web.scope.user;
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,77 +1,67 @@
//package gr.cite.notification.web.scope.user;
//
//import gr.cite.notification.convention.ConventionService;
//import gr.cite.notification.event.UserTouchedEvent;
//import gr.cite.tools.cache.CacheService;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.context.event.EventListener;
//import org.springframework.stereotype.Service;
//
//import java.util.HashMap;
//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;
// }
// }
//
// private final ConventionService conventionService;
//
// @Autowired
// public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) {
// super(options);
// this.conventionService = conventionService;
// }
//
// @EventListener
// public void handleUserTouchedEvent(UserTouchedEvent event) {
// if (!this.conventionService.isNullOrEmpty(event.getSubjectId())) this.evict(this.buildKey(event.getSubjectId()));
// if (!this.conventionService.isNullOrEmpty(event.getPreviousSubjectId())) this.evict(this.buildKey(event.getPreviousSubjectId()));
// }
//
// @Override
// protected Class<UserInterceptorCacheValue> valueClass() {
// return UserInterceptorCacheValue.class;
// }
//
// @Override
// public String keyOf(UserInterceptorCacheValue value) {
// return this.buildKey(value.getSubjectId());
// }
//
//
// public String buildKey(String subject) {
// return this.generateKey(new HashMap<>() {{
// put("$subject$", subject);
// }});
// }
//}
package gr.cite.notification.web.scope.user;
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.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;
}
public String getSubjectId() {
return subjectId;
}
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}
private String subjectId;
private UUID userId;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}
@Autowired
public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) {
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

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

View File

@ -20,11 +20,25 @@ idpclient:
Roles:
- type: resource_access
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:
- type: scope
AccessToken:
- type: x-access-token
visibility: SENSITIVE
Tenant:
- type: x-tenant
IssuedAt:
- type: iat
Issuer:
@ -37,5 +51,8 @@ idpclient:
- type: azp
Authorities:
- type: authorities
ExternalProviderName:
- type: identity_provider
TenantCodes:
- type: tenant_roles
filterBy: "(.*):(.*)"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g2]]"

View File

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

View File

@ -25,9 +25,6 @@ queue:
enable: false
options:
exchange: null
forget-me-completed-topic: forgetme.completed
what-you-know-about-me-completed-topic: whatyouknowaboutme.completed
generate-file-topic: generate.file
rabbitmq:
enable: false
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:
is-multitenant: false
interceptor:
client-claims-prefix: client_
white-listed-clients: [ ]
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")
public class MultitenancyProperties {
private boolean isMultitenant;
private String defaultTenantCode;
public boolean isMultitenant() {
return isMultitenant;
@ -13,4 +14,16 @@ public class MultitenancyProperties {
public void setIsMultitenant(boolean 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;
import gr.cite.notification.data.tenant.TenantScopedBaseEntity;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import org.hibernate.Session;
import org.slf4j.LoggerFactory;
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.web.context.annotation.RequestScope;
import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
@Component
@RequestScope
public class TenantScope {
public static final String TenantReplaceParameter = "::TenantCode::";
public static final String TenantCodesClaimName = "TenantCodes";
public static final String TenantClaimName = "x-tenant";
private MultitenancyProperties multitenancy;
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScope.class));
private UUID tenant = null;
private String tenantCode = null;
private UUID initialTenant = null;
private String initialTenantCode = null;
private final MultitenancyProperties multitenancy;
private final AtomicReference<UUID> tenant = new AtomicReference<>();
private final AtomicReference<String> tenantCode = new AtomicReference<>();
private final AtomicReference<UUID> initialTenant = new AtomicReference<>();
private final AtomicReference<String> initialTenantCode = new AtomicReference<>();
@Autowired
public TenantScope(MultitenancyProperties multitenancy) {
@ -39,49 +30,79 @@ public class TenantScope {
return multitenancy.isMultitenant();
}
public String getDefaultTenantCode() {
return multitenancy.getDefaultTenantCode();
}
public Boolean isSet() {
if (!this.isMultitenant()) return true;
return this.tenant != null;
if (!this.isMultitenant())
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 {
if (!this.isMultitenant()) return null;
if (this.tenant == null) throw new InvalidApplicationException("tenant not set");
return this.tenant;
if (!this.isMultitenant())
return null;
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 {
if (!this.isMultitenant()) return null;
if (this.tenant == null) throw new InvalidApplicationException("tenant not set");
return this.tenantCode;
if (!this.isMultitenant())
return null;
if (this.tenantCode.get() == null)
throw new InvalidApplicationException("tenant not set");
return this.tenantCode.get();
}
public void setTempTenant(EntityManager entityManager, UUID tenant, String tenantCode) {
this.tenant = tenant;
this.tenant.set(tenant);
this.tenantCode.set(tenantCode);
if(this.tenant != null) {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.tenant.toString());
if (this.tenant.get() != null && !this.isDefaultTenant()) {
if(!this.isDefaultTenant()) {
entityManager
.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) {
this.tenant = this.initialTenant;
this.tenantCode = this.initialTenantCode;
if(this.initialTenant != null) {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.initialTenant.toString());
public void removeTempTenant(EntityManager entityManager) {
this.tenant.set(this.initialTenant.get());
this.tenantCode.set(this.initialTenantCode.get());
if (this.initialTenant.get() != null && !this.isDefaultTenant()) {
if(!this.isDefaultTenant()) {
entityManager
.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) {
if (this.isMultitenant()) {
this.tenant = tenant;
this.initialTenant = tenant;
this.tenantCode = tenantCode;
this.initialTenantCode = tenantCode;
this.tenant.set(tenant);
this.initialTenant.set(tenant);
this.tenantCode.set(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";
@Column(name = "\"language\"", columnDefinition = "uuid", nullable = false)
private UUID languageId;
@Column(name = "\"language_code\"", nullable = false, length = 200)
private String languageCode;
public static final String _languageId = "languageId";
public static final String _languageCode = "languageCode";
@Column(name = "\"value\"", nullable = false)
private String value;
@ -97,12 +97,12 @@ public class NotificationTemplateEntity extends TenantScopedBaseEntity {
this.kind = kind;
}
public UUID getLanguageId() {
return languageId;
public String getLanguageCode() {
return languageCode;
}
public void setLanguageId(UUID languageId) {
this.languageId = languageId;
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
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;
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 jakarta.persistence.EntityManager;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.management.InvalidApplicationException;
import jakarta.persistence.EntityManager;
@Aspect
@Component
public class TenantFilterAspect {
private final TenantScope tenantScope;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ApplicationContext applicationContext;
@Autowired
public TenantFilterAspect(
TenantScope tenantScope,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
ApplicationContext applicationContext
TenantScope tenantScope
) {
this.tenantScope = tenantScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.applicationContext = applicationContext;
}
@AfterReturning(
pointcut="bean(entityManagerFactory) && execution(* createEntityManager(..))",
returning="retVal")
pointcut = "bean(entityManagerFactory) && execution(* createEntityManager(..))",
returning = "retVal")
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.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.TenantScoped;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.management.InvalidApplicationException;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import java.util.UUID;
public class TenantListener {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantListener.class));
private final TenantScope tenantScope;
private final ErrorThesaurusProperties errors;
@Autowired
public TenantListener(
TenantScope tenantScope
TenantScope tenantScope, ErrorThesaurusProperties errors
) {
this.tenantScope = tenantScope;
this.errors = errors;
}
@PrePersist
public void setTenantOnCreate(TenantScoped entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant()) {
final UUID tenantId = tenantScope.getTenant();
entity.setTenantId(tenantId);
if (entity.getTenantId() != null && (this.tenantScope.isDefaultTenant() || entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) {
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 {
entity.setTenantId(null);
}
@ -38,22 +49,30 @@ public class TenantListener {
@PreRemove
public void setTenantOnUpdate(TenantScoped entity) throws InvalidApplicationException {
if (tenantScope.isMultitenant()) {
if (entity.getTenantId() == null) {
logger.error("somebody tried to set null tenant");
throw new MyForbiddenException("tenant tampering");
}
if (entity.getTenantId().compareTo(tenantScope.getTenant()) != 0) {
logger.error("somebody tried to change an entries tenant");
throw new MyForbiddenException("tenant tampering");
}
if (!tenantScope.isDefaultTenant()) {
if (entity.getTenantId() == null) {
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");
throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
}
final UUID tenantId = tenantScope.getTenant();
entity.setTenantId(tenantId);
final UUID tenantId = tenantScope.getTenant();
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 {
if (entity.getTenantId() != null) {
logger.error("somebody tried to set non null tenant");
throw new MyForbiddenException("tenant tampering");
if (entity.getTenantId() != null && (!this.tenantScope.isDefaultTenant() ||entity.getTenantId().compareTo(tenantScope.getTenant()) != 0)) {
logger.error("somebody tried to change an entries tenant");
throw new MyForbiddenException(this.errors.getTenantTampering().getCode(), this.errors.getTenantTampering().getMessage());
}
}
}
}

View File

@ -1,15 +1,13 @@
package gr.cite.notification.data.tenant;
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.FilterDef;
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.util.UUID;
@ -17,23 +15,20 @@ import java.util.UUID;
//@Getter
//@Setter
//@NoArgsConstructor
@FilterDef(name = TenantScopedBaseEntity.tenantFilter, parameters = {@ParamDef(name = TenantScopedBaseEntity.tenantFilterTenantParam, type = String.class)})
@Filter(name = "tenantFilter", condition = "tenant = (cast(:tenantId as uuid))")
@FilterDef(name = TenantScopedBaseEntity.TENANT_FILTER, parameters = {@ParamDef(name = TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, type = String.class)})
@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)
public abstract class TenantScopedBaseEntity implements TenantScoped, Serializable {
@Serial
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";
public static final String tenantFilterTenantParam = "tenantId";
@Column(name = "tenant", columnDefinition = "uuid")
@Column(name = "tenant", columnDefinition = "uuid", nullable = true)
private UUID tenantId;
public static final String _tenantId = "tenantId";
public UUID getTenantId() {
return tenantId;
}
@ -42,5 +37,5 @@ public abstract class TenantScopedBaseEntity implements TenantScoped, Serializab
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
}

View File

@ -15,6 +15,8 @@ public class ErrorThesaurusProperties {
private ErrorDescription singleTenantConfigurationPerTypeSupported;
private ErrorDescription incompatibleTenantConfigurationTypes;
private ErrorDescription overlappingTenantConfigurationNotifierList;
private ErrorDescription tenantNotAllowed;
private ErrorDescription tenantTampering;
public ErrorDescription getHashConflict() {
return hashConflict;
@ -89,4 +91,19 @@ public class ErrorThesaurusProperties {
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;
import gr.cite.notification.data.QueueOutboxEntity;
import gr.cite.notification.integrationevent.outbox.OutboxProperties;
import gr.cite.notification.integrationevent.outbox.OutboxRepositoryImpl;
import gr.cite.queueoutbox.IntegrationEventContextCreator;
@ -56,7 +57,11 @@ public class OutboxIntegrationEventConfigurer extends OutboxConfigurer {
@Bean
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

View File

@ -1,6 +1,7 @@
package gr.cite.notification.integrationevent.inbox;
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.jwt.JwtClaimNames;
@ -22,12 +23,15 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
this.isAuthenticated = isAuthenticated;
}
public static InboxPrincipal build(IntegrationEventProperties properties) {
public static InboxPrincipal build(IntegrationEventProperties properties, ClaimExtractorProperties claimExtractorProperties) {
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("nbf", Instant.now().minus(30, ChronoUnit.SECONDS).toString());
inboxPrincipal.put("exp", Instant.now().plus(10, ChronoUnit.MINUTES).toString());
List<ClaimExtractorProperties.KeyPath> notBeforeKey = claimExtractorProperties.getMapping().getOrDefault("NotBefore", null);
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;
}
@ -45,7 +49,10 @@ public class InboxPrincipal implements MyPrincipal, ClaimAccessor {
public List<String> getClaimAsStringList(String claim) {
if (claims == null)
return null;
return this.getClaimAsStringList(claim);
if (this.claims.containsKey(claim)){
return List.of(this.claims.get(claim).toString());
}
return null;
}
@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.scope.fake.FakeRequestScope;
import gr.cite.notification.data.QueueInboxEntity;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.integrationevent.TrackedEvent;
import gr.cite.notification.integrationevent.inbox.notify.NotifyIntegrationEventHandler;
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.InboxRepository;
import gr.cite.queueinbox.task.MessageOptions;
import gr.cite.rabbitmq.IntegrationEventMessageConstants;
import gr.cite.rabbitmq.consumer.InboxCreatorParams;
import gr.cite.tools.data.query.Ordering;
import gr.cite.tools.data.query.QueryFactory;
@ -241,7 +243,14 @@ public class InboxRepositoryImpl implements InboxRepository {
QueueInboxEntity queueMessage = new QueueInboxEntity();
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.setRoute(inboxCreatorParams.getRoutingKey());
queueMessage.setQueue(inboxCreatorParams.getQueueName());
@ -269,6 +278,10 @@ public class InboxRepositoryImpl implements InboxRepository {
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
TenantEntityManager tenantEntityManager = this.applicationContext.getBean(TenantEntityManager.class);
tenantEntityManager.setEntityManager(entityManager);
transaction.begin();
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());
} else {
EventProcessingStatus status = this.processMessage(queueInboxMessage.getRoute(), queueInboxMessage.getMessageId().toString(), queueInboxMessage.getApplicationId(), queueInboxMessage.getMessage());
EventProcessingStatus status = this.processMessage(queueInboxMessage);
switch (status) {
case Success: {
queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL);
@ -321,38 +334,39 @@ public class InboxRepositoryImpl implements InboxRepository {
return success;
}
private EventProcessingStatus processMessage(String routingKey, String messageId, String appId, String message) {
private EventProcessingStatus processMessage(QueueInboxEntity queueInboxMessage) {
IntegrationEventHandler handler;
logger.debug("Processing message with routing key '{}'", routingKey);
if (this.routingKeyMatched(routingKey, this.inboxProperties.getNotifyTopic()))
handler = this.applicationContext.getBean(NotifyIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getTenantRemovalTopic()))
logger.debug("Processing message with routing key '{}'", queueInboxMessage.getRoute());
if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getTenantRemovalTopic()))
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);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserRemovalTopic()))
else if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getUserRemovalTopic()))
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);
else if (this.routingKeyMatched(queueInboxMessage.getRoute(), this.inboxProperties.getNotifyTopic()))
handler = this.applicationContext.getBean(NotifyIntegrationEventHandler.class);
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;
}
if (handler == null)
return EventProcessingStatus.Discard;
IntegrationEventProperties properties = new IntegrationEventProperties();
properties.setAppId(appId);
properties.setMessageId(messageId);
properties.setAppId(queueInboxMessage.getApplicationId());
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))
// {
try {
return handler.handle(properties, message);
return handler.handle(properties, queueInboxMessage.getMessage());
} 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;
}
// }

View File

@ -1,10 +1,13 @@
package gr.cite.notification.integrationevent.inbox;
import java.util.UUID;
public class IntegrationEventProperties {
private String messageId;
private String appId;
private UUID tenantId;
public String getMessageId() {
return messageId;
@ -22,4 +25,11 @@ public class IntegrationEventProperties {
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 tenantId;
private UUID notificationType;
private NotificationContactType contactTypeHint;
@ -32,14 +30,6 @@ public class NotifyIntegrationEvent extends TrackedEvent {
this.userId = userId;
}
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
public UUID getNotificationType() {
return notificationType;
}

View File

@ -1,6 +1,7 @@
package gr.cite.notification.integrationevent.inbox.notify;
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.common.JsonHandlingService;
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.tenant.TenantScope;
import gr.cite.notification.data.TenantEntity;
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.InboxPrincipal;
@ -46,17 +48,30 @@ public class NotifyIntegrationEventHandlerImpl implements NotifyIntegrationEvent
private final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errors;
private final MessageSource messageSource;
public NotifyIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext, ErrorThesaurusProperties errors, MessageSource messageSource) {
private final QueryFactory queryFactory;
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.applicationContext = applicationContext;
this.errors = errors;
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
@ -84,72 +99,42 @@ public class NotifyIntegrationEventHandlerImpl implements NotifyIntegrationEvent
model.setProvenanceRef(event.getProvenanceRef());
model.setNotifiedAt(Instant.now());
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
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()) {
EventProcessingStatus status = EventProcessingStatus.Success;
try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
NotifyConsistencyHandler notifyConsistencyHandler = this.applicationContext.getBean(NotifyConsistencyHandler.class);
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();
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
// logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
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) {
status = EventProcessingStatus.Error;
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;
import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler;
public interface TenantRemovalIntegrationEventHandler extends IntegrationEventHandler {

View File

@ -1,24 +1,18 @@
package gr.cite.notification.integrationevent.inbox.tenantremoval;
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.common.JsonHandlingService;
import gr.cite.notification.common.scope.fake.FakeRequestScope;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.integrationevent.inbox.EventProcessingStatus;
import gr.cite.notification.integrationevent.inbox.InboxPrincipal;
import gr.cite.notification.integrationevent.inbox.IntegrationEventProperties;
import gr.cite.notification.service.tenant.TenantService;
import gr.cite.tools.auditing.AuditService;
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.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope;
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 final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errors;
private final MessageSource messageSource;
public TenantRemovalIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ApplicationContext applicationContext, ErrorThesaurusProperties errors, MessageSource messageSource) {
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractorProperties claimExtractorProperties;
private final TenantService tenantService;
private final AuditService auditService;
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.applicationContext = applicationContext;
this.errors = errors;
this.messageSource = messageSource;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
this.tenantRemovalConsistencyHandler = tenantRemovalConsistencyHandler;
}
@Override
@ -52,60 +48,34 @@ public class TenantRemovalIntegrationEventHandlerImpl implements TenantRemovalIn
if (event == null)
return EventProcessingStatus.Error;
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
EventProcessingStatus status = EventProcessingStatus.Success;
try {
TenantRemovalConsistencyHandler tenantRemovalConsistencyHandler = this.applicationContext.getBean(TenantRemovalConsistencyHandler.class);
if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId()))))
return EventProcessingStatus.Postponed;
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
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();
if (!(tenantRemovalConsistencyHandler.isConsistent(new TenantRemovalConsistencyPredicates(event.getId())))) {
status = EventProcessingStatus.Postponed;
currentPrincipalResolver.pop();
return status;
}
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) {
status = EventProcessingStatus.Error;
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;
import gr.cite.notification.integrationevent.TrackedEvent;
import java.util.UUID;

View File

@ -1,9 +1,10 @@
package gr.cite.notification.integrationevent.inbox.tenanttouched;
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.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.InboxPrincipal;
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.logging.LoggerService;
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.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
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));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService;
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) {
this.applicationContext = applicationContext;
public TenantTouchedIntegrationEventHandlerImpl(JsonHandlingService jsonHandlingService, ValidatorFactory validatorFactory, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, TenantService tenantService, AuditService auditService, TenantEntityManager tenantEntityManager) {
this.jsonHandlingService = jsonHandlingService;
this.validatorFactory = validatorFactory;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractorProperties = claimExtractorProperties;
this.tenantService = tenantService;
this.auditService = auditService;
this.tenantEntityManager = tenantEntityManager;
}
@Override
@ -53,55 +57,25 @@ public class TenantTouchedIntegrationEventHandlerImpl implements TenantTouchedIn
model.setCode(event.getCode());
this.validatorFactory.validator(TenantTouchedIntegrationEventPersist.TenantTouchedIntegrationEventPersistValidator.class).validateForce(model);
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
EventProcessingStatus status = EventProcessingStatus.Success;
tenantEntityManager.disableTenantFilters();
try {
currentPrincipalResolver.push(InboxPrincipal.build(properties, claimExtractorProperties));
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
tenantService.persist(model, null);
transaction = entityManager.getTransaction();
transaction.begin();
auditService.track(AuditableAction.Tenant_Persist, Map.ofEntries(
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) {
status = EventProcessingStatus.Error;
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;
import gr.cite.notification.integrationevent.inbox.ConsistencyPredicates;
import java.util.UUID;

View File

@ -8,7 +8,6 @@ public class UserRemovalIntegrationEvent extends TrackedEvent {
private UUID userId;
private UUID tenant;
public UUID getUserId() {
return userId;
@ -17,12 +16,4 @@ public class UserRemovalIntegrationEvent extends TrackedEvent {
public void setUserId(UUID 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;
import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler;
public interface UserRemovalIntegrationEventHandler extends IntegrationEventHandler {

View File

@ -1,11 +1,12 @@
package gr.cite.notification.integrationevent.inbox.userremoval;
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.common.JsonHandlingService;
import gr.cite.notification.common.scope.fake.FakeRequestScope;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.data.TenantEntity;
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.InboxPrincipal;
@ -18,13 +19,8 @@ import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.fieldset.BaseFieldSet;
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.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.i18n.LocaleContextHolder;
@ -41,22 +37,35 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
private final JsonHandlingService jsonHandlingService;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errors;
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(
JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext,
ErrorThesaurusProperties errors,
MessageSource messageSource
JsonHandlingService jsonHandlingService,
ErrorThesaurusProperties errors,
MessageSource messageSource, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, UserRemovalConsistencyHandler userRemovalConsistencyHandler, UserService userService, AuditService auditService, TenantEntityManager tenantEntityManager
) {
this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
this.errors = errors;
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
@ -69,73 +78,44 @@ public class UserRemovalIntegrationEventHandlerImpl implements UserRemovalIntegr
logger.debug("Handling {}", UserRemovalIntegrationEvent.class.getSimpleName());
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
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()) {
EventProcessingStatus status = EventProcessingStatus.Success;
try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
currentPrincipalResolver.push(InboxPrincipal.build(properties));
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 {
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();
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
// logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
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) {
status = EventProcessingStatus.Error;
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.errorcode.ErrorThesaurusProperties;
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 org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.MessageSource;
@ -23,22 +23,24 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
public static final String _id = "id";
private UUID tenant;
private String name;
public static final String _name = "name";
public static final int _nameLength = 200;
private String subjectId;
public static final String _subjectId = "subjectId";
private UserProfile profile;
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() {
return id;
}
@ -47,14 +49,6 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
this.id = id;
}
public UUID getTenant() {
return tenant;
}
public void setTenant(UUID tenant) {
this.tenant = tenant;
}
public String getName() {
return name;
}
@ -63,12 +57,20 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
this.name = name;
}
public String getSubjectId() {
return subjectId;
public List<TenantUser> getTenantUsers() {
return tenantUsers;
}
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
public void setTenantUsers(List<TenantUser> tenantUsers) {
this.tenantUsers = tenantUsers;
}
public List<UserCredential> getCredentials() {
return credentials;
}
public void setCredentials(List<UserCredential> credentials) {
this.credentials = credentials;
}
public UserProfile getProfile() {
@ -124,9 +126,15 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
private ContactInfoType type;
public static final String _type = "type";
private String value;
private int ordinal;
public static final String _value = "value";
private Integer ordinal;
public static final String _ordinal = "ordinal";
public ContactInfoType getType() {
return type;
@ -144,16 +152,135 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
this.value = value;
}
public int getOrdinal() {
public Integer getOrdinal() {
return ordinal;
}
public void setOrdinal(int ordinal) {
public void setOrdinal(Integer 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)
public static class UserTouchedIntegrationEventValidator extends BaseValidator<UserTouchedIntegrationEvent> {
@ -161,9 +288,12 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
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);
this.messageSource = messageSource;
this.validatorFactory = validatorFactory;
}
@Override
@ -183,12 +313,14 @@ public class UserTouchedIntegrationEvent extends TrackedEvent {
.failOn(UserTouchedIntegrationEvent._name).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._name}, LocaleContextHolder.getLocale())),
this.spec()
.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())),
this.spec()
.must(() -> !this.isEmpty(item.getSubjectId()))
.failOn(UserTouchedIntegrationEvent._subjectId).failWith(messageSource.getMessage("Validation_Required", new Object[]{UserTouchedIntegrationEvent._subjectId}, LocaleContextHolder.getLocale()))
);
this.navSpec()
.iff(() -> !this.isListNullOrEmpty(item.getUserContactInfo()))
.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;
import gr.cite.notification.integrationevent.inbox.IntegrationEventHandler;
public interface UserTouchedIntegrationEventHandler extends IntegrationEventHandler {

View File

@ -1,11 +1,12 @@
package gr.cite.notification.integrationevent.inbox.usertouched;
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.common.JsonHandlingService;
import gr.cite.notification.common.scope.fake.FakeRequestScope;
import gr.cite.notification.common.scope.tenant.TenantScope;
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.InboxPrincipal;
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.logging.LoggerService;
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.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
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));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService;
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(
JsonHandlingService jsonHandlingService,
ApplicationContext applicationContext,
ValidatorFactory validatorFactory) {
JsonHandlingService jsonHandlingService,
ValidatorFactory validatorFactory, QueryFactory queryFactory, TenantScope tenantScope, CurrentPrincipalResolver currentPrincipalResolver, ClaimExtractorProperties claimExtractorProperties, UserService userService, AuditService auditService, TenantEntityManager tenantEntityManager) {
this.jsonHandlingService = jsonHandlingService;
this.applicationContext = applicationContext;
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
@ -61,69 +68,37 @@ public class UserTouchedIntegrationEventHandlerImpl implements UserTouchedIntegr
this.validatorFactory.validator(UserTouchedIntegrationEvent.UserTouchedIntegrationEventValidator.class).validateForce(event);
EntityManager entityManager = null;
EntityTransaction transaction = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
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()) {
EventProcessingStatus status = EventProcessingStatus.Success;
try {
if (this.tenantScope.isMultitenant() && properties.getTenantId() != null) {
TenantEntity tenant = queryFactory.query(TenantQuery.class).ids(properties.getTenantId()).firstAs(new BaseFieldSet().ensure(Tenant._id).ensure(Tenant._code));
if (tenant == null) {
logger.error("missing tenant from event message");
return EventProcessingStatus.Error;
}
//
// ValidationService validator = this.applicationContext.getBean(ValidationService.class);
// validator.validateForce(model);
CurrentPrincipalResolver currentPrincipalResolver = this.applicationContext.getBean(CurrentPrincipalResolver.class);
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();
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), properties.getTenantId(), tenant.getCode());
} else if (this.tenantScope.isMultitenant()) {
// logger.error("missing tenant from event message");
// return EventProcessingStatus.Error;
this.tenantScope.setTempTenant(tenantEntityManager.getEntityManager(), null, this.tenantScope.getDefaultTenantCode());
}
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) {
status = EventProcessingStatus.Error;
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 tenantTouchTopic;
private final String tenantRemovalTopic;
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
public OutboxProperties(String exchange
) {
this.exchange = exchange;
this.tenantTouchTopic = tenantTouchTopic;
this.tenantRemovalTopic = tenantRemovalTopic;
this.userTouchTopic = userTouchTopic;
this.userRemovalTopic = userRemovalTopic;
this.notifyTopic = notifyTopic;
}
public String getExchange() {
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 org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -32,9 +30,11 @@ import java.util.stream.Collectors;
public class OutboxRepositoryImpl implements OutboxRepository {
protected final ApplicationContext applicationContext;
private final Random random = new Random();
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(OutboxRepositoryImpl.class));
private final JsonHandlingService jsonHandlingService;
private final OutboxProperties outboxProperties;
public OutboxRepositoryImpl(
@ -88,14 +88,17 @@ public class OutboxRepositoryImpl implements OutboxRepository {
} 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();
if (transaction != null)
transaction.rollback();
candidate = null;
} catch (Exception 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;
} finally {
if (entityManager != null) entityManager.close();
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) {
logger.error("Problem getting list of queue outbox. Skipping: {}", ex.getMessage(), ex);
@ -137,10 +140,12 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit();
} catch (Exception 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;
} finally {
if (entityManager != null) entityManager.close();
if (entityManager != null)
entityManager.close();
}
} catch (Exception 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();
} catch (Exception 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;
} finally {
if (entityManager != null) entityManager.close();
if (entityManager != null)
entityManager.close();
}
} catch (Exception 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();
} catch (Exception 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;
} finally {
if (entityManager != null) entityManager.close();
if (entityManager != null)
entityManager.close();
}
} catch (Exception 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();
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 {
for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) {
@ -281,9 +290,11 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit();
} catch (Exception 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 {
if (entityManager != null) entityManager.close();
if (entityManager != null)
entityManager.close();
}
} catch (Exception 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();
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 {
for (QueueOutboxEntity queueOutboxMessage : queueOutboxMessages) {
@ -323,9 +334,11 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit();
} catch (Exception 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 {
if (entityManager != null) entityManager.close();
if (entityManager != null)
entityManager.close();
}
} catch (Exception 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) {
EntityTransaction transaction = null;
EntityManager entityManager = null;
boolean success = false;
QueueOutboxEntity queueMessage = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
@ -354,10 +366,11 @@ public class OutboxRepositoryImpl implements OutboxRepository {
transaction.commit();
} catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
if (transaction != null) transaction.rollback();
success = false;
if (transaction != null)
transaction.rollback();
} finally {
if (entityManager != null) entityManager.close();
if (entityManager != null)
entityManager.close();
}
} catch (Exception 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();
// 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()));
// //this._logTrackingService.Trace(correlationId.ToString(), $"Correlating current tracking context with new correlationId {correlationId}");
//
// QueueOutboxEntity queueMessage = new QueueOutboxEntity();
// queueMessage.setId(UUID.randomUUID());
// queueMessage.setTenantId(null);
// queueMessage.setTenantId(event.getTenantId());
// queueMessage.setExchange(this.outboxProperties.getExchange());
// queueMessage.setRoute(routingKey);
// 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;
public static final String _kind = "kind";
private Language language;
public static final String _language = "language";
private String languageCode;
public static final String _languageCode = "languageCode";
private NotificationTemplateValue value;
public static final String _value = "value";
@ -76,12 +76,12 @@ public class NotificationTemplate {
this.kind = kind;
}
public Language getLanguage() {
return language;
public String getLanguageCode() {
return languageCode;
}
public void setLanguage(Language language) {
this.language = language;
public void setLanguageCode(String languageCode) {
this.languageCode = languageCode;
}
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.convention.ConventionService;
import gr.cite.notification.data.NotificationTemplateEntity;
import gr.cite.notification.model.Language;
import gr.cite.notification.model.NotificationTemplate;
import gr.cite.notification.model.Tenant;
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.tools.data.builder.BuilderFactory;
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));
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<>();
for(NotificationTemplateEntity d : data){
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._notificationType))) m.setNotificationType(d.getNotificationType());
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){
NotificationTemplateValueEntity value = this.jsonHandlingService.fromJsonSafe(NotificationTemplateValueEntity.class, d.getValue());
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;
}
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 {
if (fields.isEmpty() || datas.isEmpty()) return null;
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.data.InAppNotificationEntity;
import gr.cite.notification.data.TenantScopedEntityManager;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.InAppNotificationQuery;
import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,19 +27,16 @@ import java.util.stream.Collectors;
public class InAppNotificationDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationDeleter.class));
private final TenantScopedEntityManager entityManager;
private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory;
@Autowired
public InAppNotificationDeleter(
TenantScopedEntityManager entityManager,
QueryFactory queryFactory,
DeleterFactory deleterFactory
TenantEntityManager entityManager,
QueryFactory queryFactory
) {
this.entityManager = entityManager;
this.queryFactory = queryFactory;
this.deleterFactory = deleterFactory;
}
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.data.NotificationEntity;
import gr.cite.notification.data.TenantScopedEntityManager;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.NotificationQuery;
import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,13 +27,13 @@ import java.util.stream.Collectors;
public class NotificationDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationDeleter.class));
private final TenantScopedEntityManager entityManager;
private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory;
@Autowired
public NotificationDeleter(
TenantScopedEntityManager entityManager,
TenantEntityManager entityManager,
QueryFactory queryFactory,
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.data.NotificationTemplateEntity;
import gr.cite.notification.data.TenantScopedEntityManager;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.NotificationTemplateQuery;
import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,13 +27,13 @@ import java.util.stream.Collectors;
public class NotificationTemplateDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationTemplateDeleter.class));
private final TenantScopedEntityManager entityManager;
private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory;
@Autowired
public NotificationTemplateDeleter(
TenantScopedEntityManager entityManager,
TenantEntityManager entityManager,
QueryFactory queryFactory,
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.data.TenantConfigurationEntity;
import gr.cite.notification.data.TenantScopedEntityManager;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.TenantConfigurationQuery;
import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory;
@ -27,13 +27,13 @@ import java.util.stream.Collectors;
public class TenantConfigurationDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationDeleter.class));
private final TenantScopedEntityManager entityManager;
private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory;
@Autowired
public TenantConfigurationDeleter(
TenantScopedEntityManager entityManager,
TenantEntityManager entityManager,
QueryFactory queryFactory,
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.data.TenantEntity;
import gr.cite.notification.data.TenantScopedEntityManager;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.query.TenantQuery;
import gr.cite.tools.data.deleter.Deleter;
import gr.cite.tools.data.deleter.DeleterFactory;
@ -26,13 +26,13 @@ import java.util.UUID;
public class TenantDeleter implements Deleter {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantDeleter.class));
private final TenantScopedEntityManager entityManager;
private final TenantEntityManager entityManager;
protected final QueryFactory queryFactory;
private final DeleterFactory deleterFactory;
@Autowired
public TenantDeleter(
TenantScopedEntityManager entityManager,
TenantEntityManager entityManager,
QueryFactory queryFactory,
DeleterFactory deleterFactory
) {

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