Package org.axonframework.eventsourcing

Source Code of org.axonframework.eventsourcing.EventSourcingRepository$ConflictResolvingListener

/*
* Copyright (c) 2010-2014. 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.eventsourcing;

import org.axonframework.common.Assert;
import org.axonframework.common.io.IOUtils;
import org.axonframework.domain.AggregateRoot;
import org.axonframework.domain.DomainEventMessage;
import org.axonframework.domain.DomainEventStream;
import org.axonframework.domain.EventMessage;
import org.axonframework.eventstore.EventStore;
import org.axonframework.eventstore.EventStreamNotFoundException;
import org.axonframework.repository.AggregateNotFoundException;
import org.axonframework.repository.LockManager;
import org.axonframework.repository.LockingRepository;
import org.axonframework.unitofwork.CurrentUnitOfWork;
import org.axonframework.unitofwork.UnitOfWork;
import org.axonframework.unitofwork.UnitOfWorkListenerAdapter;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
* Abstract repository implementation that allows easy implementation of an Event Sourcing mechanism. It will
* automatically publish new events to the given {@link org.axonframework.eventhandling.EventBus} and delegate event
* storage to the provided {@link org.axonframework.eventstore.EventStore}.
*
* @param <T> The type of aggregate this repository stores
* @author Allard Buijze
* @see EventSourcedAggregateRoot
* @see org.axonframework.eventsourcing.AbstractEventSourcedAggregateRoot
* @see org.axonframework.eventsourcing.annotation.AbstractAnnotatedAggregateRoot
* @see org.axonframework.eventstore.EventStore
* @since 0.1
*/
public class EventSourcingRepository<T extends EventSourcedAggregateRoot> extends LockingRepository<T> {

    private final EventStore eventStore;
    private ConflictResolver conflictResolver;
    private final Deque<EventStreamDecorator> eventStreamDecorators = new ArrayDeque<EventStreamDecorator>();
    private final AggregateFactory<T> aggregateFactory;

    /**
     * Initializes a repository with the default locking strategy, using a GenericAggregateFactory to create new
     * aggregate instances of given <code>aggregateType</code>.
     *
     * @param aggregateType The type of aggregate stored in this repository
     * @param eventStore    The event store that holds the event streams for this repository
     * @see org.axonframework.repository.LockingRepository#LockingRepository(Class)
     */
    public EventSourcingRepository(final Class<T> aggregateType, EventStore eventStore) {
        this(new GenericAggregateFactory<T>(aggregateType), eventStore);
    }

    /**
     * Initializes a repository with the default locking strategy, using the given <code>aggregateFactory</code> to
     * create new aggregate instances.
     *
     * @param aggregateFactory The factory for new aggregate instances
     * @param eventStore       The event store that holds the event streams for this repository
     * @see org.axonframework.repository.LockingRepository#LockingRepository(Class)
     */
    public EventSourcingRepository(final AggregateFactory<T> aggregateFactory, EventStore eventStore) {
        super(aggregateFactory.getAggregateType());
        Assert.notNull(eventStore, "eventStore may not be null");
        this.aggregateFactory = aggregateFactory;
        this.eventStore = eventStore;
    }

    /**
     * Initialize a repository with the given locking strategy.
     *
     * @param aggregateFactory The factory for new aggregate instances
     * @param eventStore       The event store that holds the event streams for this repository
     * @param lockManager      the locking strategy to apply to this repository
     */
    public EventSourcingRepository(AggregateFactory<T> aggregateFactory, EventStore eventStore,
                                   LockManager lockManager) {
        super(aggregateFactory.getAggregateType(), lockManager);
        Assert.notNull(eventStore, "eventStore may not be null");
        this.eventStore = eventStore;
        this.aggregateFactory = aggregateFactory;
    }

    /**
     * Initialize a repository with the given locking strategy, using a GenericAggregateFactory to create new aggregate
     * instances.
     *
     * @param aggregateType The type of aggregate to store in this repository
     * @param eventStore    The event store that holds the event streams for this repository
     * @param lockManager   the locking strategy to apply to this
     */
    public EventSourcingRepository(final Class<T> aggregateType, EventStore eventStore,
                                   final LockManager lockManager) {
        this(new GenericAggregateFactory<T>(aggregateType), eventStore, lockManager);
    }

    /**
     * Perform the actual saving of the aggregate. All necessary locks have been verified.
     *
     * @param aggregate the aggregate to store
     */
    @Override
    protected void doSaveWithLock(T aggregate) {
        DomainEventStream eventStream = aggregate.getUncommittedEvents();
        try {
            Iterator<EventStreamDecorator> iterator = eventStreamDecorators.descendingIterator();
            while (iterator.hasNext()) {
                eventStream = iterator.next().decorateForAppend(getTypeIdentifier(), aggregate, eventStream);
            }
            eventStore.appendEvents(getTypeIdentifier(), eventStream);
        } finally {
            IOUtils.closeQuietlyIfCloseable(eventStream);
        }
    }

    /**
     * Delegates to {@link #doSaveWithLock(EventSourcedAggregateRoot)}, as Event Sourcing generally doesn't delete
     * aggregates (not their events).
     * <p/>
     * This method may be safely overridden for special cases that do require deleting an Aggregate's Events.
     *
     * @param aggregate the aggregate to delete
     */
    @Override
    protected void doDeleteWithLock(T aggregate) {
        doSaveWithLock(aggregate);
    }

    /**
     * Perform the actual loading of an aggregate. The necessary locks have been obtained.
     *
     * @param aggregateIdentifier the identifier of the aggregate to load
     * @param expectedVersion     The expected version of the loaded aggregate
     * @return the fully initialized aggregate
     *
     * @throws AggregateDeletedException in case an aggregate existed in the past, but has been deleted
     * @throws AggregateNotFoundException when an aggregate with the given identifier does not exist
     */
    @Override
    protected T doLoad(Object aggregateIdentifier, final Long expectedVersion) {
        DomainEventStream events = null;
        DomainEventStream originalStream = null;
        try {
            try {
                events = eventStore.readEvents(getTypeIdentifier(), aggregateIdentifier);
            } catch (EventStreamNotFoundException e) {
                throw new AggregateNotFoundException(aggregateIdentifier, "The aggregate was not found", e);
            }
            originalStream = events;
            for (EventStreamDecorator decorator : eventStreamDecorators) {
                events = decorator.decorateForRead(getTypeIdentifier(), aggregateIdentifier, events);
            }

            final T aggregate = aggregateFactory.createAggregate(aggregateIdentifier, events.peek());
            List<DomainEventMessage> unseenEvents = new ArrayList<DomainEventMessage>();
            aggregate.initializeState(new CapturingEventStream(events, unseenEvents, expectedVersion));
            if (aggregate.isDeleted()) {
                throw new AggregateDeletedException(aggregateIdentifier);
            }
            CurrentUnitOfWork.get().registerListener(new ConflictResolvingListener(aggregate, unseenEvents));

            return aggregate;
        } finally {
            IOUtils.closeQuietlyIfCloseable(events);
            // if a decorator doesn't implement closeable, we still want to be sure we close the original stream
            IOUtils.closeQuietlyIfCloseable(originalStream);
        }
    }

    /**
     * Returns the factory used by this repository.
     *
     * @return the factory used by this repository
     */
    public AggregateFactory<T> getAggregateFactory() {
        return aggregateFactory;
    }

    /**
     * Resolve (potential) conflicts for the given <code>aggregate</code>, where given <code>unseenEvents</code> may
     * have been concurrently applied.
     *
     * @param aggregate    The aggregate containing the potential conflicts
     * @param unseenEvents The events that have been concurrently applied
     */
    protected void resolveConflicts(T aggregate, DomainEventStream unseenEvents) {
        CurrentUnitOfWork.get().registerListener(new ConflictResolvingListener(aggregate, asList(unseenEvents)));
    }

    private List<DomainEventMessage> asList(DomainEventStream domainEventStream) {
        List<DomainEventMessage> unseenEvents = new ArrayList<DomainEventMessage>();
        while (domainEventStream.hasNext()) {
            unseenEvents.add(domainEventStream.next());
        }
        return unseenEvents;
    }

    /**
     * Return the type identifier belonging to the AggregateFactory of this repository.
     *
     * @return the type identifier belonging to the AggregateFactory of this repository
     */
    public String getTypeIdentifier() {
        if (aggregateFactory == null) {
            throw new IllegalStateException("Either an aggregate factory must be configured (recommended), "
                                                    + "or the getTypeIdentifier() method must be overridden.");
        }
        return aggregateFactory.getTypeIdentifier();
    }

    /**
     * {@inheritDoc}
     * <p/>
     * This implementation will do nothing if a conflict resolver (See {@link #setConflictResolver(ConflictResolver)}
     * is
     * set. Otherwise, it will call <code>super.validateOnLoad(...)</code>.
     */
    @Override
    protected void validateOnLoad(T aggregate, Long expectedVersion) {
        if (conflictResolver == null) {
            super.validateOnLoad(aggregate, expectedVersion);
        }
    }

    /**
     * Sets the Event Stream Decorators that will process the event in the DomainEventStream when read, or written to
     * the event store.
     * <p/>
     * When appending events to the event store, the processors are invoked in the reverse order, causing the first
     * decorator in this list to receive each event first. When reading from events, the decorators are invoked in the
     * order given.
     *
     * @param eventProcessors The processors to that will process events in the DomainEventStream
     */
    public void setEventStreamDecorators(List<? extends EventStreamDecorator> eventProcessors) {
        this.eventStreamDecorators.addAll(eventProcessors);
    }

    /**
     * Sets the snapshotter trigger for this repository.
     *
     * @param snapshotterTrigger the snapshotter trigger for this repository.
     */
    public void setSnapshotterTrigger(SnapshotterTrigger snapshotterTrigger) {
        this.eventStreamDecorators.add(snapshotterTrigger);
    }

    /**
     * Sets the conflict resolver to use for this repository. If not set (or <code>null</code>), the repository will
     * throw an exception if any unexpected changes appear in loaded aggregates.
     *
     * @param conflictResolver The conflict resolver to use for this repository
     */
    public void setConflictResolver(ConflictResolver conflictResolver) {
        this.conflictResolver = conflictResolver;
    }

    private final class ConflictResolvingListener extends UnitOfWorkListenerAdapter {

        private final T aggregate;
        private final List<DomainEventMessage> unseenEvents;

        private ConflictResolvingListener(T aggregate, List<DomainEventMessage> unseenEvents) {
            this.aggregate = aggregate;
            this.unseenEvents = unseenEvents;
        }

        @Override
        public void onPrepareCommit(UnitOfWork unitOfWork, Set<AggregateRoot> aggregateRoots,
                                    List<EventMessage> events) {
            if (hasPotentialConflicts()) {
                conflictResolver.resolveConflicts(asList(aggregate.getUncommittedEvents()), unseenEvents);
            }
        }

        private boolean hasPotentialConflicts() {
            return aggregate.getUncommittedEventCount() > 0
                    && aggregate.getVersion() != null
                    && !unseenEvents.isEmpty();
        }
    }

    /**
     * Wrapper around a DomainEventStream that captures all passing events of which the sequence number is larger than
     * the expected version number.
     */
    private static final class CapturingEventStream implements DomainEventStream, Closeable {

        private final DomainEventStream eventStream;
        private final List<DomainEventMessage> unseenEvents;
        private final Long expectedVersion;

        private CapturingEventStream(DomainEventStream events, List<DomainEventMessage> unseenEvents,
                                     Long expectedVersion) {
            eventStream = events;
            this.unseenEvents = unseenEvents;
            this.expectedVersion = expectedVersion;
        }

        @Override
        public boolean hasNext() {
            return eventStream.hasNext();
        }

        @Override
        public DomainEventMessage next() {
            DomainEventMessage next = eventStream.next();
            if (expectedVersion != null && next.getSequenceNumber() > expectedVersion) {
                unseenEvents.add(next);
            }
            return next;
        }

        @Override
        public DomainEventMessage peek() {
            return eventStream.peek();
        }

        @Override
        public void close() throws IOException {
            IOUtils.closeQuietlyIfCloseable(eventStream);
        }
    }
}
TOP

Related Classes of org.axonframework.eventsourcing.EventSourcingRepository$ConflictResolvingListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.