diff --git a/core/src/main/java/io/spine/core/Events.java b/core/src/main/java/io/spine/core/Events.java
index 3ec4533c488..3a00f9c5f19 100644
--- a/core/src/main/java/io/spine/core/Events.java
+++ b/core/src/main/java/io/spine/core/Events.java
@@ -36,6 +36,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static io.spine.protobuf.AnyPacker.unpack;
+import static io.spine.util.Exceptions.newIllegalStateException;
import static io.spine.validate.Validate.checkNotEmptyOrBlank;
/**
@@ -217,6 +218,56 @@ public static boolean isExternal(EventContext context) {
return context.getExternal();
}
+ /**
+ * Clears enrichments of the specified event.
+ *
+ *
Use this method to decrease a size of an event, if enrichments aren't important.
+ *
+ *
A result won't contain:
+ *
+ * - the enrichment from the event context;
+ * - the enrichment from the first-level origin.
+ *
+ *
+ * Enrichments will not be removed from second-level and deeper origins,
+ * because it's a heavy performance operation.
+ *
+ * @param event the event to clear enrichments
+ * @return the event without enrichments
+ */
+ @Internal
+ public static Event clearEnrichments(Event event) {
+ final EventContext context = event.getContext();
+ final EventContext.Builder resultContext = context.toBuilder()
+ .clearEnrichment();
+ final EventContext.OriginCase originCase = resultContext.getOriginCase();
+ switch (originCase) {
+ case EVENT_CONTEXT:
+ resultContext.setEventContext(context.getEventContext()
+ .toBuilder()
+ .clearEnrichment());
+ break;
+ case REJECTION_CONTEXT:
+ resultContext.setRejectionContext(context.getRejectionContext()
+ .toBuilder()
+ .clearEnrichment());
+ break;
+ case COMMAND_CONTEXT:
+ // Nothing to remove.
+ break;
+ case ORIGIN_NOT_SET:
+ // Does nothing because there is no origin for this event.
+ break;
+ default:
+ throw newIllegalStateException("Unsupported origin case is encountered: %s",
+ originCase);
+ }
+ final Event result = event.toBuilder()
+ .setContext(resultContext)
+ .build();
+ return result;
+ }
+
/**
* The stringifier of event IDs.
*/
diff --git a/core/src/main/proto/spine/core/enrichment.proto b/core/src/main/proto/spine/core/enrichment.proto
index 016c67ffc67..78064114aae 100644
--- a/core/src/main/proto/spine/core/enrichment.proto
+++ b/core/src/main/proto/spine/core/enrichment.proto
@@ -34,7 +34,7 @@ import "google/protobuf/any.proto";
// Attributes with additional information (enrichment) for an outer message.
//
// A message can be enriched with one or more messages. For example, an `Event` can be enriched with
-// information for easier building of user interface projections. A `Rejection` can be eriched with
+// information for easier building of user interface projections. A `Rejection` can be enriched with
// additional information on why the command was rejected.
//
// If message enrichment should not be performed (e.g. because of performance or security
diff --git a/ext.gradle b/ext.gradle
index fd53ed27c1b..ce21c93cf63 100644
--- a/ext.gradle
+++ b/ext.gradle
@@ -25,7 +25,7 @@
* as we want to manage the versions in a single source.
*/
-def final SPINE_VERSION = '0.9.77-SNAPSHOT'
+def final SPINE_VERSION = '0.9.78-SNAPSHOT'
ext {
// The version of the modules in this project.
diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java
index ebaa7c74546..165547112e9 100644
--- a/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java
+++ b/server/src/main/java/io/spine/server/aggregate/AggregateStorage.java
@@ -38,6 +38,7 @@
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.protobuf.TextFormat.shortDebugString;
import static com.google.protobuf.util.Timestamps.checkValid;
+import static io.spine.core.Events.clearEnrichments;
import static io.spine.util.Exceptions.newIllegalStateException;
import static io.spine.validate.Validate.checkNotEmptyOrBlank;
@@ -156,13 +157,17 @@ public void write(I id, AggregateStateRecord events) {
/**
* Writes an event to the storage by an aggregate ID.
*
+ *
Before the storing, {@linkplain io.spine.core.Events#clearEnrichments(Event) enrichments}
+ * will be removed from the event.
+ *
* @param id the aggregate ID
* @param event the event to write
*/
void writeEvent(I id, Event event) {
checkNotClosedAndArguments(id, event);
- final AggregateEventRecord record = toStorageRecord(event);
+ final Event eventWithoutEnrichments = clearEnrichments(event);
+ final AggregateEventRecord record = toStorageRecord(eventWithoutEnrichments);
writeRecord(id, record);
}
diff --git a/server/src/main/java/io/spine/server/event/EEntity.java b/server/src/main/java/io/spine/server/event/EEntity.java
index ff285dd2145..a94a5a01046 100644
--- a/server/src/main/java/io/spine/server/event/EEntity.java
+++ b/server/src/main/java/io/spine/server/event/EEntity.java
@@ -23,7 +23,6 @@
import com.google.protobuf.Timestamp;
import io.spine.annotation.Internal;
import io.spine.core.Event;
-import io.spine.core.EventContext;
import io.spine.core.EventEnvelope;
import io.spine.core.EventId;
import io.spine.core.Events;
@@ -34,10 +33,12 @@
import javax.annotation.Nullable;
import java.util.Comparator;
-import static io.spine.util.Exceptions.newIllegalStateException;
+import static io.spine.core.Events.clearEnrichments;
/**
- * Stores an event.
+ * An entity for storing an event.
+ *
+ *
An underlying event doesn't contain {@linkplain Events#clearEnrichments(Event) enrichments}.
*
* @author Alexander Yevsyukov
* @author Dmytro Dashenkov
@@ -87,8 +88,8 @@ public int compare(EEntity e1, EEntity e2) {
EEntity(Event event) {
this(event.getId());
- final Event compactedEvent = compact(event);
- updateState(compactedEvent);
+ final Event eventWithoutEnrichments = clearEnrichments(event);
+ updateState(eventWithoutEnrichments);
}
/**
@@ -128,49 +129,4 @@ public String getType() {
}
return typeName.value();
}
-
- /**
- * Obtains the compacted version of the event.
- *
- *
A compacted version doesn't contain:
- *
- * - the enrichment from the event context
- * - the enrichment from the origin
- * - nested origins if the origin is {@link EventContext}
- *
- *
- * @param event the event to compact
- * @return the compacted event
- */
- private static Event compact(Event event) {
- final EventContext context = event.getContext();
- final EventContext.Builder resultContext = context.toBuilder()
- .clearEnrichment();
- final EventContext.OriginCase originCase = resultContext.getOriginCase();
- switch (originCase) {
- case EVENT_CONTEXT:
- resultContext.setEventContext(context.getEventContext()
- .toBuilder()
- .clearOrigin()
- .clearEnrichment());
- break;
- case REJECTION_CONTEXT:
- resultContext.setRejectionContext(context.getRejectionContext()
- .toBuilder()
- .clearEnrichment());
- break;
- case COMMAND_CONTEXT:
- // Does nothing.
- break;
- case ORIGIN_NOT_SET:
- // Does nothing because there is no origin for this event.
- break;
- default:
- throw newIllegalStateException("Unsupported origin case encountered: %s",
- originCase);
- }
- return event.toBuilder()
- .setContext(resultContext)
- .build();
- }
}
diff --git a/server/src/test/java/io/spine/server/aggregate/AggregateStorageShould.java b/server/src/test/java/io/spine/server/aggregate/AggregateStorageShould.java
index 28e5c4dbde5..0ffaa90b372 100644
--- a/server/src/test/java/io/spine/server/aggregate/AggregateStorageShould.java
+++ b/server/src/test/java/io/spine/server/aggregate/AggregateStorageShould.java
@@ -27,6 +27,8 @@
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.Timestamps;
import io.spine.core.Event;
+import io.spine.core.EventContext;
+import io.spine.core.RejectionContext;
import io.spine.core.Version;
import io.spine.server.aggregate.given.Given.StorageRecord;
import io.spine.server.command.TestEventFactory;
@@ -52,10 +54,12 @@
import static io.spine.Identifier.newUuid;
import static io.spine.core.Versions.increment;
import static io.spine.core.Versions.zero;
+import static io.spine.core.given.GivenEnrichment.withOneAttribute;
import static io.spine.server.aggregate.given.Given.StorageRecords.sequenceFor;
import static io.spine.server.command.TestEventFactory.newInstance;
import static io.spine.time.Durations2.seconds;
import static io.spine.time.Time.getCurrentTime;
+import static io.spine.validate.Validate.isDefault;
import static java.lang.Integer.MAX_VALUE;
import static java.util.Collections.reverse;
import static org.junit.Assert.assertEquals;
@@ -345,6 +349,65 @@ public void continue_history_reading_if_snapshot_was_not_found_in_first_batch()
assertEquals(eventCountAfterSnapshot, stateRecord.getEventCount());
}
+ @Test
+ public void not_store_enrichment_for_EventContext() {
+ final EventContext enrichedContext = EventContext.newBuilder()
+ .setEnrichment(withOneAttribute())
+ .build();
+ final Event event = Event.newBuilder()
+ .setContext(enrichedContext)
+ .setMessage(Any.getDefaultInstance())
+ .build();
+ storage.writeEvent(id, event);
+ final EventContext loadedContext = storage.read(newReadRequest(id))
+ .get()
+ .getEvent(0)
+ .getContext();
+ assertTrue(isDefault(loadedContext.getEnrichment()));
+ }
+
+ @Test
+ public void not_store_enrichment_for_origin_of_RejectionContext_type() {
+ final RejectionContext origin = RejectionContext.newBuilder()
+ .setEnrichment(withOneAttribute())
+ .build();
+ final EventContext context = EventContext.newBuilder()
+ .setRejectionContext(origin)
+ .build();
+ final Event event = Event.newBuilder()
+ .setContext(context)
+ .setMessage(Any.getDefaultInstance())
+ .build();
+ storage.writeEvent(id, event);
+ final RejectionContext loadedOrigin = storage.read(newReadRequest(id))
+ .get()
+ .getEvent(0)
+ .getContext()
+ .getRejectionContext();
+ assertTrue(isDefault(loadedOrigin.getEnrichment()));
+ }
+
+ @Test
+ public void not_store_enrichment_for_origin_of_EventContext_type() {
+ final EventContext origin = EventContext.newBuilder()
+ .setEnrichment(withOneAttribute())
+ .build();
+ final EventContext context = EventContext.newBuilder()
+ .setEventContext(origin)
+ .build();
+ final Event event = Event.newBuilder()
+ .setContext(context)
+ .setMessage(Any.getDefaultInstance())
+ .build();
+ storage.writeEvent(id, event);
+ final EventContext loadedOrigin = storage.read(newReadRequest(id))
+ .get()
+ .getEvent(0)
+ .getContext()
+ .getEventContext();
+ assertTrue(isDefault(loadedOrigin.getEnrichment()));
+ }
+
@Test(expected = IllegalStateException.class)
public void throw_exception_if_try_to_write_event_count_to_closed_storage() {
close(storage);
@@ -402,18 +465,18 @@ protected void writeAll(ProjectId id, Iterable records) {
}
private Iterator historyBackward() {
- final AggregateReadRequest readRequest = new AggregateReadRequest<>(id, MAX_VALUE);
+ final AggregateReadRequest readRequest = newReadRequest(id);
return storage.historyBackward(readRequest);
}
protected static final Function TO_EVENT =
new Function() {
- @Nullable // return null because an exception won't be propagated in this case
- @Override
- public Event apply(@Nullable AggregateEventRecord input) {
- return (input == null) ? null : input.getEvent();
- }
- };
+ @Nullable // return null because an exception won't be propagated in this case
+ @Override
+ public Event apply(@Nullable AggregateEventRecord input) {
+ return (input == null) ? null : input.getEvent();
+ }
+ };
private static Snapshot newSnapshot(Timestamp time) {
return Snapshot.newBuilder()
diff --git a/server/src/test/java/io/spine/server/event/EventStoreShould.java b/server/src/test/java/io/spine/server/event/EventStoreShould.java
index 031a4d43d58..3a019624556 100644
--- a/server/src/test/java/io/spine/server/event/EventStoreShould.java
+++ b/server/src/test/java/io/spine/server/event/EventStoreShould.java
@@ -22,14 +22,11 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.MoreExecutors;
-import com.google.protobuf.Any;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
import io.grpc.stub.StreamObserver;
import io.spine.core.ActorContext;
import io.spine.core.CommandContext;
-import io.spine.core.Enrichment;
-import io.spine.core.Enrichment.Container;
import io.spine.core.Event;
import io.spine.core.EventContext;
import io.spine.core.RejectionContext;
@@ -51,11 +48,10 @@
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.collect.Sets.newConcurrentHashSet;
-import static com.google.protobuf.Any.pack;
import static com.google.protobuf.util.Timestamps.add;
import static com.google.protobuf.util.Timestamps.subtract;
+import static io.spine.core.given.GivenEnrichment.withOneAttribute;
import static io.spine.grpc.StreamObservers.memoizingObserver;
-import static io.spine.protobuf.TypeConverter.toMessage;
import static io.spine.test.Verify.assertContainsAll;
import static io.spine.test.Verify.assertSize;
import static io.spine.time.Time.getCurrentTime;
@@ -242,7 +238,7 @@ public void not_store_enrichment_for_EventContext() {
final Event enriched = event.toBuilder()
.setContext(event.getContext()
.toBuilder()
- .setEnrichment(newEnrichment()))
+ .setEnrichment(withOneAttribute()))
.build();
eventStore.append(enriched);
final MemoizingObserver observer = memoizingObserver();
@@ -256,7 +252,7 @@ public void not_store_enrichment_for_EventContext() {
@Test
public void not_store_enrichment_for_origin_of_RejectionContext_type() {
final RejectionContext originContext = RejectionContext.newBuilder()
- .setEnrichment(newEnrichment())
+ .setEnrichment(withOneAttribute())
.build();
final Event event = projectCreated(Time.getCurrentTime());
final Event enriched = event.toBuilder()
@@ -277,7 +273,7 @@ public void not_store_enrichment_for_origin_of_RejectionContext_type() {
@Test
public void not_store_enrichment_for_origin_of_EventContext_type() {
final EventContext.Builder originContext = EventContext.newBuilder()
- .setEnrichment(newEnrichment());
+ .setEnrichment(withOneAttribute());
final Event event = projectCreated(Time.getCurrentTime());
final Event enriched = event.toBuilder()
.setContext(event.getContext()
@@ -294,29 +290,6 @@ public void not_store_enrichment_for_origin_of_EventContext_type() {
assertTrue(isDefault(loadedOriginContext.getEnrichment()));
}
- @Test
- public void not_store_nested_origins_for_EventContext_origin() {
- final EventContext.Builder context = EventContext.newBuilder()
- .setEnrichment(newEnrichment());
- final EventContext originContext = EventContext.newBuilder()
- .setEventContext(context)
- .build();
- final Event event = projectCreated(Time.getCurrentTime());
- final Event enriched = event.toBuilder()
- .setContext(event.getContext()
- .toBuilder()
- .setEventContext(originContext))
- .build();
- eventStore.append(enriched);
- final MemoizingObserver observer = memoizingObserver();
- eventStore.read(EventStreamQuery.getDefaultInstance(), observer);
- final EventContext loadedOriginContext = observer.responses()
- .get(0)
- .getContext()
- .getEventContext();
- assertTrue(isDefault(loadedOriginContext.getEventContext()));
- }
-
/*
* Test environment
*********************/
@@ -337,16 +310,6 @@ private static void assertDone(AtomicBoolean done) {
}
}
- private static Enrichment newEnrichment() {
- final String key = "enrichment key";
- final Any value = pack(toMessage("enrichment value"));
- return Enrichment.newBuilder()
- .setContainer(Container.newBuilder()
- .putItems(key, value)
- .build())
- .build();
- }
-
private static class ResponseObserver implements StreamObserver {
private final Collection resultStorage;
diff --git a/testutil-core/src/main/java/io/spine/core/given/GivenEnrichment.java b/testutil-core/src/main/java/io/spine/core/given/GivenEnrichment.java
new file mode 100644
index 00000000000..aefb4231f2f
--- /dev/null
+++ b/testutil-core/src/main/java/io/spine/core/given/GivenEnrichment.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017, TeamDev Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.core.given;
+
+import com.google.protobuf.Any;
+import io.spine.core.Enrichment;
+
+import static com.google.protobuf.Any.pack;
+import static io.spine.Identifier.newUuid;
+import static io.spine.protobuf.TypeConverter.toMessage;
+
+/**
+ * Factory methods to create {@code Enrichment} instances in test purposes.
+ *
+ * @author Dmytro Grankin
+ */
+public class GivenEnrichment {
+
+ /** Prevents instantiation of this utility class. */
+ private GivenEnrichment() {}
+
+ /**
+ * Creates a new {@link Enrichment} with one {@linkplain Enrichment#getContainer() attribute}.
+ *
+ * An enrichment attribute is invalid and has random values.
+ *
+ * @return a new enrichment instance
+ */
+ public static Enrichment withOneAttribute() {
+ final String key = newUuid();
+ final Any value = pack(toMessage(newUuid()));
+ final Enrichment result = Enrichment.newBuilder()
+ .setContainer(Enrichment.Container.newBuilder()
+ .putItems(key, value)
+ .build())
+ .build();
+ return result;
+ }
+}
diff --git a/testutil-core/src/test/java/io/spine/core/given/GivenEnrichmentShould.java b/testutil-core/src/test/java/io/spine/core/given/GivenEnrichmentShould.java
new file mode 100644
index 00000000000..ac7bba8a12d
--- /dev/null
+++ b/testutil-core/src/test/java/io/spine/core/given/GivenEnrichmentShould.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017, TeamDev Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.core.given;
+
+import com.google.protobuf.Any;
+import io.spine.core.Enrichment;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static io.spine.core.given.GivenEnrichment.withOneAttribute;
+import static io.spine.test.Tests.assertHasPrivateParameterlessCtor;
+import static io.spine.test.Verify.assertSize;
+
+/**
+ * @author Dmytro Grankin
+ */
+public class GivenEnrichmentShould {
+
+ @Test
+ public void have_private_utility_ctor() {
+ assertHasPrivateParameterlessCtor(GivenEnrichment.class);
+ }
+
+ @Test
+ public void create_enrichment_with_one_attribute() {
+ final Enrichment enrichment = withOneAttribute();
+ final Map enrichmentAttributes = enrichment.getContainer()
+ .getItems();
+ assertSize(1, enrichmentAttributes);
+ }
+}