From fa9e7be7e1d531bfdd6c93ef3aafef8f0e527cb2 Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 17:12:36 +0300 Subject: [PATCH 1/9] Add cleaning of `EntityContext` to `EEntity` --- .../java/io/spine/server/event/EEntity.java | 40 +++++- .../spine/server/event/EventStoreShould.java | 114 +++++++++++++++++- .../given/EventReactorMethodTestEnv.java | 68 ----------- 3 files changed, 147 insertions(+), 75 deletions(-) delete mode 100644 server/src/test/java/io/spine/server/event/given/EventReactorMethodTestEnv.java 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 0a84ea22122..2264186d4be 100644 --- a/server/src/main/java/io/spine/server/event/EEntity.java +++ b/server/src/main/java/io/spine/server/event/EEntity.java @@ -23,9 +23,11 @@ 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; +import io.spine.core.RejectionContext; import io.spine.server.entity.AbstractEntity; import io.spine.server.entity.storage.Column; import io.spine.type.TypeName; @@ -33,6 +35,8 @@ import javax.annotation.Nullable; import java.util.Comparator; +import static io.spine.validate.Validate.isDefault; + /** * Stores an event. * @@ -84,7 +88,8 @@ public int compare(EEntity e1, EEntity e2) { EEntity(Event event) { this(event.getId()); - updateState(event); + final Event optimizedEvent = optimizeContext(event); + updateState(optimizedEvent); } /** @@ -124,4 +129,37 @@ public String getType() { } return typeName.value(); } + + /** + * Obtains the specified event with the optimized {@link EventContext}. + * + *

Removes the following items: + *

+ * + * @param event the event to optimize + * @return the optimized event + */ + private static Event optimizeContext(Event event) { + final EventContext context = event.getContext(); + final EventContext.Builder resultContext = context.toBuilder() + .clearEnrichment(); + final EventContext originEventContext = context.getEventContext(); + if (!isDefault(originEventContext)) { + resultContext.setEventContext(originEventContext.toBuilder() + .clearOrigin() + .clearEnrichment()); + } + final RejectionContext originRejectionContext = context.getRejectionContext(); + if (!isDefault(originRejectionContext)) { + resultContext.setRejectionContext(originRejectionContext.toBuilder() + .clearEnrichment()); + } + return event.toBuilder() + .setContext(resultContext) + .build(); + } } 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 708dcdab688..49ab8a4c49e 100644 --- a/server/src/test/java/io/spine/server/event/EventStoreShould.java +++ b/server/src/test/java/io/spine/server/event/EventStoreShould.java @@ -22,20 +22,26 @@ 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; import io.spine.core.TenantId; +import io.spine.grpc.MemoizingObserver; import io.spine.server.BoundedContext; import io.spine.server.command.TestEventFactory; import io.spine.test.event.ProjectCreated; import io.spine.test.event.TaskAdded; import io.spine.testdata.Sample; import io.spine.time.Durations2; +import io.spine.time.Time; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -45,14 +51,19 @@ 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.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; import static io.spine.type.TypeName.of; +import static io.spine.validate.Validate.isDefault; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -210,21 +221,102 @@ public void fail_to_store_events_of_different_tenants_in_a_single_operation() { .setActorContext(secondTenantActor) .build(); final EventContext firstTenantContext = EventContext.newBuilder() - .setCommandContext(firstTenantCommand) - .build(); + .setCommandContext(firstTenantCommand) + .build(); final EventContext secondTenantContext = EventContext.newBuilder() - .setCommandContext(secondTenantCommand) - .build(); + .setCommandContext(secondTenantCommand) + .build(); final Event firstTenantEvent = Event.newBuilder() .setContext(firstTenantContext) .build(); final Event secondTenantEvent = Event.newBuilder() - .setContext(secondTenantContext) - .build(); + .setContext(secondTenantContext) + .build(); final Collection event = ImmutableSet.of(firstTenantEvent, secondTenantEvent); eventStore.appendAll(event); } + @Test + public void not_store_enrichment_for_EventContext() { + final Event event = projectCreated(Time.getCurrentTime()); + final Event enriched = event.toBuilder() + .setContext(event.getContext() + .toBuilder() + .setEnrichment(newEnrichment())) + .build(); + eventStore.append(enriched); + final MemoizingObserver observer = memoizingObserver(); + eventStore.read(EventStreamQuery.getDefaultInstance(), observer); + final EventContext context = observer.responses() + .get(0) + .getContext(); + assertTrue(isDefault(context.getEnrichment())); + } + + @Test + public void not_store_enrichment_for_origin_of_RejectionContext_type() { + final RejectionContext origin = RejectionContext.newBuilder() + .setEnrichment(newEnrichment()) + .build(); + final Event event = projectCreated(Time.getCurrentTime()); + final Event enriched = event.toBuilder() + .setContext(event.getContext() + .toBuilder() + .setRejectionContext(origin)) + .build(); + eventStore.append(enriched); + final MemoizingObserver observer = memoizingObserver(); + eventStore.read(EventStreamQuery.getDefaultInstance(), observer); + final RejectionContext modifiedOrigin = observer.responses() + .get(0) + .getContext() + .getRejectionContext(); + assertTrue(isDefault(modifiedOrigin.getEnrichment())); + } + + @Test + public void not_store_enrichment_for_origin_of_EventContext_type() { + final EventContext.Builder origin = EventContext.newBuilder() + .setEnrichment(newEnrichment()); + final Event event = projectCreated(Time.getCurrentTime()); + final Event enriched = event.toBuilder() + .setContext(event.getContext() + .toBuilder() + .setEventContext(origin)) + .build(); + eventStore.append(enriched); + final MemoizingObserver observer = memoizingObserver(); + eventStore.read(EventStreamQuery.getDefaultInstance(), observer); + final EventContext modifiedOrigin = observer.responses() + .get(0) + .getContext() + .getEventContext(); + assertTrue(isDefault(modifiedOrigin.getEnrichment())); + } + + @Test + public void not_store_nested_origins_for_EventContext_origin() { + final EventContext.Builder context = EventContext.newBuilder() + .setEnrichment(newEnrichment()); + final EventContext origin = EventContext.newBuilder() + .setEventContext(context) + .build(); + final Event event = projectCreated(Time.getCurrentTime()); + final Event enriched = event.toBuilder() + .setContext(event.getContext() + .toBuilder() + .setEventContext(origin)) + .build(); + eventStore.append(enriched); + final MemoizingObserver observer = memoizingObserver(); + eventStore.read(EventStreamQuery.getDefaultInstance(), observer); + final EventContext modifiedOrigin = observer.responses() + .get(0) + .getContext() + .getEventContext(); + assertTrue(isDefault(modifiedOrigin.getEventContext())); + } + /* * Test environment *********************/ @@ -245,6 +337,16 @@ 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/server/src/test/java/io/spine/server/event/given/EventReactorMethodTestEnv.java b/server/src/test/java/io/spine/server/event/given/EventReactorMethodTestEnv.java deleted file mode 100644 index ab36d14b1c0..00000000000 --- a/server/src/test/java/io/spine/server/event/given/EventReactorMethodTestEnv.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.server.event.given; - -import com.google.protobuf.Int32Value; -import com.google.protobuf.StringValue; -import com.google.protobuf.UInt32Value; -import io.spine.core.React; -import io.spine.server.aggregate.Aggregate; -import io.spine.server.aggregate.Apply; -import io.spine.server.command.Assign; -import io.spine.validate.StringValueVBuilder; - -/** - * @author Alexander Yevsyukov - */ -public class EventReactorMethodTestEnv { - - public static class ReactingAggregate - extends Aggregate { - - public ReactingAggregate(Long id) { - super(id); - } - - @Assign - StringValue handle(Int32Value value) { - final String str = String.valueOf(value.getValue()); - return toMessage(str); - } - - private static StringValue toMessage(String str) { - return StringValue.newBuilder() - .setValue(str) - .build(); - } - - @Apply - private void event(StringValue value) { - final String currentState = getState().getValue(); - getBuilder().setValue(currentState + System.lineSeparator() + value.getValue()); - } - - @React - StringValue on(UInt32Value value) { - final String str = String.valueOf(value.getValue()); - return toMessage(str); - } - } -} From 5442a7418afc5134d584edb78423cacccdc3f0d0 Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 18:08:13 +0300 Subject: [PATCH 2/9] Rename `EEntity.optimizeContext()` to `compact` --- .../main/java/io/spine/server/event/EEntity.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 2264186d4be..c426b288598 100644 --- a/server/src/main/java/io/spine/server/event/EEntity.java +++ b/server/src/main/java/io/spine/server/event/EEntity.java @@ -88,8 +88,8 @@ public int compare(EEntity e1, EEntity e2) { EEntity(Event event) { this(event.getId()); - final Event optimizedEvent = optimizeContext(event); - updateState(optimizedEvent); + final Event compactedEvent = compact(event); + updateState(compactedEvent); } /** @@ -131,19 +131,19 @@ public String getType() { } /** - * Obtains the specified event with the optimized {@link EventContext}. + * Obtains the compacted version of the event. * - *

Removes the following items: + *

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 optimize - * @return the optimized event + * @param event the event to compact + * @return the compacted event */ - private static Event optimizeContext(Event event) { + private static Event compact(Event event) { final EventContext context = event.getContext(); final EventContext.Builder resultContext = context.toBuilder() .clearEnrichment(); From 903b11c6cc52ff62dbacf9196d35d4afa34cb09f Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 18:22:27 +0300 Subject: [PATCH 3/9] Replace `if` statements by `switch` --- .../java/io/spine/server/event/EEntity.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) 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 c426b288598..232fe21adee 100644 --- a/server/src/main/java/io/spine/server/event/EEntity.java +++ b/server/src/main/java/io/spine/server/event/EEntity.java @@ -27,7 +27,6 @@ import io.spine.core.EventEnvelope; import io.spine.core.EventId; import io.spine.core.Events; -import io.spine.core.RejectionContext; import io.spine.server.entity.AbstractEntity; import io.spine.server.entity.storage.Column; import io.spine.type.TypeName; @@ -35,7 +34,7 @@ import javax.annotation.Nullable; import java.util.Comparator; -import static io.spine.validate.Validate.isDefault; +import static io.spine.util.Exceptions.newIllegalStateException; /** * Stores an event. @@ -147,16 +146,23 @@ private static Event compact(Event event) { final EventContext context = event.getContext(); final EventContext.Builder resultContext = context.toBuilder() .clearEnrichment(); - final EventContext originEventContext = context.getEventContext(); - if (!isDefault(originEventContext)) { - resultContext.setEventContext(originEventContext.toBuilder() - .clearOrigin() - .clearEnrichment()); - } - final RejectionContext originRejectionContext = context.getRejectionContext(); - if (!isDefault(originRejectionContext)) { - resultContext.setRejectionContext(originRejectionContext.toBuilder() - .clearEnrichment()); + switch (resultContext.getOriginCase()) { + 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: + throw newIllegalStateException("EventContext.origin should be set."); } return event.toBuilder() .setContext(resultContext) From afec3bf796b7f2c5e931d4cdc99a212db1924c38 Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 18:32:37 +0300 Subject: [PATCH 4/9] Make variable names less confusing --- .../spine/server/event/EventStoreShould.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) 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 49ab8a4c49e..031a4d43d58 100644 --- a/server/src/test/java/io/spine/server/event/EventStoreShould.java +++ b/server/src/test/java/io/spine/server/event/EventStoreShould.java @@ -255,66 +255,66 @@ public void not_store_enrichment_for_EventContext() { @Test public void not_store_enrichment_for_origin_of_RejectionContext_type() { - final RejectionContext origin = RejectionContext.newBuilder() - .setEnrichment(newEnrichment()) - .build(); + final RejectionContext originContext = RejectionContext.newBuilder() + .setEnrichment(newEnrichment()) + .build(); final Event event = projectCreated(Time.getCurrentTime()); final Event enriched = event.toBuilder() .setContext(event.getContext() .toBuilder() - .setRejectionContext(origin)) + .setRejectionContext(originContext)) .build(); eventStore.append(enriched); final MemoizingObserver observer = memoizingObserver(); eventStore.read(EventStreamQuery.getDefaultInstance(), observer); - final RejectionContext modifiedOrigin = observer.responses() - .get(0) - .getContext() - .getRejectionContext(); - assertTrue(isDefault(modifiedOrigin.getEnrichment())); + final RejectionContext loadedOriginContext = observer.responses() + .get(0) + .getContext() + .getRejectionContext(); + assertTrue(isDefault(loadedOriginContext.getEnrichment())); } @Test public void not_store_enrichment_for_origin_of_EventContext_type() { - final EventContext.Builder origin = EventContext.newBuilder() - .setEnrichment(newEnrichment()); + final EventContext.Builder originContext = EventContext.newBuilder() + .setEnrichment(newEnrichment()); final Event event = projectCreated(Time.getCurrentTime()); final Event enriched = event.toBuilder() .setContext(event.getContext() .toBuilder() - .setEventContext(origin)) + .setEventContext(originContext)) .build(); eventStore.append(enriched); final MemoizingObserver observer = memoizingObserver(); eventStore.read(EventStreamQuery.getDefaultInstance(), observer); - final EventContext modifiedOrigin = observer.responses() - .get(0) - .getContext() - .getEventContext(); - assertTrue(isDefault(modifiedOrigin.getEnrichment())); + final EventContext loadedOriginContext = observer.responses() + .get(0) + .getContext() + .getEventContext(); + assertTrue(isDefault(loadedOriginContext.getEnrichment())); } @Test public void not_store_nested_origins_for_EventContext_origin() { final EventContext.Builder context = EventContext.newBuilder() .setEnrichment(newEnrichment()); - final EventContext origin = EventContext.newBuilder() - .setEventContext(context) - .build(); + final EventContext originContext = EventContext.newBuilder() + .setEventContext(context) + .build(); final Event event = projectCreated(Time.getCurrentTime()); final Event enriched = event.toBuilder() .setContext(event.getContext() .toBuilder() - .setEventContext(origin)) + .setEventContext(originContext)) .build(); eventStore.append(enriched); final MemoizingObserver observer = memoizingObserver(); eventStore.read(EventStreamQuery.getDefaultInstance(), observer); - final EventContext modifiedOrigin = observer.responses() + final EventContext loadedOriginContext = observer.responses() .get(0) .getContext() .getEventContext(); - assertTrue(isDefault(modifiedOrigin.getEventContext())); + assertTrue(isDefault(loadedOriginContext.getEventContext())); } /* From 1a73e2ea2552fdd47db6ccd6f16ee8aeb5382bef Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 18:47:29 +0300 Subject: [PATCH 5/9] Add `default` statement for the `switch` --- server/src/main/java/io/spine/server/event/EEntity.java | 2 ++ 1 file changed, 2 insertions(+) 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 232fe21adee..2dc9b9faf49 100644 --- a/server/src/main/java/io/spine/server/event/EEntity.java +++ b/server/src/main/java/io/spine/server/event/EEntity.java @@ -163,6 +163,8 @@ private static Event compact(Event event) { break; case ORIGIN_NOT_SET: throw newIllegalStateException("EventContext.origin should be set."); + default: + throw newIllegalStateException("Not all of `OriginCase` values were handled."); } return event.toBuilder() .setContext(resultContext) From 66bfa0b01be446a5e23a84f804be94befbbd3e89 Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 18:54:46 +0300 Subject: [PATCH 6/9] Fix the failing test --- server/src/main/java/io/spine/server/event/EEntity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 2dc9b9faf49..442c0f0055b 100644 --- a/server/src/main/java/io/spine/server/event/EEntity.java +++ b/server/src/main/java/io/spine/server/event/EEntity.java @@ -162,7 +162,8 @@ private static Event compact(Event event) { // Does nothing. break; case ORIGIN_NOT_SET: - throw newIllegalStateException("EventContext.origin should be set."); + // Does nothing because the origins is `null`. + break; default: throw newIllegalStateException("Not all of `OriginCase` values were handled."); } From 445bc5959ecab2dde58c6f8eb8d3f20fe24b7e1c Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 18:57:11 +0300 Subject: [PATCH 7/9] Fix the comment --- server/src/main/java/io/spine/server/event/EEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 442c0f0055b..2c7eb6dc22a 100644 --- a/server/src/main/java/io/spine/server/event/EEntity.java +++ b/server/src/main/java/io/spine/server/event/EEntity.java @@ -162,7 +162,7 @@ private static Event compact(Event event) { // Does nothing. break; case ORIGIN_NOT_SET: - // Does nothing because the origins is `null`. + // Does nothing because there is no origin for this event. break; default: throw newIllegalStateException("Not all of `OriginCase` values were handled."); From 3b54e90af5ca275776b7988cde5ea3157e2f5b20 Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 19:02:48 +0300 Subject: [PATCH 8/9] Add more details to the exception message --- server/src/main/java/io/spine/server/event/EEntity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 2c7eb6dc22a..ff285dd2145 100644 --- a/server/src/main/java/io/spine/server/event/EEntity.java +++ b/server/src/main/java/io/spine/server/event/EEntity.java @@ -146,7 +146,8 @@ private static Event compact(Event event) { final EventContext context = event.getContext(); final EventContext.Builder resultContext = context.toBuilder() .clearEnrichment(); - switch (resultContext.getOriginCase()) { + final EventContext.OriginCase originCase = resultContext.getOriginCase(); + switch (originCase) { case EVENT_CONTEXT: resultContext.setEventContext(context.getEventContext() .toBuilder() @@ -165,7 +166,8 @@ private static Event compact(Event event) { // Does nothing because there is no origin for this event. break; default: - throw newIllegalStateException("Not all of `OriginCase` values were handled."); + throw newIllegalStateException("Unsupported origin case encountered: %s", + originCase); } return event.toBuilder() .setContext(resultContext) From ddb0a8dd114f8a26cc5756f1a59335ab2b6e62c3 Mon Sep 17 00:00:00 2001 From: "dmytro.grankin" Date: Tue, 19 Sep 2017 19:07:54 +0300 Subject: [PATCH 9/9] Bump Spine version to `0.9.66-SNAPSHOT` --- ext.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext.gradle b/ext.gradle index b897e14c5f9..44c1a90ae60 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.65-SNAPSHOT' +def final SPINE_VERSION = '0.9.66-SNAPSHOT' ext { // The version of the modules in this project.