@Override public Object resolveParameterValue(Message message) { if (!CurrentUnitOfWork.isStarted()) { return null; } return CurrentUnitOfWork.get(); }
/** * Check if the Unit of Work is the 'currently' active Unit of Work returned by {@link CurrentUnitOfWork#get()}. * * @return {@code true} if the Unit of Work is the currently active Unit of Work */ default boolean isCurrent() { return CurrentUnitOfWork.isStarted() && CurrentUnitOfWork.get() == this; }
/** * If a Unit of Work is started, execute the given {@code function} on it. Otherwise, returns an empty Optional. * Use this method when you wish to retrieve information from a Unit of Work, reverting to a default when no Unit * of Work is started. * * @param function The function to apply to the unit of work, if present * @param <T> The type of return value expected * @return an optional containing the result of the function, or an empty Optional when no Unit of Work was started * @throws NullPointerException when a Unit of Work is present and the function returns null */ public static <T> Optional<T> map(Function<UnitOfWork<?>, T> function) { return isStarted() ? Optional.of(function.apply(get())) : Optional.empty(); }
/** * If a UnitOfWork is started, invokes the given {@code consumer} with the active Unit of Work. Otherwise, * it does nothing * * @param consumer The consumer to invoke if a Unit of Work is active * @return {@code true} if a unit of work is active, {@code false} otherwise */ public static boolean ifStarted(Consumer<UnitOfWork<?>> consumer) { if (isStarted()) { consumer.accept(get()); return true; } return false; }
/** * Clears the UnitOfWork currently bound to the current thread, if that UnitOfWork is the given * {@code unitOfWork}. * * @param unitOfWork The UnitOfWork expected to be bound to the current thread. * @throws IllegalStateException when the given UnitOfWork was not the current active UnitOfWork. This exception * indicates a potentially wrong nesting of Units Of Work. */ public static void clear(UnitOfWork<?> unitOfWork) { if (!isStarted()) { throw new IllegalStateException("Could not clear this UnitOfWork. There is no UnitOfWork active."); } if (CURRENT.get().peek() == unitOfWork) { CURRENT.get().pop(); if (CURRENT.get().isEmpty()) { CURRENT.remove(); } } else { throw new IllegalStateException("Could not clear this UnitOfWork. It is not the active one."); } }
/** * Run a given {@code deadlineCall} immediately, or schedule it for the {@link UnitOfWork} it's 'prepare commit' * phase if a UnitOfWork is active. This is required as the DeadlineManager schedule message which we want to happen * on order with other message being handled. * * @param deadlineCall a {@link Runnable} to be executed now or on prepare commit if a {@link UnitOfWork} is active */ protected void runOnPrepareCommitOrNow(Runnable deadlineCall) { if (CurrentUnitOfWork.isStarted()) { CurrentUnitOfWork.get().onPrepareCommit(unitOfWork -> deadlineCall.run()); } else { deadlineCall.run(); } }
/** * Returns a list of all the events staged for publication in this Unit of Work. Changing this list will * not affect the publication of events. * * @return a list of all the events staged for publication */ protected List<EventMessage<?>> queuedMessages() { if (!CurrentUnitOfWork.isStarted()) { return Collections.emptyList(); } List<EventMessage<?>> messages = new ArrayList<>(); for (UnitOfWork<?> uow = CurrentUnitOfWork.get(); uow != null; uow = uow.parent().orElse(null)) { messages.addAll(0, uow.getOrDefaultResource(eventsKey, Collections.emptyList())); } return messages; }
/** * Initializes current unit of work with interceptor chain. * * @param interceptorChain the interceptor chain */ public static void initialize(InterceptorChain interceptorChain) { Assert.state(CurrentUnitOfWork.isStarted(), () -> "An active Unit of Work is required for injecting interceptor chain"); CurrentUnitOfWork.get().resources().put(INTERCEPTOR_CHAIN_EMITTER_KEY, interceptorChain); }
/** * Return {@code true} if the {@link CurrentUnitOfWork#isStarted()} returns {@code true} and in if the phase is * {@link UnitOfWork.Phase#STARTED}, otherwise {@code false}. * * @return {@code true} if the {@link CurrentUnitOfWork#isStarted()} returns {@code true} and in if the phase is * {@link UnitOfWork.Phase#STARTED}, otherwise {@code false}. */ private boolean inStartedPhaseOfUnitOfWork() { return CurrentUnitOfWork.isStarted() && UnitOfWork.Phase.STARTED.equals(CurrentUnitOfWork.get().phase()); }
/** * Initialize conflict resolution in the context of the current Unit of Work dealing with a command on an event * sourced aggregate. * * @param conflictResolver conflict resolver able to detect conflicts */ public static void initialize(ConflictResolver conflictResolver) { Assert.state(CurrentUnitOfWork.isStarted(), () -> "An active Unit of Work is required for conflict resolution"); CurrentUnitOfWork.get().getOrComputeResource(CONFLICT_RESOLUTION_KEY, key -> conflictResolver); }
@Override public void storeToken(TrackingToken token, String processorName, int segment) { if (CurrentUnitOfWork.isStarted()) { CurrentUnitOfWork.get().afterCommit(uow -> tokens.put(new ProcessAndSegment(processorName, segment), getOrDefault(token, NULL_TOKEN))); } else { tokens.put(new ProcessAndSegment(processorName, segment), getOrDefault(token, NULL_TOKEN)); } }
@Override public void handle(List<? extends EventMessage<?>> events, Consumer<List<? extends EventMessage<?>>> processor) { if (CurrentUnitOfWork.isStarted()) { UnitOfWork<?> unitOfWorkRoot = CurrentUnitOfWork.get().root(); unitOfWorkRoot.getOrComputeResource(scheduledEventsKey, key -> { List<EventMessage<?>> allEvents = new ArrayList<>(); unitOfWorkRoot.afterCommit(uow -> schedule(allEvents, processor)); return allEvents; }).addAll(events); } else { schedule(events, processor); } }
@Override public void eventHandled(EventMessage<?> msg) { if (++counter >= threshold && msg instanceof DomainEventMessage) { if (CurrentUnitOfWork.isStarted()) { CurrentUnitOfWork.get().onPrepareCommit( u -> scheduleSnapshot((DomainEventMessage) msg)); } else { scheduleSnapshot((DomainEventMessage) msg); } counter = 0; } }
@After public void tearDown() { scheduledThreadPool.shutdownNow(); while (CurrentUnitOfWork.isStarted()) { CurrentUnitOfWork.get().rollback(); } }
@Override public void publish(List<? extends EventMessage<?>> events) { Stream<MessageMonitor.MonitorCallback> ingested = events.stream().map(messageMonitor::onMessageIngested); if (CurrentUnitOfWork.isStarted()) { UnitOfWork<?> unitOfWork = CurrentUnitOfWork.get(); Assert.state(!unitOfWork.phase().isAfter(PREPARE_COMMIT), () -> "It is not allowed to publish events when the current Unit of Work has already been " + "committed. Please start a new Unit of Work before publishing events."); Assert.state(!unitOfWork.root().phase().isAfter(PREPARE_COMMIT), () -> "It is not allowed to publish events when the root Unit of Work has already been " + "committed."); unitOfWork.afterCommit(u -> ingested.forEach(MessageMonitor.MonitorCallback::reportSuccess)); unitOfWork.onRollback(uow -> ingested.forEach( message -> message.reportFailure(uow.getExecutionResult().getExceptionResult()) )); eventsQueue(unitOfWork).addAll(events); } else { try { prepareCommit(intercept(events)); commit(events); afterCommit(events); ingested.forEach(MessageMonitor.MonitorCallback::reportSuccess); } catch (Exception e) { ingested.forEach(m -> m.reportFailure(e)); throw e; } } }
@Override public void onResult(CommandMessage<? extends C> commandMessage, CommandResultMessage<? extends R> commandResultMessage) { if (commandResultMessage.isExceptional()) { Throwable cause = commandResultMessage.exceptionResult(); history.add(simplify(cause)); try { // We fail immediately when the exception is checked, // or when it is a Deadlock Exception and we have an active unit of work. if (!(cause instanceof RuntimeException) || (isCausedBy(cause, DeadlockException.class) && CurrentUnitOfWork.isStarted()) || !retryScheduler.scheduleRetry(commandMessage, (RuntimeException) cause, new ArrayList<>(history), new RetryDispatch(commandMessage))) { delegate.onResult(commandMessage, commandResultMessage); } } catch (Exception e) { delegate.onResult(commandMessage, asCommandResultMessage(e)); } } else { delegate.onResult(commandMessage, commandResultMessage); } }
@After public void tearDown() { eventProcessor.shutDown(); while (CurrentUnitOfWork.isStarted()) { CurrentUnitOfWork.get().rollback(); } }
@Override public Connection getConnection() throws SQLException { if (!CurrentUnitOfWork.isStarted() || CurrentUnitOfWork.get().phase().isAfter(UnitOfWork.Phase.PREPARE_COMMIT)) { return delegate.getConnection();
@Override protected void appendEvents(List<? extends EventMessage<?>> events, Serializer serializer) { AppendEventTransaction sender; if (CurrentUnitOfWork.isStarted()) { sender = CurrentUnitOfWork.get().root().getOrComputeResource(APPEND_EVENT_TRANSACTION, k -> { AppendEventTransaction appendEventTransaction = eventStoreClient.createAppendEventConnection(); CurrentUnitOfWork.get().root().onRollback( u -> appendEventTransaction.rollback(u.getExecutionResult().getExceptionResult()) ); CurrentUnitOfWork.get().root().onCommit(u -> commit(appendEventTransaction)); return appendEventTransaction; }); } else { sender = eventStoreClient.createAppendEventConnection(); } for (EventMessage<?> eventMessage : events) { sender.append(map(eventMessage, serializer)); } if (!CurrentUnitOfWork.isStarted()) { commit(sender); } }
/** * Initializes current unit of work with interceptor chain. * * @param interceptorChain the interceptor chain */ public static void initialize(InterceptorChain interceptorChain) { Assert.state(CurrentUnitOfWork.isStarted(), () -> "An active Unit of Work is required for injecting interceptor chain"); CurrentUnitOfWork.get().resources().put(INTERCEPTOR_CHAIN_EMITTER_KEY, interceptorChain); }