/** * Copyright (c) 2016, All Contributors (see CONTRIBUTORS file) * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.eventsourcing; import com.eventsourcing.events.CommandTerminatedExceptionally; import com.eventsourcing.events.EventCausalityEstablished; import com.eventsourcing.events.JavaExceptionOccurred; import com.googlecode.cqengine.resultset.ResultSet; import static com.eventsourcing.queries.QueryFactory.*; /** * Command is a request for changes in the domain. Unlike an event, * it is not a statement of fact as it might be rejected. * <p> * For example, ConfirmOrder command may or may not result in an * OrderConfirmed event being produced. * * @param <S> state type * @param <R> result type */ public interface Command<S, R> extends Entity<Command<S, R>> { /** * Returns a stream of events that should be recorded. By default, an empty stream returned. * <p> * This version of the function receives a {@link LockProvider} if one is needed. {@link Repository} * will pass a special "tracking" provider that will release the locks in two situations: * <p> * <ul> * <li>{@link #events(Repository, LockProvider)} threw an exception</li> * <li>{@link #result(Object, Repository, LockProvider)} did not release any locks</li> * </ul> * * @param repository Configured repository * @param lockProvider Lock provider * @return stream of events * @throws Exception if the command is to be rejected, an exception has to be thrown. In this case, no events will * be recorded */ default EventStream<S> events(Repository repository, LockProvider lockProvider) throws Exception { return EventStream.empty(); } /** * Once all events are recorded, this callback will be invoked * <p> * By default, it does nothing and it is meant to be overridden when necessary. For example, * if upon the successful recording of events an email has to be sent, this is the place * to do it. * * @return Result */ default R result(S state, Repository repository, LockProvider lockProvider) { return null; } /** * Figure out if the command has terminated exceptionally by testing the presence of an associated * {@link CommandTerminatedExceptionally} event. * @param repository * @return <code>true</code> if the command has terminated exceptionally */ default boolean hasTerminatedExceptionally(Repository repository) { try (ResultSet<EntityHandle<CommandTerminatedExceptionally>> resultSet = repository .query(CommandTerminatedExceptionally.class, and(all(CommandTerminatedExceptionally.class), existsIn( repository.getIndexEngine().getIndexedCollection(EventCausalityEstablished.class), CommandTerminatedExceptionally.ID, EventCausalityEstablished.EVENT)))) { return resultSet.isNotEmpty(); } } /** * Figure out the cause of command termination by searching for {@link JavaExceptionOccurred} events, associated * with the commend. * @param repository * @return an instance of {@link JavaExceptionOccurred} or <code>null</code> if none found */ default JavaExceptionOccurred exceptionalTerminationCause(Repository repository) { try (ResultSet<EntityHandle<JavaExceptionOccurred>> resultSet = repository .query(JavaExceptionOccurred.class, and(all(JavaExceptionOccurred.class), existsIn( repository.getIndexEngine().getIndexedCollection(EventCausalityEstablished.class), JavaExceptionOccurred.ID, EventCausalityEstablished.EVENT)))) { if (resultSet.isEmpty()) { return null; } return resultSet.uniqueResult().get(); } } }