argos/annotation-service/annotation/src/main/java/gr/cite/annotation/integrationevent/inbox/InboxRepositoryImpl.java

370 lines
17 KiB
Java

package gr.cite.annotation.integrationevent.inbox;
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.integrationevent.TrackedEvent;
import gr.cite.annotation.integrationevent.inbox.annotationentitiesremoval.AnnotationEntitiesRemovalIntegrationEventHandler;
import gr.cite.annotation.integrationevent.inbox.annotationentitiestouch.AnnotationEntitiesTouchedIntegrationEventHandler;
import gr.cite.annotation.integrationevent.inbox.tenantremoval.TenantRemovalIntegrationEventHandler;
import gr.cite.annotation.integrationevent.inbox.tenanttouch.TenantTouchedIntegrationEventHandler;
import gr.cite.annotation.integrationevent.inbox.userremoval.UserRemovalIntegrationEventHandler;
import gr.cite.annotation.integrationevent.inbox.usertouch.UserTouchedIntegrationEventHandler;
import gr.cite.annotation.query.QueueInboxQuery;
import gr.cite.queueinbox.entity.QueueInbox;
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.consumer.InboxCreatorParams;
import gr.cite.tools.data.query.Ordering;
import gr.cite.tools.data.query.QueryFactory;
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.context.ApplicationContext;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
public class InboxRepositoryImpl implements InboxRepository {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InboxRepositoryImpl.class));
protected final ApplicationContext applicationContext;
private final JsonHandlingService jsonHandlingService;
private final InboxProperties inboxProperties;
public InboxRepositoryImpl(
ApplicationContext applicationContext,
InboxProperties inboxProperties
) {
this.applicationContext = applicationContext;
this.jsonHandlingService = this.applicationContext.getBean(JsonHandlingService.class);
this.inboxProperties = inboxProperties;
}
@Override
public CandidateInfo candidate(Instant lastCandidateCreationTimestamp, MessageOptions options) {
EntityTransaction transaction = null;
EntityManager entityManager = null;
CandidateInfo candidate = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
QueueInboxEntity item = queryFactory.query(QueueInboxQuery.class)
.isActives(IsActive.Active)
.status(QueueInboxStatus.PENDING, QueueInboxStatus.ERROR)
.retryThreshold(options.getRetryThreashold())
.createdAfter(lastCandidateCreationTimestamp)
.ordering(new Ordering().addAscending(QueueInboxEntity._createdAt))
.first();
if (item != null) {
QueueInboxStatus prevState = item.getStatus();
item.setStatus(QueueInboxStatus.PROCESSING);
entityManager.merge(item);
entityManager.flush();
candidate = new CandidateInfo();
candidate.setId(item.getId());
candidate.setCreatedAt(item.getCreatedAt());
candidate.setPreviousState(prevState);
}
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 inbox. Skipping: {} ", ex.getMessage());
if (transaction != null)
transaction.rollback();
candidate = null;
} catch (Exception ex) {
logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex);
if (transaction != null)
transaction.rollback();
candidate = null;
} finally {
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) {
logger.error("Problem getting list of queue inbox. Skipping: {}", ex.getMessage(), ex);
}
return candidate;
}
@Override
public Boolean shouldOmit(CandidateInfo candidate, Function<QueueInbox, Boolean> shouldOmit) {
EntityTransaction transaction = null;
EntityManager entityManager = null;
boolean success = false;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
QueueInboxEntity item = queryFactory.query(QueueInboxQuery.class).ids(candidate.getId()).first();
if (item == null) {
logger.warn("Could not lookup queue inbox {} to process. Continuing...", candidate.getId());
} else {
if (shouldOmit.apply(item)) {
item.setStatus(QueueInboxStatus.OMITTED);
entityManager.merge(item);
entityManager.flush();
success = true;
}
}
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;
} finally {
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
}
return success;
}
@Override
public boolean shouldWait(CandidateInfo candidate, Function<QueueInbox, Boolean> itIsTimeFunc) {
EntityTransaction transaction = null;
EntityManager entityManager = null;
boolean success = false;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
QueueInboxEntity item = queryFactory.query(QueueInboxQuery.class).ids(candidate.getId()).first();
if (item.getRetryCount() != null && item.getRetryCount() >= 1) {
Boolean itIsTime = itIsTimeFunc.apply(item);
if (!itIsTime) {
item.setStatus(candidate.getPreviousState());
entityManager.merge(item);
entityManager.flush();
success = true;
}
success = !itIsTime;
}
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;
} finally {
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
}
return success;
}
@Override
public QueueInbox create(InboxCreatorParams inboxCreatorParams) {
EntityTransaction transaction = null;
EntityManager entityManager = null;
boolean success = false;
QueueInboxEntity queueMessage = null;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
queueMessage = this.createQueueInboxEntity(inboxCreatorParams);
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
entityManager.persist(queueMessage);
entityManager.flush();
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;
} finally {
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
}
return queueMessage;
}
private QueueInboxEntity createQueueInboxEntity(InboxCreatorParams inboxCreatorParams) {
QueueInboxEntity queueMessage = new QueueInboxEntity();
queueMessage.setId(UUID.randomUUID());
queueMessage.setTenantId(null);
queueMessage.setExchange(this.inboxProperties.getExchange());
queueMessage.setRoute(inboxCreatorParams.getRoutingKey());
queueMessage.setQueue(inboxCreatorParams.getQueueName());
queueMessage.setApplicationId(inboxCreatorParams.getAppId());
queueMessage.setMessageId(UUID.fromString(inboxCreatorParams.getMessageId()));
queueMessage.setMessage(inboxCreatorParams.getMessageBody());
queueMessage.setIsActive(IsActive.Active);
queueMessage.setStatus(QueueInboxStatus.PENDING);
queueMessage.setRetryCount(0);
queueMessage.setCreatedAt(Instant.now());
return queueMessage;
}
@Override
public Boolean emit(CandidateInfo candidateInfo) {
EntityTransaction transaction = null;
EntityManager entityManager = null;
boolean success = false;
try (FakeRequestScope ignored = new FakeRequestScope()) {
try {
EntityManagerFactory entityManagerFactory = this.applicationContext.getBean(EntityManagerFactory.class);
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
QueryFactory queryFactory = this.applicationContext.getBean(QueryFactory.class);
QueueInboxEntity queueInboxMessage = queryFactory.query(QueueInboxQuery.class).ids(candidateInfo.getId()).first();
if (queueInboxMessage == null) {
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());
switch (status) {
case Success: {
queueInboxMessage.setStatus(QueueInboxStatus.SUCCESSFUL);
break;
}
case Postponed: {
queueInboxMessage.setStatus(QueueInboxStatus.PARKED);
break;
}
case Error: {
queueInboxMessage.setStatus(QueueInboxStatus.ERROR);
queueInboxMessage.setRetryCount(queueInboxMessage.getRetryCount() != null ? queueInboxMessage.getRetryCount() + 1 : 0);
break;
}
case Discard:
default: {
queueInboxMessage.setStatus(QueueInboxStatus.DISCARD);
break;
}
}
success = status == EventProcessingStatus.Success;
entityManager.merge(queueInboxMessage);
entityManager.flush();
}
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;
} finally {
if (entityManager != null)
entityManager.close();
}
} catch (Exception ex) {
logger.error("Problem executing purge. rolling back any db changes and marking error. Continuing...", ex);
}
return success;
}
private EventProcessingStatus processMessage(String routingKey, String messageId, String appId, String message) {
IntegrationEventHandler handler;
logger.debug("Processing message with routing key '{}'", routingKey);
if (this.routingKeyMatched(routingKey, this.inboxProperties.getTenantRemovalTopic()))
handler = this.applicationContext.getBean(TenantRemovalIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getTenantTouchTopic()))
handler = this.applicationContext.getBean(TenantTouchedIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserRemovalTopic()))
handler = this.applicationContext.getBean(UserRemovalIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getUserTouchTopic()))
handler = this.applicationContext.getBean(UserTouchedIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getAnnotationEntitiesTouchTopic()))
handler = this.applicationContext.getBean(AnnotationEntitiesTouchedIntegrationEventHandler.class);
else if (this.routingKeyMatched(routingKey, this.inboxProperties.getAnnotationEntitiesRemovalTopic()))
handler = this.applicationContext.getBean(AnnotationEntitiesRemovalIntegrationEventHandler.class);
else {
logger.error("No handler found for message routing key '{}'. Discarding.", routingKey);
handler = null;
}
if (handler == null)
return EventProcessingStatus.Discard;
IntegrationEventProperties properties = new IntegrationEventProperties();
properties.setAppId(appId);
properties.setMessageId(messageId);
TrackedEvent event = this.jsonHandlingService.fromJsonSafe(TrackedEvent.class, message);
// using (LogContext.PushProperty(this._logTrackingConfig.LogTrackingContextName, @event.TrackingContextTag))
// {
try {
return handler.handle(properties, message);
} catch (Exception ex) {
logger.error("problem handling event from routing key " + routingKey + ". Setting nack and continuing...", ex);
return EventProcessingStatus.Error;
}
// }
}
private Boolean routingKeyMatched(String routingKey, List<String> topics) {
if (topics == null || topics.isEmpty())
return false;
return topics.stream().anyMatch(x -> x.equals(routingKey));
}
}