/* * Copyright (c) 2010-2016. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.axonframework.commandhandling.disruptor; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.LifecycleAware; import org.axonframework.commandhandling.model.Aggregate; import org.axonframework.commandhandling.model.AggregateNotFoundException; import org.axonframework.commandhandling.model.ConflictingAggregateVersionException; import org.axonframework.commandhandling.model.Repository; import org.axonframework.commandhandling.model.inspection.AggregateModel; import org.axonframework.commandhandling.model.inspection.ModelInspector; import org.axonframework.common.Assert; import org.axonframework.common.caching.Cache; import org.axonframework.eventsourcing.AggregateFactory; import org.axonframework.eventsourcing.EventSourcedAggregate; import org.axonframework.eventsourcing.SnapshotTrigger; import org.axonframework.eventsourcing.SnapshotTriggerDefinition; import org.axonframework.eventsourcing.eventstore.DomainEventStream; import org.axonframework.eventsourcing.eventstore.EventStore; import org.axonframework.messaging.annotation.ParameterResolverFactory; import org.axonframework.messaging.unitofwork.CurrentUnitOfWork; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; /** * Component of the DisruptorCommandBus that invokes the command handler. The execution is done within a Unit Of Work. * If an aggregate has been pre-loaded, it is set to the ThreadLocal. * * @author Allard Buijze * @since 2.0 */ public class CommandHandlerInvoker implements EventHandler<CommandHandlingEntry>, LifecycleAware { private static final Logger logger = LoggerFactory.getLogger(CommandHandlerInvoker.class); private static final ThreadLocal<CommandHandlerInvoker> CURRENT_INVOKER = new ThreadLocal<>(); private static final Object PLACEHOLDER_VALUE = new Object(); private final Map<Class<?>, DisruptorRepository> repositories = new ConcurrentHashMap<>(); private final Cache cache; private final int segmentId; private final EventStore eventStore; /** * Create an aggregate invoker instance that uses the given {@code eventStore} and {@code cache} to * retrieve aggregate instances. * * @param eventStore The event store providing access to events to reconstruct aggregates * @param cache The cache temporarily storing aggregate instances * @param segmentId The id of the segment this invoker should handle */ public CommandHandlerInvoker(EventStore eventStore, Cache cache, int segmentId) { this.eventStore = eventStore; this.cache = cache; this.segmentId = segmentId; } /** * Returns the Repository instance for Aggregate with given {@code typeIdentifier} used by the * CommandHandlerInvoker that is running on the current thread. * <p> * Calling this method from any other thread will return {@code null}. * * @param type The type of aggregate * @param <T> The type of aggregate * @return the repository instance for aggregate of given type */ @SuppressWarnings("unchecked") public static <T> DisruptorRepository<T> getRepository(Class<?> type) { final CommandHandlerInvoker invoker = CURRENT_INVOKER.get(); Assert.state(invoker != null, () -> "The repositories of a DisruptorCommandBus are only available " + "in the invoker thread"); return invoker.repositories.get(type); } @Override public void onEvent(CommandHandlingEntry entry, long sequence, boolean endOfBatch) throws Exception { if (entry.isRecoverEntry()) { removeEntry(entry.getAggregateIdentifier()); } else if (entry.getInvokerId() == segmentId) { entry.start(); try { Object result = entry.getInvocationInterceptorChain().proceed(); entry.setResult(result); } catch (Exception throwable) { entry.setExceptionResult(throwable); } finally { entry.pause(); } } } /** * Create a repository instance for an aggregate created by the given {@code aggregateFactory}. The returning * repository must be sage to use by this invoker instance. * * @param <T> The type of aggregate created by the factory * @param aggregateFactory The factory creating aggregate instances * @param snapshotTriggerDefinition The trigger definition for snapshots * @param parameterResolverFactory The factory used to resolve parameters on command handler methods * @return A Repository instance for the given aggregate */ @SuppressWarnings("unchecked") public <T> Repository<T> createRepository(AggregateFactory<T> aggregateFactory, SnapshotTriggerDefinition snapshotTriggerDefinition, ParameterResolverFactory parameterResolverFactory) { return repositories.computeIfAbsent(aggregateFactory.getAggregateType(), k -> new DisruptorRepository<>(aggregateFactory, cache, eventStore, parameterResolverFactory, snapshotTriggerDefinition)); } private void removeEntry(String aggregateIdentifier) { for (DisruptorRepository repository : repositories.values()) { repository.removeFromCache(aggregateIdentifier); } cache.remove(aggregateIdentifier); } @Override public void onStart() { CURRENT_INVOKER.set(this); } @Override public void onShutdown() { CURRENT_INVOKER.remove(); } /** * Repository implementation that is safe to use by a single CommandHandlerInvoker instance. * * @param <T> The type of aggregate stored in this repository */ static final class DisruptorRepository<T> implements Repository<T> { private final EventStore eventStore; private final SnapshotTriggerDefinition snapshotTriggerDefinition; private final AggregateFactory<T> aggregateFactory; private final Map<EventSourcedAggregate<T>, Object> firstLevelCache = new WeakHashMap<>(); private final Cache cache; private final AggregateModel<T> model; private DisruptorRepository(AggregateFactory<T> aggregateFactory, Cache cache, EventStore eventStore, ParameterResolverFactory parameterResolverFactory, SnapshotTriggerDefinition snapshotTriggerDefinition) { this.aggregateFactory = aggregateFactory; this.cache = cache; this.eventStore = eventStore; this.snapshotTriggerDefinition = snapshotTriggerDefinition; this.model = ModelInspector.inspectAggregate(aggregateFactory.getAggregateType(), parameterResolverFactory); } @Override public Aggregate<T> load(String aggregateIdentifier, Long expectedVersion) { ((CommandHandlingEntry) CurrentUnitOfWork.get()).registerAggregateIdentifier(aggregateIdentifier); Aggregate<T> aggregate = load(aggregateIdentifier); if (expectedVersion != null && aggregate.version() > expectedVersion) { throw new ConflictingAggregateVersionException(aggregateIdentifier, expectedVersion, aggregate.version()); } return aggregate; } @SuppressWarnings("unchecked") @Override public Aggregate<T> load(String aggregateIdentifier) { ((CommandHandlingEntry) CurrentUnitOfWork.get()).registerAggregateIdentifier(aggregateIdentifier); EventSourcedAggregate<T> aggregateRoot = null; for (EventSourcedAggregate<T> cachedAggregate : firstLevelCache.keySet()) { if (aggregateIdentifier.equals(cachedAggregate.identifierAsString())) { logger.debug("Aggregate {} found in first level cache", aggregateIdentifier); aggregateRoot = cachedAggregate; } } if (aggregateRoot == null) { Object cachedItem = cache.get(aggregateIdentifier); if (cachedItem != null && EventSourcedAggregate.class.isInstance(cachedItem)) { EventSourcedAggregate<T> cachedAggregate = (EventSourcedAggregate<T>) cachedItem; aggregateRoot = cachedAggregate.invoke(r -> { if (aggregateFactory.getAggregateType().isInstance(r)) { return cachedAggregate; } else { return null; } }); } } if (aggregateRoot == null) { logger.debug("Aggregate {} not in first level cache, loading fresh one from Event Store", aggregateIdentifier); DomainEventStream eventStream = eventStore.readEvents(aggregateIdentifier); SnapshotTrigger trigger = snapshotTriggerDefinition.prepareTrigger(aggregateFactory.getAggregateType()); if (!eventStream.hasNext()) { throw new AggregateNotFoundException(aggregateIdentifier, "The aggregate was not found in the event store"); } aggregateRoot = EventSourcedAggregate .initialize(aggregateFactory.createAggregateRoot(aggregateIdentifier, eventStream.peek()), model, eventStore, trigger); aggregateRoot.initializeState(eventStream); firstLevelCache.put(aggregateRoot, PLACEHOLDER_VALUE); cache.put(aggregateIdentifier, aggregateRoot); } return aggregateRoot; } @Override public Aggregate<T> newInstance(Callable<T> factoryMethod) throws Exception { SnapshotTrigger trigger = snapshotTriggerDefinition.prepareTrigger(aggregateFactory.getAggregateType()); EventSourcedAggregate<T> aggregate = EventSourcedAggregate.initialize(factoryMethod, model, eventStore, trigger); firstLevelCache.put(aggregate, PLACEHOLDER_VALUE); cache.put(aggregate.identifierAsString(), aggregate); return aggregate; } private void removeFromCache(String aggregateIdentifier) { for (EventSourcedAggregate<T> cachedAggregate : firstLevelCache.keySet()) { if (aggregateIdentifier.equals(cachedAggregate.identifierAsString())) { firstLevelCache.remove(cachedAggregate); logger.debug("Aggregate {} removed from first level cache for recovery purposes.", aggregateIdentifier); return; } } } } }