org.axonframework.eventstore.mongo.DocumentPerEventStorageStrategy.java Source code

Java tutorial

Introduction

Here is the source code for org.axonframework.eventstore.mongo.DocumentPerEventStorageStrategy.java

Source

/*
 * 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.eventstore.mongo;

import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import org.axonframework.domain.DomainEventMessage;
import org.axonframework.eventstore.mongo.criteria.MongoCriteria;
import org.axonframework.serializer.SerializedDomainEventData;
import org.axonframework.serializer.SerializedMetaData;
import org.axonframework.serializer.SerializedObject;
import org.axonframework.serializer.Serializer;
import org.axonframework.serializer.SimpleSerializedObject;
import org.axonframework.upcasting.UpcasterChain;
import org.joda.time.DateTime;

import java.util.List;

import static org.axonframework.serializer.MessageSerializer.serializeMetaData;
import static org.axonframework.serializer.MessageSerializer.serializePayload;
import static org.axonframework.upcasting.UpcastUtils.upcastAndDeserialize;

/**
 * Implementation of the StorageStrategy that stores each event as a separate document. This makes it easier to query
 * the event store for specific events, but does not allow for atomic storage of a single commit.
 * <p/>
 * The structure is as follows:
 * <ul>
 * <li>aggregateIdentifier => [aggregateIdentifier]</li>
 * <li>sequenceNumber => [sequenceNumber of first event]</li>
 * <li>timestamp => [timestamp of first event]</li>
 * <li>type => [aggregate type]</li>
 * <li>serializedPayload => [payload of the event]</li>
 * <li>payloadType => [type of the payload]</li>
 * <li>payloadRevision => [revision of the payload]</li>
 * <li>serializedMetaData => [meta data of the event]</li>
 * <li>eventIdentifier => [identifier of the event]</li>
 * </ul>
 * <p/>
 * <em>Note: the SerializedType of Message Meta Data is not stored. Upon retrieval, it is set to the default value
 * (name = "org.axonframework.domain.MetaData", revision = null). See {@link org.axonframework.serializer.SerializedMetaData#isSerializedMetaData(org.axonframework.serializer.SerializedObject)}</em>
 *
 * @author Allard Buijze
 * @since 2.0
 */
public class DocumentPerEventStorageStrategy implements StorageStrategy {

    private static final int ORDER_ASC = 1;
    private static final int ORDER_DESC = -1;

    @Override
    public DBObject[] createDocuments(String type, Serializer eventSerializer, List<DomainEventMessage> messages) {
        DBObject[] dbObjects = new DBObject[messages.size()];
        for (int i = 0, messagesSize = messages.size(); i < messagesSize; i++) {
            DomainEventMessage message = messages.get(i);
            dbObjects[i] = new EventEntry(type, message, eventSerializer).asDBObject();
        }
        return dbObjects;
    }

    @Override
    public DBCursor findEvents(DBCollection collection, String aggregateType, String aggregateIdentifier,
            long firstSequenceNumber) {
        return collection.find(EventEntry.forAggregate(aggregateType, aggregateIdentifier, firstSequenceNumber))
                .sort(new BasicDBObject(EventEntry.SEQUENCE_NUMBER_PROPERTY, ORDER_ASC));
    }

    @Override
    public List<DomainEventMessage> extractEventMessages(DBObject entry, Object aggregateIdentifier,
            Serializer serializer, UpcasterChain upcasterChain, boolean skipUnknownTypes) {
        return new EventEntry(entry).getDomainEvents(aggregateIdentifier, serializer, upcasterChain,
                skipUnknownTypes);
    }

    @Override
    public void ensureIndexes(DBCollection eventsCollection, DBCollection snapshotsCollection) {
        eventsCollection.ensureIndex(new BasicDBObject(EventEntry.AGGREGATE_IDENTIFIER_PROPERTY, 1)
                .append(EventEntry.AGGREGATE_TYPE_PROPERTY, 1).append(EventEntry.SEQUENCE_NUMBER_PROPERTY, 1),
                "uniqueAggregateIndex", true);

        eventsCollection.ensureIndex(
                new BasicDBObject(EventEntry.TIME_STAMP_PROPERTY, 1).append(EventEntry.SEQUENCE_NUMBER_PROPERTY, 1),
                "orderedEventStreamIndex", false);
        snapshotsCollection.ensureIndex(new BasicDBObject(EventEntry.AGGREGATE_IDENTIFIER_PROPERTY, 1)
                .append(EventEntry.AGGREGATE_TYPE_PROPERTY, 1).append(EventEntry.SEQUENCE_NUMBER_PROPERTY, 1),
                "uniqueAggregateIndex", true);
    }

    @Override
    public DBCursor findEvents(DBCollection collection, MongoCriteria criteria) {
        DBObject filter = criteria == null ? null : criteria.asMongoObject();
        DBObject sort = BasicDBObjectBuilder.start().add(EventEntry.TIME_STAMP_PROPERTY, ORDER_ASC)
                .add(EventEntry.SEQUENCE_NUMBER_PROPERTY, ORDER_ASC).get();
        return collection.find(filter).sort(sort);
    }

    @Override
    public DBCursor findLastSnapshot(DBCollection collection, String aggregateType, String aggregateIdentifier) {
        DBObject mongoEntry = BasicDBObjectBuilder.start()
                .add(EventEntry.AGGREGATE_IDENTIFIER_PROPERTY, aggregateIdentifier)
                .add(EventEntry.AGGREGATE_TYPE_PROPERTY, aggregateType).get();
        return collection.find(mongoEntry).sort(new BasicDBObject(EventEntry.SEQUENCE_NUMBER_PROPERTY, ORDER_DESC))
                .limit(1);
    }

    /**
     * Data needed by different types of event logs.
     *
     * @author Allard Buijze
     * @author Jettro Coenradie
     * @since 2.0 (in incubator since 0.7)
     */
    private static final class EventEntry implements SerializedDomainEventData {

        /**
         * Property name in mongo for the Aggregate Identifier.
         */
        private static final String AGGREGATE_IDENTIFIER_PROPERTY = "aggregateIdentifier";

        /**
         * Property name in mongo for the Sequence Number.
         */
        private static final String SEQUENCE_NUMBER_PROPERTY = "sequenceNumber";

        /**
         * Property name in mongo for the Aggregate's Type Identifier.
         */
        private static final String AGGREGATE_TYPE_PROPERTY = "type";

        /**
         * Property name in mongo for the Time Stamp.
         */
        private static final String TIME_STAMP_PROPERTY = "timeStamp";

        private static final String SERIALIZED_PAYLOAD_PROPERTY = "serializedPayload";
        private static final String PAYLOAD_TYPE_PROPERTY = "payloadType";
        private static final String PAYLOAD_REVISION_PROPERTY = "payloadRevision";
        private static final String META_DATA_PROPERTY = "serializedMetaData";
        private static final String EVENT_IDENTIFIER_PROPERTY = "eventIdentifier";
        /**
         * Charset used for the serialization is usually UTF-8, which is presented by this constant.
         */
        private final String aggregateIdentifier;
        private final long sequenceNumber;
        private final String timeStamp;
        private final String aggregateType;
        private final Object serializedPayload;
        private final String payloadType;
        private final String payloadRevision;
        private final Object serializedMetaData;
        private final String eventIdentifier;

        /**
         * Constructor used to create a new event entry to store in Mongo.
         *
         * @param aggregateType String containing the aggregate type of the event
         * @param event         The actual DomainEvent to store
         * @param serializer    Serializer to use for the event to store
         */
        private EventEntry(String aggregateType, DomainEventMessage event, Serializer serializer) {
            this.aggregateType = aggregateType;
            this.aggregateIdentifier = event.getAggregateIdentifier().toString();
            this.sequenceNumber = event.getSequenceNumber();
            this.eventIdentifier = event.getIdentifier();
            Class<?> serializationTarget = String.class;
            if (serializer.canSerializeTo(DBObject.class)) {
                serializationTarget = DBObject.class;
            }
            SerializedObject serializedPayloadObject = serializePayload(event, serializer, serializationTarget);
            SerializedObject serializedMetaDataObject = serializeMetaData(event, serializer, serializationTarget);

            this.serializedPayload = serializedPayloadObject.getData();
            this.payloadType = serializedPayloadObject.getType().getName();
            this.payloadRevision = serializedPayloadObject.getType().getRevision();
            this.serializedMetaData = serializedMetaDataObject.getData();
            this.timeStamp = event.getTimestamp().toString();
        }

        /**
         * Creates a new EventEntry based onm data provided by Mongo.
         *
         * @param dbObject Mongo object that contains data to represent an EventEntry
         */
        private EventEntry(DBObject dbObject) {
            this.aggregateIdentifier = (String) dbObject.get(AGGREGATE_IDENTIFIER_PROPERTY);
            this.sequenceNumber = ((Number) dbObject.get(SEQUENCE_NUMBER_PROPERTY)).longValue();
            this.serializedPayload = dbObject.get(SERIALIZED_PAYLOAD_PROPERTY);
            this.timeStamp = (String) dbObject.get(TIME_STAMP_PROPERTY);
            this.aggregateType = (String) dbObject.get(AGGREGATE_TYPE_PROPERTY);
            this.payloadType = (String) dbObject.get(PAYLOAD_TYPE_PROPERTY);
            this.payloadRevision = (String) dbObject.get(PAYLOAD_REVISION_PROPERTY);
            this.serializedMetaData = dbObject.get(META_DATA_PROPERTY);
            this.eventIdentifier = (String) dbObject.get(EVENT_IDENTIFIER_PROPERTY);
        }

        /**
         * Returns the actual DomainEvent from the EventEntry using the provided Serializer.
         *
         * @param actualAggregateIdentifier The actual aggregate identifier instance used to perform the lookup, or
         *                                  <code>null</code> if unknown
         * @param eventSerializer           Serializer used to de-serialize the stored DomainEvent
         * @param upcasterChain             Set of upcasters to use when an event needs upcasting before
         *                                  de-serialization
         * @param skipUnknownTypes          whether to skip unknown event types
         * @return The actual DomainEventMessage instances stored in this entry
         */
        @SuppressWarnings("unchecked")
        public List<DomainEventMessage> getDomainEvents(Object actualAggregateIdentifier,
                Serializer eventSerializer, UpcasterChain upcasterChain, boolean skipUnknownTypes) {
            return upcastAndDeserialize(this, actualAggregateIdentifier, eventSerializer, upcasterChain,
                    skipUnknownTypes);
        }

        private Class<?> getRepresentationType() {
            Class<?> representationType = String.class;
            if (serializedPayload instanceof DBObject) {
                representationType = DBObject.class;
            }
            return representationType;
        }

        @Override
        public String getEventIdentifier() {
            return eventIdentifier;
        }

        @Override
        public Object getAggregateIdentifier() {
            return aggregateIdentifier;
        }

        /**
         * getter for the sequence number of the event.
         *
         * @return long representing the sequence number of the event
         */
        public long getSequenceNumber() {
            return sequenceNumber;
        }

        @Override
        public DateTime getTimestamp() {
            return new DateTime(timeStamp);
        }

        @SuppressWarnings("unchecked")
        @Override
        public SerializedObject getMetaData() {
            return new SerializedMetaData(serializedMetaData, getRepresentationType());
        }

        @SuppressWarnings("unchecked")
        @Override
        public SerializedObject getPayload() {
            return new SimpleSerializedObject(serializedPayload, getRepresentationType(), payloadType,
                    payloadRevision);
        }

        /**
         * Returns the current EventEntry as a mongo DBObject.
         *
         * @return DBObject representing the EventEntry
         */
        public DBObject asDBObject() {
            return BasicDBObjectBuilder.start().add(AGGREGATE_IDENTIFIER_PROPERTY, aggregateIdentifier)
                    .add(SEQUENCE_NUMBER_PROPERTY, sequenceNumber)
                    .add(SERIALIZED_PAYLOAD_PROPERTY, serializedPayload).add(TIME_STAMP_PROPERTY, timeStamp)
                    .add(AGGREGATE_TYPE_PROPERTY, aggregateType).add(PAYLOAD_TYPE_PROPERTY, payloadType)
                    .add(PAYLOAD_REVISION_PROPERTY, payloadRevision).add(META_DATA_PROPERTY, serializedMetaData)
                    .add(EVENT_IDENTIFIER_PROPERTY, eventIdentifier).get();
        }

        /**
         * Returns the mongo DBObject used to query mongo for events for specified aggregate identifier and type.
         *
         * @param type                The type of the aggregate to create the mongo DBObject for
         * @param aggregateIdentifier Identifier of the aggregate to obtain the mongo DBObject for
         * @param firstSequenceNumber number representing the first event to obtain
         * @return Created DBObject based on the provided parameters to be used for a query
         */
        public static DBObject forAggregate(String type, String aggregateIdentifier, long firstSequenceNumber) {
            return BasicDBObjectBuilder.start().add(EventEntry.AGGREGATE_IDENTIFIER_PROPERTY, aggregateIdentifier)
                    .add(EventEntry.SEQUENCE_NUMBER_PROPERTY, new BasicDBObject("$gte", firstSequenceNumber))
                    .add(EventEntry.AGGREGATE_TYPE_PROPERTY, type).get();
        }
    }
}