On our projects we have started using the saga functionality which
meets our requirements perfectly. However we have come across some
issues when using the AnnotatedSagaManager with the JpaSagaRepository.
We have 3 sagas that handle the same event and are all associated with
the same value.
1) The first problem is this exception
java.lang.ClassCastException
at java.lang.Class.cast(Class.java:2990)
at
org.axonframework.saga.repository.jpa.JpaSagaRepository.loadSaga(JpaSagaRepository.java:
91)
at
org.axonframework.saga.repository.AbstractSagaRepository.load(AbstractSagaRepository.java:
65)
at
org.axonframework.saga.repository.AbstractSagaRepository.find(AbstractSagaRepository.java:
53)
at
org.axonframework.saga.annotation.AnnotatedSagaManager.findSagas(AnnotatedSagaManager.java:
116)
at
org.axonframework.saga.annotation.AnnotatedSagaManager.findSagas(AnnotatedSagaManager.java:
104)
at org.axonframework.saga.AbstractSagaManager
$SagaLookupAndInvocationTask.run(AbstractSagaManager.java:227)
at org.axonframework.saga.AsynchronousSagaExecutor
$Task.execute(AsynchronousSagaExecutor.java:94)
at
org.axonframework.saga.AsynchronousSagaExecutor.doHandle(AsynchronousSagaExecutor.java:
61)
at
org.axonframework.saga.AsynchronousSagaExecutor.doHandle(AsynchronousSagaExecutor.java:
32)
at org.axonframework.eventhandling.AsynchronousExecutionWrapper
$1.doHandle(AsynchronousExecutionWrapper.java:149)
at
org.axonframework.eventhandling.EventProcessingScheduler.handleEventBatch(EventProcessingScheduler.java:
317)
at
org.axonframework.eventhandling.EventProcessingScheduler.processOrRetryBatch(EventProcessingScheduler.java:
239)
at
org.axonframework.eventhandling.EventProcessingScheduler.run(EventProcessingScheduler.java:
200)
at java.util.concurrent.ThreadPoolExecutor
$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor
$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
When the AnnotatedSagaManager loops through the managed saga it
attempts to load a saga as the wrong type. This seems to be because
the associatedValue lookup matches more than one saga
2) The second problem is that the AbstractSagaRepository#find method
can return null sagas in the returned set. The following exception is
then thrown trying the execute the null saga
java.lang.NullPointerException
at org.axonframework.saga.AbstractSagaManager
$SagaLookupAndInvocationTask.run(AbstractSagaManager.java:229)
at org.axonframework.saga.AsynchronousSagaExecutor
$Task.execute(AsynchronousSagaExecutor.java:94)
at
org.axonframework.saga.AsynchronousSagaExecutor.doHandle(AsynchronousSagaExecutor.java:
61)
at
org.axonframework.saga.AsynchronousSagaExecutor.doHandle(AsynchronousSagaExecutor.java:
32)
at org.axonframework.eventhandling.AsynchronousExecutionWrapper
$1.doHandle(AsynchronousExecutionWrapper.java:149)
at
org.axonframework.eventhandling.EventProcessingScheduler.handleEventBatch(EventProcessingScheduler.java:
317)
at
org.axonframework.eventhandling.EventProcessingScheduler.processOrRetryBatch(EventProcessingScheduler.java:
239)
at
org.axonframework.eventhandling.EventProcessingScheduler.run(EventProcessingScheduler.java:
200)
at java.util.concurrent.ThreadPoolExecutor
$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor
$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)
Fix:
To fix the problem temporarily we have created a subclass of the
JpaSagaRepository that handles the ClassCastException and does not add
null values to the set of saga. Here is the code
@Override
protected <T extends Saga> T loadSaga(Class<T> type, String
sagaId) {
try {
return super.loadSaga(type, sagaId);
} catch (ClassCastException cce) {
return null;
}
}
@Override
public <T extends Saga> Set<T> find(Class<T> type,
Set<AssociationValue> associationValues) {
Set<String> sagaIdentifiers = new HashSet<String>();
Set<T> result = new HashSet<T>();
for (AssociationValue associationValue : associationValues) {
Set<String> identifiers =
getAssociationValueMap().findSagas(associationValue);
if (identifiers != null) {
sagaIdentifiers.addAll(identifiers);
}
}
if (!sagaIdentifiers.isEmpty()) {
for (String sagaId : sagaIdentifiers) {
T cachedSaga = load(type, sagaId);
if(cachedSaga != null) {
result.add(cachedSaga);
}
}
}
return result;
}