Java tutorial
/* * 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.BasicDBList; 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.ArrayList; 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 commit as a single document. The document contains an * array * containing each separate event. * <p/> * The structure is as follows: * <ul> * <li>aggregateIdentifier => [aggregateIdentifier]</li> * <li>sequenceNumber => [sequenceNumber of first event]</li> * <li>firstSequenceNumber => [sequenceNumber of first event]</li> * <li>lastSequenceNumber => [sequenceNumber of last event]</li> * <li>timestamp => [timestamp of first event]</li> * <li>firstTimeStamp => [timestamp of first event]</li> * <li>lastTimeStamp => [timestamp of last event]</li> * <li>type => [aggregate type]</li> * <li>events => array of: * <ul> * <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> * <li>sequenceNumber => [sequence number of the event]</li> * <li>timestamp => [timestamp of the event]</li> * </ul> * </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 DocumentPerCommitStorageStrategy 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) { return new DBObject[] { new CommitEntry(type, eventSerializer, messages).asDBObject() }; } @Override public DBCursor findEvents(DBCollection collection, String aggregateType, String aggregateIdentifier, long firstSequenceNumber) { return collection.find(CommitEntry.forAggregate(aggregateType, aggregateIdentifier, firstSequenceNumber)) .sort(new BasicDBObject(CommitEntry.SEQUENCE_NUMBER_PROPERTY, ORDER_ASC)); } @Override public DBCursor findEvents(DBCollection collection, MongoCriteria criteria) { DBObject filter = criteria == null ? null : criteria.asMongoObject(); DBObject sort = BasicDBObjectBuilder.start().add(CommitEntry.TIME_STAMP_PROPERTY, ORDER_ASC) .add(CommitEntry.SEQUENCE_NUMBER_PROPERTY, ORDER_ASC).get(); return collection.find(filter).sort(sort); } @Override public List<DomainEventMessage> extractEventMessages(DBObject entry, Object aggregateIdentifier, Serializer serializer, UpcasterChain upcasterChain, boolean skipUnknownTypes) { return new CommitEntry(entry).getDomainEvents(aggregateIdentifier, serializer, upcasterChain, skipUnknownTypes); } @Override public void ensureIndexes(DBCollection eventsCollection, DBCollection snapshotsCollection) { eventsCollection.ensureIndex(new BasicDBObject(CommitEntry.AGGREGATE_IDENTIFIER_PROPERTY, 1) .append(CommitEntry.AGGREGATE_TYPE_PROPERTY, 1).append(CommitEntry.SEQUENCE_NUMBER_PROPERTY, 1), "uniqueAggregateIndex", true); eventsCollection.ensureIndex(new BasicDBObject(CommitEntry.TIME_STAMP_PROPERTY, 1) .append(CommitEntry.SEQUENCE_NUMBER_PROPERTY, 1), "orderedEventStreamIndex", false); snapshotsCollection.ensureIndex(new BasicDBObject(CommitEntry.AGGREGATE_IDENTIFIER_PROPERTY, 1) .append(CommitEntry.AGGREGATE_TYPE_PROPERTY, 1).append(CommitEntry.SEQUENCE_NUMBER_PROPERTY, 1), "uniqueAggregateIndex", true); } @Override public DBCursor findLastSnapshot(DBCollection collection, String aggregateType, String aggregateIdentifier) { DBObject mongoEntry = BasicDBObjectBuilder.start() .add(CommitEntry.AGGREGATE_IDENTIFIER_PROPERTY, aggregateIdentifier) .add(CommitEntry.AGGREGATE_TYPE_PROPERTY, aggregateType).get(); return collection.find(mongoEntry).sort(new BasicDBObject(CommitEntry.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 CommitEntry { private static final String AGGREGATE_IDENTIFIER_PROPERTY = "aggregateIdentifier"; private static final String SEQUENCE_NUMBER_PROPERTY = "sequenceNumber"; private static final String AGGREGATE_TYPE_PROPERTY = "type"; private static final String TIME_STAMP_PROPERTY = "timestamp"; private static final String FIRST_TIME_STAMP_PROPERTY = "firstTimeStamp"; private static final String LAST_TIME_STAMP_PROPERTY = "lastTimeStamp"; private static final String FIRST_SEQUENCE_NUMBER_PROPERTY = "firstSequenceNumber"; private static final String LAST_SEQUENCE_NUMBER_PROPERTY = "lastSequenceNumber"; private static final String EVENTS_PROPERTY = "events"; /** * Charset used for the serialization is usually UTF-8, which is presented by this constant. */ private final String aggregateIdentifier; private final long firstSequenceNumber; private final long lastSequenceNumber; private final String firstTimestamp; private final String lastTimestamp; private final String aggregateType; private final EventEntry[] eventEntries; /** * Constructor used to create a new event entry to store in Mongo. * * @param aggregateType String containing the aggregate type of the event * @param eventSerializer Serializer to use for the event to store * @param events The events contained in this commit */ private CommitEntry(String aggregateType, Serializer eventSerializer, List<DomainEventMessage> events) { this.aggregateType = aggregateType; this.aggregateIdentifier = events.get(0).getAggregateIdentifier().toString(); this.firstSequenceNumber = events.get(0).getSequenceNumber(); this.firstTimestamp = events.get(0).getTimestamp().toString(); final DomainEventMessage lastEvent = events.get(events.size() - 1); this.lastTimestamp = lastEvent.getTimestamp().toString(); this.lastSequenceNumber = lastEvent.getSequenceNumber(); eventEntries = new EventEntry[events.size()]; for (int i = 0, eventsLength = events.size(); i < eventsLength; i++) { DomainEventMessage event = events.get(i); eventEntries[i] = new EventEntry(eventSerializer, event); } } /** * Creates a new CommitEntry based onm data provided by Mongo. * * @param dbObject Mongo object that contains data to represent an CommitEntry */ @SuppressWarnings("unchecked") private CommitEntry(DBObject dbObject) { this.aggregateIdentifier = (String) dbObject.get(AGGREGATE_IDENTIFIER_PROPERTY); this.firstSequenceNumber = ((Number) dbObject.get(FIRST_SEQUENCE_NUMBER_PROPERTY)).longValue(); this.lastSequenceNumber = ((Number) dbObject.get(LAST_SEQUENCE_NUMBER_PROPERTY)).longValue(); this.firstTimestamp = (String) dbObject.get(FIRST_TIME_STAMP_PROPERTY); this.lastTimestamp = (String) dbObject.get(LAST_TIME_STAMP_PROPERTY); this.aggregateType = (String) dbObject.get(AGGREGATE_TYPE_PROPERTY); List<DBObject> entries = (List<DBObject>) dbObject.get(EVENTS_PROPERTY); eventEntries = new EventEntry[entries.size()]; for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) { eventEntries[i] = new EventEntry(entries.get(i)); } } /** * Returns the actual DomainEvent from the CommitEntry 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 * @return The actual DomainEventMessage instances stored in this entry */ @SuppressWarnings("unchecked") public List<DomainEventMessage> getDomainEvents(Object actualAggregateIdentifier, Serializer eventSerializer, UpcasterChain upcasterChain, boolean skipUnknownTypes) { List<DomainEventMessage> messages = new ArrayList<DomainEventMessage>(); for (final EventEntry eventEntry : eventEntries) { messages.addAll(upcastAndDeserialize(new DomainEventData(this, eventEntry), actualAggregateIdentifier, eventSerializer, upcasterChain, skipUnknownTypes)); } return messages; } public Object getAggregateIdentifier() { return aggregateIdentifier; } /** * Returns the current CommitEntry as a mongo DBObject. * * @return DBObject representing the CommitEntry */ public DBObject asDBObject() { final BasicDBList events = new BasicDBList(); BasicDBObjectBuilder commitBuilder = BasicDBObjectBuilder.start() .add(AGGREGATE_IDENTIFIER_PROPERTY, aggregateIdentifier) .add(SEQUENCE_NUMBER_PROPERTY, firstSequenceNumber) .add(LAST_SEQUENCE_NUMBER_PROPERTY, lastSequenceNumber) .add(FIRST_SEQUENCE_NUMBER_PROPERTY, firstSequenceNumber) .add(TIME_STAMP_PROPERTY, firstTimestamp).add(FIRST_TIME_STAMP_PROPERTY, firstTimestamp) .add(LAST_TIME_STAMP_PROPERTY, lastTimestamp).add(AGGREGATE_TYPE_PROPERTY, aggregateType) .add(EVENTS_PROPERTY, events); for (EventEntry eventEntry : eventEntries) { events.add(eventEntry.asDBObject()); } return commitBuilder.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(CommitEntry.AGGREGATE_IDENTIFIER_PROPERTY, aggregateIdentifier) .add(CommitEntry.SEQUENCE_NUMBER_PROPERTY, new BasicDBObject("$gte", firstSequenceNumber)) .add(CommitEntry.AGGREGATE_TYPE_PROPERTY, type).get(); } private static class DomainEventData implements SerializedDomainEventData { private final CommitEntry commitEntry; private final EventEntry eventEntry; public DomainEventData(CommitEntry commitEntry, EventEntry eventEntry) { this.commitEntry = commitEntry; this.eventEntry = eventEntry; } @Override public String getEventIdentifier() { return eventEntry.getEventIdentifier(); } @Override public Object getAggregateIdentifier() { return commitEntry.getAggregateIdentifier(); } @Override public long getSequenceNumber() { return eventEntry.getSequenceNumber(); } @Override public DateTime getTimestamp() { return eventEntry.getTimestamp(); } @Override public SerializedObject getMetaData() { return eventEntry.getMetaData(); } @Override public SerializedObject getPayload() { return eventEntry.getPayload(); } } } /** * Represents an entry for a single event inside a commit */ private static final class EventEntry { 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"; private static final String EVENT_SEQUENCE_NUMBER_PROPERTY = "sequenceNumber"; private static final String EVENT_TIMESTAMP_PROPERTY = "timestamp"; private final Object serializedPayload; private final String payloadType; private final String payloadRevision; private final Object serializedMetaData; private final String eventIdentifier; private final long sequenceNumber; private final String timestamp; private EventEntry(Serializer serializer, DomainEventMessage event) { 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.sequenceNumber = event.getSequenceNumber(); this.timestamp = event.getTimestamp().toString(); } private EventEntry(DBObject dbObject) { this.serializedPayload = dbObject.get(SERIALIZED_PAYLOAD_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); this.sequenceNumber = (Long) dbObject.get(EVENT_SEQUENCE_NUMBER_PROPERTY); this.timestamp = (String) dbObject.get(EVENT_TIMESTAMP_PROPERTY); } public Class<?> getRepresentationType() { Class<?> representationType = String.class; if (serializedPayload instanceof DBObject) { representationType = DBObject.class; } return representationType; } public String getEventIdentifier() { return eventIdentifier; } @SuppressWarnings("unchecked") public SerializedObject getMetaData() { return new SerializedMetaData(serializedMetaData, getRepresentationType()); } @SuppressWarnings("unchecked") public SerializedObject getPayload() { return new SimpleSerializedObject(serializedPayload, getRepresentationType(), payloadType, payloadRevision); } public long getSequenceNumber() { return sequenceNumber; } public DateTime getTimestamp() { return new DateTime(timestamp); } public DBObject asDBObject() { final BasicDBObjectBuilder entryBuilder = BasicDBObjectBuilder.start(); return entryBuilder.add(SERIALIZED_PAYLOAD_PROPERTY, serializedPayload) .add(PAYLOAD_TYPE_PROPERTY, payloadType).add(PAYLOAD_REVISION_PROPERTY, payloadRevision) .add(EVENT_TIMESTAMP_PROPERTY, timestamp).add(EVENT_SEQUENCE_NUMBER_PROPERTY, sequenceNumber) .add(META_DATA_PROPERTY, serializedMetaData).add(EVENT_IDENTIFIER_PROPERTY, eventIdentifier) .get(); } } }