diff --git a/client/src/main/java/io/spine/client/CommandFactory.java b/client/src/main/java/io/spine/client/CommandFactory.java index 526d2b58f80..535afea865d 100644 --- a/client/src/main/java/io/spine/client/CommandFactory.java +++ b/client/src/main/java/io/spine/client/CommandFactory.java @@ -170,11 +170,11 @@ private CommandContext createContext(int targetVersion) { private static CommandContext withCurrentTime(CommandContext value) { ActorContext withCurrentTime = value.getActorContext() - .toVBuilder() + .toBuilder() .setTimestamp(currentTime()) .build(); CommandContext result = - value.toVBuilder() + value.toBuilder() .setActorContext(withCurrentTime) .build(); return result; diff --git a/client/src/main/java/io/spine/client/Queries.java b/client/src/main/java/io/spine/client/Queries.java index e72ea403d18..c881fe7e8a4 100644 --- a/client/src/main/java/io/spine/client/Queries.java +++ b/client/src/main/java/io/spine/client/Queries.java @@ -53,7 +53,7 @@ private Queries() { public static QueryId generateId() { String formattedId = format(QUERY_ID_FORMAT, Identifier.newUuid()); - return QueryId.vBuilder() + return QueryId.newBuilder() .setValue(formattedId) .build(); } diff --git a/client/src/main/java/io/spine/client/QueryBuilder.java b/client/src/main/java/io/spine/client/QueryBuilder.java index 97ab05aeec0..6f6892fb6d8 100644 --- a/client/src/main/java/io/spine/client/QueryBuilder.java +++ b/client/src/main/java/io/spine/client/QueryBuilder.java @@ -139,7 +139,7 @@ private Optional pagination() { if (limit == 0) { return Optional.empty(); } - Pagination result = Pagination.vBuilder() + Pagination result = Pagination.newBuilder() .setPageSize(limit) .build(); return Optional.of(result); @@ -149,7 +149,7 @@ private Optional orderBy() { if (orderingColumn == null) { return Optional.empty(); } - OrderBy result = OrderBy.vBuilder() + OrderBy result = OrderBy.newBuilder() .setColumn(orderingColumn) .setDirection(direction) .build(); diff --git a/client/src/main/java/io/spine/client/Targets.java b/client/src/main/java/io/spine/client/Targets.java index 5c98e0a3dd5..953f3763660 100644 --- a/client/src/main/java/io/spine/client/Targets.java +++ b/client/src/main/java/io/spine/client/Targets.java @@ -95,6 +95,7 @@ public static Target allOf(Class targetClass) { * a set of predicates which target entity state or event message must match * @return a {@code Target} instance formed according to the provided parameters */ + @SuppressWarnings("CheckReturnValue") public static Target composeTarget(Class targetClass, @Nullable Iterable ids, @Nullable Iterable filters) { @@ -103,7 +104,7 @@ public static Target composeTarget(Class targetClass, boolean includeAll = (ids == null && filters == null); TypeUrl typeUrl = TypeUrl.of(targetClass); - TargetVBuilder builder = Target.vBuilder() + Target.Builder builder = Target.newBuilder() .setType(typeUrl.value()); if (includeAll) { builder.setIncludeAll(true); @@ -132,7 +133,7 @@ private static IdFilter composeIdFilter(Collection items) { private static IdFilter idFilter(List ids) { return IdFilter - .vBuilder() + .newBuilder() .addAllIds(ids) .build(); } @@ -154,7 +155,7 @@ private static I checkId(I item) { } private static TargetFilters targetFilters(List filters, IdFilter idFilter) { - return TargetFilters.vBuilder() + return TargetFilters.newBuilder() .setIdFilter(idFilter) .addAllFilter(filters) .build(); diff --git a/config b/config index 9ee31f25fd3..b2576eafe6c 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 9ee31f25fd377adfc130b2521c58f38ea5b2d94f +Subproject commit b2576eafe6cb240fe360b49153543b3f678b6454 diff --git a/core/src/main/java/io/spine/core/EventMixin.java b/core/src/main/java/io/spine/core/EventMixin.java index 3d9c2779f79..318af835177 100644 --- a/core/src/main/java/io/spine/core/EventMixin.java +++ b/core/src/main/java/io/spine/core/EventMixin.java @@ -95,16 +95,16 @@ default boolean isRejection() { default Event clearEnrichments() { EventContext context = context(); EventContext.OriginCase originCase = context.getOriginCase(); - EventContextVBuilder resultContext = context.toVBuilder() + EventContext.Builder resultContext = context.toBuilder() .clearEnrichment(); if (originCase == EVENT_CONTEXT) { resultContext.setEventContext(context.getEventContext() - .toVBuilder() + .toBuilder() .clearEnrichment() .build()); } Event thisEvent = (Event) this; - Event result = thisEvent.toVBuilder() + Event result = thisEvent.toBuilder() .setContext(resultContext.build()) .build(); return result; diff --git a/license-report.md b/license-report.md index dbb79eef898..6e4f456017b 100644 --- a/license-report.md +++ b/license-report.md @@ -431,7 +431,7 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:26 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri May 17 11:12:03 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -821,7 +821,7 @@ This report was generated on **Mon May 13 10:24:26 EEST 2019** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:27 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri May 17 11:12:03 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -1359,7 +1359,7 @@ This report was generated on **Mon May 13 10:24:27 EEST 2019** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:27 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri May 17 11:12:04 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -2059,7 +2059,7 @@ This report was generated on **Mon May 13 10:24:27 EEST 2019** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:28 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri May 17 11:12:04 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -2603,7 +2603,7 @@ This report was generated on **Mon May 13 10:24:28 EEST 2019** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:29 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri May 17 11:12:05 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -3108,7 +3108,7 @@ This report was generated on **Mon May 13 10:24:29 EEST 2019** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:29 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri May 17 11:12:05 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -3621,7 +3621,7 @@ This report was generated on **Mon May 13 10:24:29 EEST 2019** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:30 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Fri May 17 11:12:05 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). @@ -4234,4 +4234,4 @@ This report was generated on **Mon May 13 10:24:30 EEST 2019** using [Gradle-Lic The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon May 13 10:24:30 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Fri May 17 11:12:06 EEST 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/server/src/main/java/io/spine/server/BoundedContext.java b/server/src/main/java/io/spine/server/BoundedContext.java index 145000becfa..fd199e9d451 100644 --- a/server/src/main/java/io/spine/server/BoundedContext.java +++ b/server/src/main/java/io/spine/server/BoundedContext.java @@ -165,10 +165,12 @@ private static CommandBus buildCommandBus(BoundedContextBuilder builder, EventBu /** * Creates a new instance of {@link IntegrationBus} with the given parameters. * - * @param builder the {@link BoundedContextBuilder} to obtain - * the {@link IntegrationBus.Builder} from - * @param eventBus the initialized {@link EventBus} - * @param name the name of the constructed Bounded Context + * @param builder + * the {@link BoundedContextBuilder} to obtain the {@link IntegrationBus.Builder} from + * @param eventBus + * the initialized {@link EventBus} + * @param name + * the name of the constructed Bounded Context * @return new instance of {@link IntegrationBus} */ private static IntegrationBus buildIntegrationBus(BoundedContextBuilder builder, @@ -216,7 +218,7 @@ public static BoundedContextBuilder newBuilder() { */ public > void register(Repository repository) { checkNotNull(repository); - repository.setBoundedContext(this); + repository.setContext(this); guard.register(repository); repository.onRegistered(); registerEventDispatcher(stand()); @@ -270,10 +272,13 @@ private void registerWithIntegrationBus(ExternalDispatcherFactory dispatcher) } /** - * Registers the passed event dispatcher with the {@code EventBus} of - * this {@code BoundedContext}, if it dispatches domestic events. + * Registers the passed event dispatcher with the buses of this {@code BoundedContext}. + * + *

If the passed instance dispatches domestic events, registers it with the {@code EventBus}. * If the passed instance dispatches external events, registers it with * the {@code IntegrationBus}. + * + * @see #registerEventDispatcher(EventDispatcherDelegate) */ public void registerEventDispatcher(EventDispatcher dispatcher) { checkNotNull(dispatcher); @@ -288,10 +293,10 @@ public void registerEventDispatcher(EventDispatcher dispatcher) { } /** - * Registers the passed event dispatcher with the {@code EventBus} of - * this {@code BoundedContext}, if it dispatchers domestic events. - * If the passed instance dispatches external events, registers it with - * the {@code IntegrationBus}. + * Registers the passed delegate of an {@link EventDispatcher} with the buses of this + * {@code BoundedContext}. + * + * @see #registerEventDispatcher(EventDispatcher) */ public void registerEventDispatcher(EventDispatcherDelegate dispatcher) { checkNotNull(dispatcher); @@ -305,7 +310,8 @@ public void registerEventDispatcher(EventDispatcherDelegate dispatcher) { */ private static Supplier notExternalDispatcherFrom(Object dispatcher) { - return () -> newIllegalStateException("No external dispatcher provided by %s", dispatcher); + return () -> newIllegalStateException( + "No external dispatcher provided by `%s`.", dispatcher); } /** @@ -518,4 +524,12 @@ private void shutDownRepositories() { tenantIndex.close(); } } + + /** + * Returns the name of this Bounded Context. + */ + @Override + public String toString() { + return name.getValue(); + } } diff --git a/server/src/main/java/io/spine/server/aggregate/AggregatePartRepository.java b/server/src/main/java/io/spine/server/aggregate/AggregatePartRepository.java index 0b42ff3b8b3..4ca07233da0 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregatePartRepository.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregatePartRepository.java @@ -20,7 +20,9 @@ package io.spine.server.aggregate; +import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; import io.spine.annotation.Internal; +import io.spine.server.BoundedContext; import io.spine.server.aggregate.model.AggregatePartClass; import static io.spine.server.aggregate.model.AggregatePartClass.asAggregatePartClass; @@ -47,11 +49,19 @@ protected AggregatePartRepository() { super(); } + /** + * Registers itself with the {@link io.spine.server.BoundedContext#aggregateRootDirectory() + * AggregateRootDirectory} of the parent {@code BoundedContext}. + * + * @param context + * the Bounded Context of this repository + */ @Override - public void onRegistered() { - super.onRegistered(); - boundedContext().aggregateRootDirectory() - .register(this); + @OverridingMethodsMustInvokeSuper + protected void init(BoundedContext context) { + super.init(context); + context.aggregateRootDirectory() + .register(this); } @Override @@ -71,9 +81,8 @@ AggregatePartClass aggregatePartClass() { return (AggregatePartClass) entityModelClass(); } - //TODO:2017-06-06:alexander.yevsyukov: Cache aggregate roots shared among part repositories private AggregateRoot createAggregateRoot(I id) { - AggregateRoot result = aggregatePartClass().createRoot(boundedContext(), id); + AggregateRoot result = aggregatePartClass().createRoot(context(), id); return result; } diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateRepository.java b/server/src/main/java/io/spine/server/aggregate/AggregateRepository.java index 285f7d344fa..72a94361178 100644 --- a/server/src/main/java/io/spine/server/aggregate/AggregateRepository.java +++ b/server/src/main/java/io/spine/server/aggregate/AggregateRepository.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets.SetView; +import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; import io.spine.core.CommandId; import io.spine.core.Event; import io.spine.server.BoundedContext; @@ -34,7 +35,6 @@ import io.spine.server.event.EventBus; import io.spine.server.event.EventDispatcherDelegate; import io.spine.server.route.CommandRouting; -import io.spine.server.route.EventRoute; import io.spine.server.route.EventRouting; import io.spine.server.storage.Storage; import io.spine.server.storage.StorageFactory; @@ -46,10 +46,12 @@ import java.util.Collection; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Suppliers.memoize; import static com.google.common.collect.Sets.union; import static io.spine.option.EntityOption.Kind.AGGREGATE; import static io.spine.server.aggregate.model.AggregateClass.asAggregateClass; @@ -64,29 +66,27 @@ * the type of the aggregate IDs * @param * the type of the aggregates managed by this repository + * @see Aggregate */ @SuppressWarnings("ClassWithTooManyMethods") public abstract class AggregateRepository> extends Repository - implements CommandDispatcher, - EventDispatcherDelegate { + implements CommandDispatcher, EventDispatcherDelegate { /** The default number of events to be stored before a next snapshot is made. */ static final int DEFAULT_SNAPSHOT_TRIGGER = 100; /** The routing schema for commands handled by the aggregates. */ - private final CommandRouting commandRouting = CommandRouting.newInstance(); + private final Supplier> commandRouting; /** The routing schema for events to which aggregates react. */ - private final EventRouting eventRouting = - EventRouting.withDefault(EventRoute.byProducerId()); + private final EventRouting eventRouting = EventRouting.withDefaultByProducerId(); /** * The routing for event import, which by default obtains the target aggregate ID as the * {@linkplain io.spine.core.EventContext#getProducerId() producer ID} of the event. */ - private final EventRouting eventImportRoute = - EventRouting.withDefault(EventRoute.byProducerId()); + private final EventRouting eventImportRouting = EventRouting.withDefaultByProducerId(); /** * The {@link CommandErrorHandler} tackling the dispatching errors. @@ -102,34 +102,43 @@ public abstract class AggregateRepository> /** Creates a new instance. */ protected AggregateRepository() { super(); + this.commandRouting = memoize(() -> CommandRouting.newInstance(idClass())); } /** - * {@inheritDoc} + * Initializes the repository during its registration with a {@code BoundedContext}. + * + *

Verifies that the class of aggregates of this repository subscribes to at least one + * type of messages. * - *

{@code AggregateRepository} also registers itself with: + *

Registers itself with {@link io.spine.server.commandbus.CommandBus CommandBus}, + * {@link io.spine.server.event.EventBus EventBus}, and + * {@link io.spine.server.aggregate.ImportBus ImportBus} of the parent {@code BoundedContext} + * for dispatching messages to its aggregates. * - *

+ * @param context + * the {@code BoundedContext} of this repository + * @throws IllegalStateException + * if the aggregate class does not handle any messages */ @Override - public void onRegistered() { + @OverridingMethodsMustInvokeSuper + protected void init(BoundedContext context) { checkNotVoid(); - super.onRegistered(); + super.init(context); - BoundedContext boundedContext = boundedContext(); - boundedContext.registerCommandDispatcher(this); - boundedContext.registerEventDispatcher(this); + setupCommandRouting(commandRouting.get()); + setupEventRouting(eventRouting); + setupImportRouting(eventImportRouting); + + context.registerCommandDispatcher(this); + context.registerEventDispatcher(this); if (aggregateClass().importsEvents()) { - boundedContext.importBus() - .register(EventImportDispatcher.of(this)); + context.importBus() + .register(EventImportDispatcher.of(this)); } - this.commandErrorHandler = boundedContext.createCommandErrorHandler(); + this.commandErrorHandler = context.createCommandErrorHandler(); } /** @@ -141,11 +150,68 @@ private void checkNotVoid() { if (!handlesCommands && !reactsOnEvents) { throw newIllegalStateException( - "Aggregates of the repository %s neither handle commands" + + "Aggregates of the repository `%s` neither handle commands" + " nor react on events.", this); } } + /** + * A callback for derived classes to customize routing schema for commands. + * + *

Default routing returns the value of the first field of a command message. + * + * @param routing + * the routing schema to customize + */ + @SuppressWarnings("NoopMethodInAbstractClass") // See Javadoc + protected void setupCommandRouting(CommandRouting routing) { + // Do nothing. + } + + /** + * A callback for derived classes to customize routing schema for events. + * + *

Default routing returns the ID of the entity which + * {@linkplain io.spine.core.EventContext#getProducerId() produced} the event. + * This allows to “link” different kinds of entities by having the same class of IDs. + * More complex scenarios (e.g. one-to-many relationships) may require custom routing schemas. + * + * @param routing + * the routing schema to customize + */ + @SuppressWarnings("NoopMethodInAbstractClass") // see Javadoc + protected void setupEventRouting(EventRouting routing) { + // Do nothing. + } + + /** + * A callback for derived classes to customize routing schema for importable events. + * + *

The default routing uses {@linkplain io.spine.core.EventContext#getProducerId() + * producer ID} of the event as the ID of the target aggregate. + * + *

This default routing requires that {@link Event Event} instances + * {@linkplain ImportBus#post(com.google.protobuf.Message, io.grpc.stub.StreamObserver) posted} + * for import must {@link io.spine.core.EventContext#getProducerId() contain} the ID of the + * target aggregate. Not providing a valid aggregate ID would result in + * {@code RuntimeException}. + * + *

Some aggregates may produce events with the aggregate ID as the first field of an event + * message. To set the default routing for repositories of such aggregates, please use the + * code below: + * + *

{@code
+     * routing.replaceDefault(EventRoute.fromFirstMessageField());
+     * }
+ * + * @param routing + * the routing schema to customize. + */ + @SuppressWarnings("NoopMethodInAbstractClass") // see Javadoc + protected void setupImportRouting(EventRouting routing) { + // Do nothing. + } + @Override public A create(I id) { A aggregate = aggregateClass().create(id); @@ -308,7 +374,7 @@ private void dispatchTo(I id, EventEnvelope event) { /** * Imports the passed event into one of the aggregates. */ - I importEvent(EventEnvelope event) { + final I importEvent(EventEnvelope event) { checkNotNull(event); I target = with(event.tenantId()).evaluate(() -> doImport(event)); return target; @@ -322,7 +388,7 @@ private I doImport(EventEnvelope event) { } private I routeImport(EventEnvelope event) { - Set ids = eventImportRouting().apply(event.message(), event.context()); + Set ids = eventImportRouting.apply(event.message(), event.context()); int numberOfTargets = ids.size(); checkState( numberOfTargets > 0, @@ -357,48 +423,23 @@ public void onError(EventEnvelope event, RuntimeException exception) { /** * Obtains command routing instance used by this repository. */ - protected final CommandRouting commandRouting() { - return commandRouting; + private CommandRouting commandRouting() { + return commandRouting.get(); } /** * Obtains event routing instance used by this repository. */ - protected final EventRouting eventRouting() { + private EventRouting eventRouting() { return eventRouting; } - /** - * Obtains the event import routing, which by default uses - * {@linkplain io.spine.core.EventContext#getProducerId() producer ID} of the event - * as the target aggregate ID. - * - *

This default routing requires that {@link Event Event} instances - * {@linkplain ImportBus#post(com.google.protobuf.Message, io.grpc.stub.StreamObserver) posted} - * for import must {@link io.spine.core.EventContext#getProducerId() contain} the ID of the - * target aggregate. Not providing a valid aggregate ID would result in - * {@code RuntimeException}. - * - *

Some aggregates may produce events with the aggregate ID as the first field of an event - * message. To set the default routing for repositories of such aggregates, please use the - * code below: - * - *

{@code
-     * eventImportRouting().replaceDefault(EventRoute.fromFirstMessageField());
-     * }
- * - * Consider adding this code to the constructor of your {@code AggregateRepository} class. - */ - protected final EventRouting eventImportRouting() { - return eventImportRoute; - } - /** * Posts passed events to {@link EventBus}. */ - void postEvents(Collection events) { + final void postEvents(Collection events) { Iterable filteredEvents = eventFilter().filter(events); - EventBus bus = boundedContext().eventBus(); + EventBus bus = context().eventBus(); bus.post(filteredEvents); } @@ -442,7 +483,7 @@ protected AggregateStorage aggregateStorage() { * @param id the ID of the aggregate * @return loaded or created aggregate instance */ - A loadOrCreate(I id) { + final A loadOrCreate(I id) { A result = load(id).orElseGet(() -> createNew(id)); return result; } @@ -546,7 +587,7 @@ protected EntityLifecycle lifecycleOf(I id) { return super.lifecycleOf(id); } - void onDispatchEvent(I id, Event event) { + final void onDispatchEvent(I id, Event event) { lifecycleOf(id).onDispatchEventToReactor(event); } @@ -554,7 +595,7 @@ private void onCommandTargetSet(I id, CommandId commandId) { lifecycleOf(id).onTargetAssignedToCommand(commandId); } - void onEventImported(I id, Event event) { + final void onEventImported(I id, Event event) { lifecycleOf(id).onEventImported(event); } } diff --git a/server/src/main/java/io/spine/server/aggregate/Apply.java b/server/src/main/java/io/spine/server/aggregate/Apply.java index cd457019143..69bd27d5af1 100644 --- a/server/src/main/java/io/spine/server/aggregate/Apply.java +++ b/server/src/main/java/io/spine/server/aggregate/Apply.java @@ -56,7 +56,7 @@ * defined as the first parameter of the annotated method. * * @see ImportBus - * @see AggregateRepository#eventImportRouting() + * @see AggregateRepository#setupImportRouting(io.spine.server.route.EventRouting) */ boolean allowImport() default false; } diff --git a/server/src/main/java/io/spine/server/aggregate/ImportBus.java b/server/src/main/java/io/spine/server/aggregate/ImportBus.java index 7ad314d8964..5a9e1c6a0a4 100644 --- a/server/src/main/java/io/spine/server/aggregate/ImportBus.java +++ b/server/src/main/java/io/spine/server/aggregate/ImportBus.java @@ -60,8 +60,8 @@ * * *

{@linkplain Apply#allowImport() Marking} events and ensuring proper - * {@linkplain AggregateRepository#eventImportRouting() routing} allows to store aggregate - * events without having intermediate messages. + * {@linkplain AggregateRepository#setupImportRouting(io.spine.server.route.EventRouting) routing} + * allows to store aggregate events without having intermediate messages. * *

Temporal Logic

* diff --git a/server/src/main/java/io/spine/server/aggregate/VersionSequence.java b/server/src/main/java/io/spine/server/aggregate/VersionSequence.java index edb9658b0b1..971ff89f59d 100644 --- a/server/src/main/java/io/spine/server/aggregate/VersionSequence.java +++ b/server/src/main/java/io/spine/server/aggregate/VersionSequence.java @@ -77,11 +77,11 @@ ImmutableList update(Collection originalEvents) { private static Event substituteVersion(Event event, Version newVersion) { EventContext newContext = event.context() - .toVBuilder() + .toBuilder() .setVersion(newVersion) .build(); Event result = - event.toVBuilder() + event.toBuilder() .setContext(newContext) .build(); return result; diff --git a/server/src/main/java/io/spine/server/bus/MessageDispatcher.java b/server/src/main/java/io/spine/server/bus/MessageDispatcher.java index b689ab2dc14..3677379e091 100644 --- a/server/src/main/java/io/spine/server/bus/MessageDispatcher.java +++ b/server/src/main/java/io/spine/server/bus/MessageDispatcher.java @@ -29,13 +29,16 @@ /** * A dispatcher of a message. * - * @param the type of class of the dispatched messages - * @param the type of the message envelopes - * @param the type of the result of the {@linkplain #dispatch(MessageEnvelope) dispatching - * function}. For {@linkplain UnicastDispatcher unicast dispatching} is the type of - * the IDs of entity that receives a dispatched message. - * For {@linkplain MulticastDispatcher multicast dispatching} is the type of the set - * of entity IDs. + * @param + * the type of class of the dispatched messages + * @param + * the type of the message envelopes + * @param + * the type of the result of the {@linkplain #dispatch(MessageEnvelope) dispatching + * function}. For {@linkplain UnicastDispatcher unicast dispatching} is the type of + * the IDs of entity that receives a dispatched message. + * For {@linkplain MulticastDispatcher multicast dispatching} is the type of the set + * of entity IDs. */ public interface MessageDispatcher { diff --git a/server/src/main/java/io/spine/server/commandbus/CommandScheduler.java b/server/src/main/java/io/spine/server/commandbus/CommandScheduler.java index 9e390c964b3..f0d21088850 100644 --- a/server/src/main/java/io/spine/server/commandbus/CommandScheduler.java +++ b/server/src/main/java/io/spine/server/commandbus/CommandScheduler.java @@ -212,18 +212,18 @@ static Command setSchedule(Command command, Duration delay, Timestamp scheduling CommandContext context = command.getContext(); CommandContext.Schedule scheduleUpdated = context.getSchedule() - .toVBuilder() + .toBuilder() .setDelay(delay) .build(); - CommandContext contextUpdated = context.toVBuilder() + CommandContext contextUpdated = context.toBuilder() .setSchedule(scheduleUpdated) .build(); Command.SystemProperties sysProps = command.getSystemProperties() - .toVBuilder() + .toBuilder() .setSchedulingTime(schedulingTime) .build(); - Command result = command.toVBuilder() + Command result = command.toBuilder() .setContext(contextUpdated) .setSystemProperties(sysProps) .build(); diff --git a/server/src/main/java/io/spine/server/commandbus/CommandValidator.java b/server/src/main/java/io/spine/server/commandbus/CommandValidator.java index be1cf7c1d06..f69896a76be 100644 --- a/server/src/main/java/io/spine/server/commandbus/CommandValidator.java +++ b/server/src/main/java/io/spine/server/commandbus/CommandValidator.java @@ -26,6 +26,7 @@ import io.spine.base.CommandMessage; import io.spine.base.Identifier; import io.spine.core.Command; +import io.spine.core.CommandContext; import io.spine.core.CommandId; import io.spine.core.MessageInvalid; import io.spine.core.TenantId; @@ -58,6 +59,10 @@ */ final class CommandValidator implements EnvelopeValidator { + /** Default route for validating command message fields. */ + private static final DefaultCommandRoute defaultRoute = + DefaultCommandRoute.newInstance(Object.class); + private final CommandBus commandBus; CommandValidator(CommandBus commandBus) { @@ -181,12 +186,14 @@ private void validateContext() { private void validateTargetId() { CommandMessage message = command.message(); - Optional targetId = DefaultCommandRoute.asOptional(message); - if (targetId.isPresent()) { - Object target = targetId.get(); - if (Identifier.isEmpty(target)) { - addViolation("Command target entity ID cannot be empty."); - } + if (!DefaultCommandRoute.exists(message)) { + addViolation("The command message does not have a field with a command target ID."); + return; + } + + Object target = defaultRoute.apply(message, CommandContext.getDefaultInstance()); + if (Identifier.isEmpty(target)) { + addViolation("Command target ID cannot be empty."); } } diff --git a/server/src/main/java/io/spine/server/entity/AbstractEntity.java b/server/src/main/java/io/spine/server/entity/AbstractEntity.java index 0735b98b25b..0dc3b8c7e19 100644 --- a/server/src/main/java/io/spine/server/entity/AbstractEntity.java +++ b/server/src/main/java/io/spine/server/entity/AbstractEntity.java @@ -333,7 +333,7 @@ public final boolean isArchived() { * Sets {@code archived} status flag to the passed value. */ protected void setArchived(boolean archived) { - setLifecycleFlags(lifecycleFlags().toVBuilder() + setLifecycleFlags(lifecycleFlags().toBuilder() .setArchived(archived) .build()); } @@ -352,7 +352,7 @@ public final boolean isDeleted() { * Sets {@code deleted} status flag to the passed value. */ protected void setDeleted(boolean deleted) { - setLifecycleFlags(lifecycleFlags().toVBuilder() + setLifecycleFlags(lifecycleFlags().toBuilder() .setDeleted(deleted) .build()); } diff --git a/server/src/main/java/io/spine/server/entity/DefaultRecordBasedRepository.java b/server/src/main/java/io/spine/server/entity/DefaultRecordBasedRepository.java index 8dd685398f2..32acad7a68a 100644 --- a/server/src/main/java/io/spine/server/entity/DefaultRecordBasedRepository.java +++ b/server/src/main/java/io/spine/server/entity/DefaultRecordBasedRepository.java @@ -24,6 +24,7 @@ import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; import com.google.errorprone.annotations.concurrent.LazyInit; import com.google.protobuf.Message; +import io.spine.server.BoundedContext; import io.spine.server.entity.model.EntityClass; import io.spine.type.TypeUrl; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -66,14 +67,16 @@ protected StorageConverter storageConverter() { } /** - * @inheritDoc + * Initializes the repository by performing the validation of the entity class and + * creating the storage converter. * - *

Performs validation of the entity class and initializes the storage converter. + * @param context + * the Bounded Context of this repository */ @OverridingMethodsMustInvokeSuper @Override - public void onRegistered() { - super.onRegistered(); + protected void init(BoundedContext context) { + super.init(context); storageConverter(); } } diff --git a/server/src/main/java/io/spine/server/entity/EntityLifecycle.java b/server/src/main/java/io/spine/server/entity/EntityLifecycle.java index c02e708f166..9b9db79b8dd 100644 --- a/server/src/main/java/io/spine/server/entity/EntityLifecycle.java +++ b/server/src/main/java/io/spine/server/entity/EntityLifecycle.java @@ -35,7 +35,6 @@ import io.spine.option.EntityOption; import io.spine.system.server.CommandTarget; import io.spine.system.server.DispatchedMessageId; -import io.spine.system.server.DispatchedMessageIdVBuilder; import io.spine.system.server.EntityHistoryId; import io.spine.system.server.SystemWriteSide; import io.spine.system.server.command.AssignTargetToCommand; @@ -402,7 +401,7 @@ protected void postCommand(CommandMessage command) { @SuppressWarnings("ChainOfInstanceofChecks") private static DispatchedMessageId dispatchedMessageId(MessageId messageId) { checkNotNull(messageId); - DispatchedMessageIdVBuilder builder = DispatchedMessageId.vBuilder(); + DispatchedMessageId.Builder builder = DispatchedMessageId.newBuilder(); if (messageId instanceof EventId) { EventId eventId = (EventId) messageId; return builder.setEventId(eventId) diff --git a/server/src/main/java/io/spine/server/entity/EventDispatchingRepository.java b/server/src/main/java/io/spine/server/entity/EventDispatchingRepository.java index 6bbcfbf1ff2..041d9a243a7 100644 --- a/server/src/main/java/io/spine/server/entity/EventDispatchingRepository.java +++ b/server/src/main/java/io/spine/server/entity/EventDispatchingRepository.java @@ -20,13 +20,13 @@ package io.spine.server.entity; +import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; import com.google.protobuf.Message; -import io.spine.base.EventMessage; import io.spine.core.Event; +import io.spine.server.BoundedContext; import io.spine.server.event.EventDispatcher; import io.spine.server.integration.ExternalMessageDispatcher; import io.spine.server.integration.ExternalMessageEnvelope; -import io.spine.server.route.EventRoute; import io.spine.server.route.EventRouting; import io.spine.server.type.EventEnvelope; @@ -46,34 +46,47 @@ public abstract class EventDispatchingRepository eventRouting; - /** - * Creates new repository instance. - * - * @param defaultFunction the default function for getting target entity IDs - */ - protected EventDispatchingRepository(EventRoute defaultFunction) { + protected EventDispatchingRepository() { super(); - this.eventRouting = EventRouting.withDefault(defaultFunction); + this.eventRouting = EventRouting.withDefaultByProducerId(); } /** * Obtains the {@link EventRouting} schema used by the repository for calculating identifiers * of event targets. */ - protected final EventRouting eventRouting() { + private EventRouting eventRouting() { return eventRouting; } /** - * {@inheritDoc} + * Registers itself as an event dispatcher with the parent {@code BoundedContext}. * - *

{@linkplain io.spine.server.event.EventBus#register(io.spine.server.bus.MessageDispatcher) - * Registers} itself with the {@code EventBus} of the parent {@code BoundedContext}. + * @param context + * the {@code BoundedContext} of this repository */ @Override - public void onRegistered() { - super.onRegistered(); - boundedContext().registerEventDispatcher(this); + @OverridingMethodsMustInvokeSuper + protected void init(BoundedContext context) { + super.init(context); + context.registerEventDispatcher(this); + setupEventRouting(eventRouting()); + } + + /** + * A callback for derived repository classes to customize routing schema for events. + * + *

Default routing returns the ID of the entity which + * {@linkplain io.spine.core.EventContext#getProducerId() produced} the event. + * This allows to “link” different kinds of entities by having the same class of IDs. + * More complex scenarios (e.g. one-to-many relationships) may require custom routing schemas. + * + * @param routing + * the routing schema to customize + */ + @SuppressWarnings("NoopMethodInAbstractClass") // see Javadoc + protected void setupEventRouting(EventRouting routing) { + // Do nothing. } /** diff --git a/server/src/main/java/io/spine/server/entity/EventFieldFilter.java b/server/src/main/java/io/spine/server/entity/EventFieldFilter.java index 85897bb9d3e..89259dbf584 100644 --- a/server/src/main/java/io/spine/server/entity/EventFieldFilter.java +++ b/server/src/main/java/io/spine/server/entity/EventFieldFilter.java @@ -72,7 +72,7 @@ public ImmutableCollection filter(Collection events) { private Event maskEvent(Event event) { EventMessage message = event.enclosedMessage(); EventMessage masked = mask(message); - return event.toVBuilder() + return event.toBuilder() .setMessage(pack(masked)) .build(); } diff --git a/server/src/main/java/io/spine/server/entity/EventFilter.java b/server/src/main/java/io/spine/server/entity/EventFilter.java index 0822e4747ec..66b77631d94 100644 --- a/server/src/main/java/io/spine/server/entity/EventFilter.java +++ b/server/src/main/java/io/spine/server/entity/EventFilter.java @@ -88,7 +88,7 @@ default ImmutableCollection filter(Collection events) { .map(event -> { EventMessage eventMessage = event.enclosedMessage(); Optional filtered = filter(eventMessage); - Optional result = filtered.map(message -> event.toVBuilder() + Optional result = filtered.map(message -> event.toBuilder() .setMessage(pack(message)) .build()); return result; diff --git a/server/src/main/java/io/spine/server/entity/IncrementFromEvent.java b/server/src/main/java/io/spine/server/entity/IncrementFromEvent.java index 4d4ba31cdb9..93d3871cebf 100644 --- a/server/src/main/java/io/spine/server/entity/IncrementFromEvent.java +++ b/server/src/main/java/io/spine/server/entity/IncrementFromEvent.java @@ -36,7 +36,7 @@ * current version of an {@code Aggregate} is the version of the last applied event. */ @Internal -public class IncrementFromEvent extends VersionIncrement { +public final class IncrementFromEvent extends VersionIncrement { private final EventEnvelope event; diff --git a/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java b/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java index a2d83c8f0b2..88b933a97b2 100644 --- a/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java +++ b/server/src/main/java/io/spine/server/entity/RecordBasedRepository.java @@ -30,6 +30,7 @@ import io.spine.client.OrderBy; import io.spine.client.Pagination; import io.spine.client.TargetFilters; +import io.spine.server.BoundedContext; import io.spine.server.entity.storage.Column; import io.spine.server.entity.storage.EntityColumnCache; import io.spine.server.entity.storage.EntityQueries; @@ -97,15 +98,16 @@ protected RecordStorage recordStorage() { } /** - * {@inheritDoc} + * Initializes the repository by caching {@link Column} definitions of + * the {@link Entity} class managed by this repository. * - *

Caches {@link Column} definitions of the {@link Entity} class managed by this repository. + * @param context + * the Bounded Context of this repository */ @Override @OverridingMethodsMustInvokeSuper - public void onRegistered() { - super.onRegistered(); - + protected void init(BoundedContext context) { + super.init(context); cacheEntityColumns(); } diff --git a/server/src/main/java/io/spine/server/entity/Repository.java b/server/src/main/java/io/spine/server/entity/Repository.java index 1aad04a8931..887592cafbc 100644 --- a/server/src/main/java/io/spine/server/entity/Repository.java +++ b/server/src/main/java/io/spine/server/entity/Repository.java @@ -64,9 +64,10 @@ public abstract class Repository> implements AutoClose * The {@link BoundedContext} to which the repository belongs. * *

This field is null when a repository is not {@linkplain - * BoundedContext#register(Repository) registered} yet. + * BoundedContext#register(Repository) registered} yet and + * after the repository is {@linkplain #close() closed}. */ - private @MonotonicNonNull BoundedContext boundedContext; + private @Nullable BoundedContext context; /** * Model class of entities managed by this repository. @@ -171,8 +172,8 @@ public TypeUrl entityStateType() { /** * Obtains classes of the events produced by this {@code Repository}. * - *

For convenience purposes the default version returns empty collection. This method should be - * overridden by repositories which actually produce events. + *

For convenience purposes the default version returns an empty set. + * This method should be overridden by repositories which actually produce events. */ public ImmutableSet outgoingEvents() { return ImmutableSet.of(); @@ -184,45 +185,86 @@ public ImmutableSet outgoingEvents() { *

If the repository does not have a storage assigned prior to this call, the storage * will be {@linkplain #initStorage(StorageFactory) initialized} from a {@code StorageFactory} * associated with the passed {@code BoundedContext}. + * + *

A context for a repository can be set only once. Passing the same second time will have + * no effect. + * + * @throws IllegalStateException + * if the repository has a context value already assigned, and the passed value is + * not equal to the assigned one */ @Internal - public final void setBoundedContext(BoundedContext boundedContext) { - this.boundedContext = boundedContext; + public final void setContext(BoundedContext context) { + checkNotNull(context); + boolean sameValue = context.equals(this.context); + if (this.context != null && !sameValue) { + throw newIllegalStateException( + "The repository `%s` has the Bounded Context (`%s`) assigned." + + " This operation can be performed only once." + + " Attempted to set: `%s`.", + this, this.context, context); + } + + if (sameValue) { + return; + } + + this.context = context; if (!isStorageAssigned()) { - initStorage(boundedContext.storageFactory()); + initStorage(context.storageFactory()); } + init(context); + } + + /** + * Initializes the repository during its {@linkplain BoundedContext#register(Repository) + * registration} with a {@code BoundedContext}. + * + *

When this method is called, the repository already has {@link #context() BoundedContext} + * and the {@link #storage() Storage} {@linkplain #initStorage(StorageFactory) assigned}. + * + *

Registers itself as a type supplier with the {@link io.spine.server.stand.Stand Stand} + * of the parent {@code BoundedContext}. + * + * @param context + * the {@code BoundedContext} of this repository + */ + @OverridingMethodsMustInvokeSuper + protected void init(BoundedContext context) { + context.stand() + .registerTypeSupplier(this); } /** * Verifies whether the repository is registered with a {@code BoundedContext}. */ protected final boolean isRegistered() { - return boundedContext != null; + return context != null; } /** - * Obtains {@code BoundedContext} to which this repository belongs. + * Obtains the {@code BoundedContext} to which this repository belongs. * * @return parent {@code BoundedContext} * @throws IllegalStateException * if the repository is not registered {@linkplain BoundedContext#register(Repository) * registered} yet */ - protected final BoundedContext boundedContext() { - checkState(boundedContext != null, + protected final BoundedContext context() { + checkState(context != null, "The repository (class: `%s`) is not registered with a `BoundedContext`.", getClass().getName()); - return boundedContext; + return context; } /** * The callback called by a {@link BoundedContext} during the {@linkplain * BoundedContext#register(Repository) registration} of the repository. */ + @SuppressWarnings("NoopMethodInAbstractClass") // see Javadoc @OverridingMethodsMustInvokeSuper public void onRegistered() { - boundedContext().stand() - .registerTypeSupplier(this); + // Do nothing by default. } /** @@ -291,12 +333,13 @@ public void close() { this.storage.close(); this.storage = null; } + this.context = null; } /** * Verifies if the repository is open. */ - public boolean isOpen() { + public final boolean isOpen() { return storage != null; } @@ -336,8 +379,8 @@ protected void logError(String msgFormat, protected EntityLifecycle lifecycleOf(I id) { checkNotNull(id); TypeUrl stateType = entityStateType(); - SystemWriteSide writeSide = boundedContext().systemClient() - .writeSide(); + SystemWriteSide writeSide = context().systemClient() + .writeSide(); EventFilter eventFilter = eventFilter(); EntityLifecycle lifecycle = EntityLifecycle .newBuilder() @@ -423,7 +466,7 @@ public E next() { Optional loaded = repository.find(id); if (!loaded.isPresent()) { String idStr = Identifier.toString(id); - throw newIllegalStateException("Unable to load entity with ID: %s", idStr); + throw newIllegalStateException("Unable to load entity with ID: `%s`.", idStr); } E entity = loaded.get(); diff --git a/server/src/main/java/io/spine/server/entity/Transaction.java b/server/src/main/java/io/spine/server/entity/Transaction.java index b6b80d9b3be..5a0122f1431 100644 --- a/server/src/main/java/io/spine/server/entity/Transaction.java +++ b/server/src/main/java/io/spine/server/entity/Transaction.java @@ -486,13 +486,13 @@ public void setListener(TransactionListener listener) { } public void setArchived(boolean archived) { - lifecycleFlags = lifecycleFlags.toVBuilder() + lifecycleFlags = lifecycleFlags.toBuilder() .setArchived(archived) .build(); } public void setDeleted(boolean deleted) { - lifecycleFlags = lifecycleFlags.toVBuilder() + lifecycleFlags = lifecycleFlags.toBuilder() .setDeleted(deleted) .build(); } diff --git a/server/src/main/java/io/spine/server/entity/model/StateSubscribingClass.java b/server/src/main/java/io/spine/server/entity/model/StateSubscribingClass.java index f9063d11e82..d977d97522b 100644 --- a/server/src/main/java/io/spine/server/entity/model/StateSubscribingClass.java +++ b/server/src/main/java/io/spine/server/entity/model/StateSubscribingClass.java @@ -39,4 +39,14 @@ public interface StateSubscribingClass { * Obtains external entity states to which the class is subscribed. */ Set externalStates(); + + /** + * Verifies if this class is {@linkplain io.spine.core.Subscribe subscribed} to updates of + * entity states, either domestic or external. + */ + default boolean subscribesToStates() { + boolean dispatchesDomestic = !domesticStates().isEmpty(); + boolean dispatchesExternal = !externalStates().isEmpty(); + return dispatchesDomestic || dispatchesExternal; + } } diff --git a/server/src/main/java/io/spine/server/entity/rejection/StandardRejection.java b/server/src/main/java/io/spine/server/entity/rejection/StandardRejection.java new file mode 100644 index 00000000000..6905b110f8e --- /dev/null +++ b/server/src/main/java/io/spine/server/entity/rejection/StandardRejection.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019, TeamDev. 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.entity.rejection; + +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.Any; +import io.spine.annotation.GeneratedMixin; +import io.spine.base.RejectionMessage; +import io.spine.protobuf.AnyPacker; + +/** + * Interface common for standard rejections which is used during routing. + */ +@Immutable +@GeneratedMixin +public interface StandardRejection extends RejectionMessage { + + /** + * Obtains the packed version of ID of the entity which caused the rejection. + */ + Any getEntityId(); + + /** + * Obtains the ID of the entity from the {@linkplain #getEntityId() packed form}. + */ + default Object entityId() { + Object result = AnyPacker.unpack(getEntityId()); + return result; + } +} diff --git a/server/src/main/java/io/spine/server/entity/rejection/package-info.java b/server/src/main/java/io/spine/server/entity/rejection/package-info.java new file mode 100644 index 00000000000..aff946095f6 --- /dev/null +++ b/server/src/main/java/io/spine/server/entity/rejection/package-info.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019, TeamDev. 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. + */ + +/** + * This package contains classes and interfaces for rejections related to lifecycle of entities. + */ + +@CheckReturnValue +@ParametersAreNonnullByDefault +package io.spine.server.entity.rejection; + +import com.google.errorprone.annotations.CheckReturnValue; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/src/main/java/io/spine/server/entity/storage/EntityColumn.java b/server/src/main/java/io/spine/server/entity/storage/EntityColumn.java index 6718c7d00f3..360f40def46 100644 --- a/server/src/main/java/io/spine/server/entity/storage/EntityColumn.java +++ b/server/src/main/java/io/spine/server/entity/storage/EntityColumn.java @@ -222,6 +222,8 @@ private EntityColumn(Method getter, String storedName, boolean nullable) { this.getter = getter; + // To allow calling on non-public classes. + this.getter.setAccessible(true); this.entityType = getter.getDeclaringClass(); this.getterMethodName = getter.getName(); this.name = name; @@ -295,16 +297,16 @@ public boolean isNullable() { try { Serializable result = (Serializable) getter.invoke(source); if (!nullable) { - checkNotNull(result, format("Not null getter %s returned null.", getter.getName())); + checkNotNull(result, format("Not null getter `%s` returned null.", getter.getName())); } Serializable value = toPersistedValue(result); return value; } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException( - format("Could not invoke getter of property %s from object %s", - getName(), - source), - e); + throw newIllegalStateException( + e, "Could not invoke getter of property `%s` from object `%s`.", + getName(), + source + ); } } diff --git a/server/src/main/java/io/spine/server/event/EventFactory.java b/server/src/main/java/io/spine/server/event/EventFactory.java index 4c868a53470..76bf6cd6366 100644 --- a/server/src/main/java/io/spine/server/event/EventFactory.java +++ b/server/src/main/java/io/spine/server/event/EventFactory.java @@ -161,7 +161,7 @@ private static Event createEvent(EventId id, EventMessage message, EventContext checkNotNull(context); Any packed = pack(message); Event result = Event - .vBuilder() + .newBuilder() .setId(id) .setMessage(packed) .setContext(context) diff --git a/server/src/main/java/io/spine/server/event/RejectionEnvelope.java b/server/src/main/java/io/spine/server/event/RejectionEnvelope.java index 9642bee9b0a..49bb7f4334a 100644 --- a/server/src/main/java/io/spine/server/event/RejectionEnvelope.java +++ b/server/src/main/java/io/spine/server/event/RejectionEnvelope.java @@ -206,7 +206,7 @@ public DispatchedCommand getOrigin() { Any commandMessage = rejectionContext.getCommandMessage(); CommandContext commandContext = context.getCommandContext(); DispatchedCommand result = DispatchedCommand - .vBuilder() + .newBuilder() .setMessage(commandMessage) .setContext(commandContext) .build(); diff --git a/server/src/main/java/io/spine/server/event/model/EventReceivingClassDelegate.java b/server/src/main/java/io/spine/server/event/model/EventReceivingClassDelegate.java index 8d9358d2efb..b2fdae954d0 100644 --- a/server/src/main/java/io/spine/server/event/model/EventReceivingClassDelegate.java +++ b/server/src/main/java/io/spine/server/event/model/EventReceivingClassDelegate.java @@ -145,7 +145,7 @@ private ImmutableSet extractStates(boolean external) { .filter(h -> h instanceof StateSubscriberMethod) .map(h -> (StateSubscriberMethod) h) .filter(external ? HandlerMethod::isExternal : HandlerMethod::isDomestic) - .map(StateSubscriberMethod::entityType) + .map(StateSubscriberMethod::stateType) .map(StateClass::from) .collect(toImmutableSet()); return result; diff --git a/server/src/main/java/io/spine/server/event/model/EventSubscriberMethod.java b/server/src/main/java/io/spine/server/event/model/EventSubscriberMethod.java index ade69e865f4..5e1b42b0a24 100644 --- a/server/src/main/java/io/spine/server/event/model/EventSubscriberMethod.java +++ b/server/src/main/java/io/spine/server/event/model/EventSubscriberMethod.java @@ -58,7 +58,7 @@ public MessageFilter filter() { Object expectedValue = fromString(byFieldFilter.value(), fieldType); Any packedValue = toAny(expectedValue); MessageFilter messageFilter = MessageFilter - .vBuilder() + .newBuilder() .setField(fieldPath) .setValue(packedValue) .build(); diff --git a/server/src/main/java/io/spine/server/event/model/StateSubscriberMethod.java b/server/src/main/java/io/spine/server/event/model/StateSubscriberMethod.java index f4175a53db8..409bfd1cd54 100644 --- a/server/src/main/java/io/spine/server/event/model/StateSubscriberMethod.java +++ b/server/src/main/java/io/spine/server/event/model/StateSubscriberMethod.java @@ -52,15 +52,15 @@ public final class StateSubscriberMethod extends SubscriberMethod implements Log private static final FieldPath TYPE_URL_PATH = FieldPaths.parse("id.type_url"); private final BoundedContextName contextOfSubscriber; - private final Class entityType; + private final Class stateType; private final Any typeUrlAsAny; StateSubscriberMethod(Method method, ParameterSpec parameterSpec) { super(checkNotFiltered(method), parameterSpec); this.contextOfSubscriber = contextOf(method.getDeclaringClass()); - this.entityType = firstParamType(rawMethod()); + this.stateType = firstParamType(rawMethod()); checkExternal(); - TypeUrl targetType = TypeUrl.of(this.entityType); + TypeUrl targetType = TypeUrl.of(this.stateType); this.typeUrlAsAny = toAny(targetType.value()); } @@ -74,22 +74,22 @@ private static Method checkNotFiltered(Method method) { } private void checkExternal() { - BoundedContextName originContext = contextOf(entityType()); + BoundedContextName originContext = contextOf(stateType()); boolean external = !originContext.equals(contextOfSubscriber); ensureExternalMatch(external); } /** - * Obtains the type of the entity to which the method is subscribed. + * Obtains the type of the entity state to which the method is subscribed. */ - public Class entityType() { - return entityType; + public Class stateType() { + return stateType; } @Override public MessageFilter filter() { return MessageFilter - .vBuilder() + .newBuilder() .setField(TYPE_URL_PATH) .setValue(typeUrlAsAny) .build(); diff --git a/server/src/main/java/io/spine/server/event/model/SubscriberMethod.java b/server/src/main/java/io/spine/server/event/model/SubscriberMethod.java index c40127d49ae..10b77517d0e 100644 --- a/server/src/main/java/io/spine/server/event/model/SubscriberMethod.java +++ b/server/src/main/java/io/spine/server/event/model/SubscriberMethod.java @@ -70,7 +70,7 @@ public HandlerId id() { FieldPath fieldPath = filter.getField(); return fieldPath.getFieldNameList().isEmpty() ? typeBasedToken - : typeBasedToken.toVBuilder() + : typeBasedToken.toBuilder() .setFilter(filter) .build(); } diff --git a/server/src/main/java/io/spine/server/event/store/QueryToFilters.java b/server/src/main/java/io/spine/server/event/store/QueryToFilters.java index c89c8a3df9b..b367648dc85 100644 --- a/server/src/main/java/io/spine/server/event/store/QueryToFilters.java +++ b/server/src/main/java/io/spine/server/event/store/QueryToFilters.java @@ -22,10 +22,8 @@ import com.google.protobuf.Timestamp; import io.spine.client.CompositeFilter; -import io.spine.client.CompositeFilterVBuilder; import io.spine.client.Filter; import io.spine.client.TargetFilters; -import io.spine.client.TargetFiltersVBuilder; import io.spine.server.event.EventFilter; import io.spine.server.event.EventStreamQuery; @@ -45,7 +43,7 @@ final class QueryToFilters { private final EventStreamQuery query; - private final TargetFiltersVBuilder builder; + private final TargetFilters.Builder builder; /** * Creates an instance of {@link TargetFilters} from the given {@link EventStreamQuery}. @@ -65,7 +63,7 @@ static TargetFilters convert(EventStreamQuery query) { private QueryToFilters(EventStreamQuery query) { this.query = query; - this.builder = TargetFilters.vBuilder(); + this.builder = TargetFilters.newBuilder(); } private TargetFilters convert() { @@ -76,8 +74,8 @@ private TargetFilters convert() { @SuppressWarnings("CheckReturnValue") // calling builder private void addTimeFilter() { - CompositeFilterVBuilder timeFilter = CompositeFilter - .vBuilder() + CompositeFilter.Builder timeFilter = CompositeFilter + .newBuilder() .setOperator(ALL); String createdColumn = ColumnName.created.name(); if (query.hasAfter()) { @@ -95,8 +93,8 @@ private void addTimeFilter() { @SuppressWarnings("CheckReturnValue") // calling builder private void addTypeFilter() { - CompositeFilterVBuilder typeFilter = CompositeFilter - .vBuilder() + CompositeFilter.Builder typeFilter = CompositeFilter + .newBuilder() .setOperator(EITHER); String typeColumn = ColumnName.type.name(); for (EventFilter eventFilter : query.getFilterList()) { @@ -111,8 +109,8 @@ private void addTypeFilter() { } @SuppressWarnings("CheckReturnValue") // calling builder - private void add(CompositeFilterVBuilder filter) { - boolean filterIsEmpty = filter.getFilter() + private void add(CompositeFilter.Builder filter) { + boolean filterIsEmpty = filter.getFilterList() .isEmpty(); if (!filterIsEmpty) { builder.addFilter(filter.build()); diff --git a/server/src/main/java/io/spine/server/integration/EventBusAdapter.java b/server/src/main/java/io/spine/server/integration/EventBusAdapter.java index 28e64216e0c..aeb0a2c8912 100644 --- a/server/src/main/java/io/spine/server/integration/EventBusAdapter.java +++ b/server/src/main/java/io/spine/server/integration/EventBusAdapter.java @@ -25,7 +25,6 @@ import io.spine.core.BoundedContextName; import io.spine.core.Event; import io.spine.core.EventContext; -import io.spine.core.EventVBuilder; import io.spine.server.event.EventBus; import io.spine.server.event.EventDispatcher; import io.spine.server.type.EventClass; @@ -62,9 +61,9 @@ ExternalMessageEnvelope toExternalEnvelope(ExternalMessage message) { ExternalMessageEnvelope markExternal(ExternalMessage externalMsg) { Any packedEvent = externalMsg.getOriginalMessage(); Event event = unpack(packedEvent, Event.class); - EventVBuilder eventBuilder = event.toVBuilder(); + Event.Builder eventBuilder = event.toBuilder(); EventContext modifiedContext = eventBuilder.getContext() - .toVBuilder() + .toBuilder() .setExternal(true) .build(); diff --git a/server/src/main/java/io/spine/server/model/MessageHandlerMap.java b/server/src/main/java/io/spine/server/model/MessageHandlerMap.java index 9ab86f01a94..6e239383aae 100644 --- a/server/src/main/java/io/spine/server/model/MessageHandlerMap.java +++ b/server/src/main/java/io/spine/server/model/MessageHandlerMap.java @@ -168,7 +168,7 @@ public ImmutableCollection handlersOf(M messageClass, MessageClass originClas .build(); HandlerTypeInfo presentKey = map.containsKey(keyWithOrigin) ? keyWithOrigin - : keyWithOrigin.toVBuilder() + : keyWithOrigin.toBuilder() .clearOriginType() .build(); return handlersOf(presentKey); diff --git a/server/src/main/java/io/spine/server/model/UnknownEntityTypeException.java b/server/src/main/java/io/spine/server/model/UnknownEntityTypeException.java index 9f54e7c16a0..6162c38f0c1 100644 --- a/server/src/main/java/io/spine/server/model/UnknownEntityTypeException.java +++ b/server/src/main/java/io/spine/server/model/UnknownEntityTypeException.java @@ -32,6 +32,6 @@ public final class UnknownEntityTypeException extends RuntimeException { private static final long serialVersionUID = 0L; public UnknownEntityTypeException(TypeUrl type) { - super(format("Type `%s` does not belong to any known bounded context.", type)); + super(format("Type `%s` does not belong to any known Bounded Context.", type)); } } diff --git a/server/src/main/java/io/spine/server/procman/ProcessManagerRepository.java b/server/src/main/java/io/spine/server/procman/ProcessManagerRepository.java index 6d9d545c208..38c4ccd6d3a 100644 --- a/server/src/main/java/io/spine/server/procman/ProcessManagerRepository.java +++ b/server/src/main/java/io/spine/server/procman/ProcessManagerRepository.java @@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; import com.google.protobuf.Message; import io.spine.annotation.Internal; import io.spine.core.Event; @@ -42,6 +43,7 @@ import io.spine.server.procman.model.ProcessManagerClass; import io.spine.server.route.CommandRouting; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.server.type.CommandClass; import io.spine.server.type.CommandEnvelope; import io.spine.server.type.EventClass; @@ -51,8 +53,10 @@ import java.util.Collection; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Suppliers.memoize; import static io.spine.option.EntityOption.Kind.PROCESS_MANAGER; import static io.spine.server.procman.model.ProcessManagerClass.asProcessManagerClass; import static io.spine.server.tenant.TenantAwareRunner.with; @@ -61,9 +65,12 @@ /** * The abstract base for Process Managers repositories. * - * @param the type of IDs of process managers - * @param

the type of process managers - * @param the type of process manager state messages + * @param + * the type of IDs of process managers + * @param

+ * the type of process managers + * @param + * the type of process manager state messages * @see ProcessManager */ public abstract class ProcessManagerRepository { /** The command routing schema used by this repository. */ - private final CommandRouting commandRouting = CommandRouting.newInstance(); + private final Supplier> commandRouting; /** * The {@link CommandErrorHandler} tackling the dispatching errors. @@ -94,11 +101,9 @@ public abstract class ProcessManagerRepository CommandRouting.newInstance(idClass())); } /** @@ -117,6 +122,8 @@ protected final ProcessManagerClass

toModelClass(Class

cls) { /** * {@inheritDoc} * + *

Customizes event routing to use first message field. + * *

Registers with the {@code CommandBus} for dispatching commands * (via {@linkplain DelegatingCommandDispatcher delegating dispatcher}). * @@ -133,19 +140,52 @@ protected final ProcessManagerClass

toModelClass(Class

cls) { * * *

Throws an {@code IllegalStateException} otherwise. + * @param context + * the Bounded Context of this repository + * @throws IllegalStateException + * if the Process Manager class of this repository does not declare message + * handling methods */ @Override - public void onRegistered() { - super.onRegistered(); - - BoundedContext boundedContext = boundedContext(); - boundedContext.registerCommandDispatcher(this); + @OverridingMethodsMustInvokeSuper + protected void init(BoundedContext context) { + super.init(context); + setupCommandRouting(commandRouting()); checkNotDeaf(); - this.commandErrorHandler = boundedContext.createCommandErrorHandler(); + context.registerCommandDispatcher(this); + + this.commandErrorHandler = context.createCommandErrorHandler(); PmSystemEventWatcher systemSubscriber = new PmSystemEventWatcher<>(this); - systemSubscriber.registerIn(boundedContext); + systemSubscriber.registerIn(context); + } + + /** + * Replaces default routing with the one which takes the target ID from the first field + * of an event message. + * + * @param routing + * the routing to customize + */ + @Override + @OverridingMethodsMustInvokeSuper + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.replaceDefault(EventRoute.byFirstMessageField(idClass())); + } + + /** + * A callback for derived classes to customize routing schema for commands. + * + *

Default routing returns the value of the first field of a command message. + * + * @param routing + * the routing schema to customize + */ + @SuppressWarnings("NoopMethodInAbstractClass") // See Javadoc + protected void setupCommandRouting(CommandRouting routing) { + // Do nothing. } /** @@ -198,8 +238,8 @@ public Set commandClasses() { /** * Obtains command routing schema used by this repository. */ - protected final CommandRouting commandRouting() { - return commandRouting; + private CommandRouting commandRouting() { + return commandRouting.get(); } /** @@ -309,7 +349,7 @@ public void onError(CommandEnvelope cmd, RuntimeException exception) { */ void postEvents(Collection events) { Iterable filteredEvents = eventFilter().filter(events); - EventBus bus = boundedContext().eventBus(); + EventBus bus = context().eventBus(); bus.post(filteredEvents); } @@ -337,7 +377,7 @@ protected EntityLifecycle lifecycleOf(I id) { @Override protected P findOrCreate(I id) { P result = super.findOrCreate(id); - CommandBus commandBus = boundedContext().commandBus(); + CommandBus commandBus = context().commandBus(); result.setCommandBus(commandBus); return result; } diff --git a/server/src/main/java/io/spine/server/projection/ProjectionRepository.java b/server/src/main/java/io/spine/server/projection/ProjectionRepository.java index e117a281a55..fd3c64aa6fd 100644 --- a/server/src/main/java/io/spine/server/projection/ProjectionRepository.java +++ b/server/src/main/java/io/spine/server/projection/ProjectionRepository.java @@ -27,6 +27,7 @@ import com.google.protobuf.Timestamp; import io.spine.annotation.Internal; import io.spine.core.Event; +import io.spine.server.BoundedContext; import io.spine.server.entity.EventDispatchingRepository; import io.spine.server.entity.StorageConverter; import io.spine.server.event.EventFilter; @@ -37,6 +38,8 @@ import io.spine.server.integration.ExternalMessageDispatcher; import io.spine.server.integration.ExternalMessageEnvelope; import io.spine.server.projection.model.ProjectionClass; +import io.spine.server.route.EventRouting; +import io.spine.server.route.StateUpdateRouting; import io.spine.server.stand.Stand; import io.spine.server.storage.RecordStorage; import io.spine.server.storage.StorageFactory; @@ -49,10 +52,9 @@ import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Sets.union; import static io.spine.option.EntityOption.Kind.PROJECTION; import static io.spine.server.projection.model.ProjectionClass.asProjectionClass; -import static io.spine.server.route.EventRoute.byProducerId; -import static io.spine.server.route.EventRoute.ignoreEntityUpdates; import static io.spine.util.Exceptions.newIllegalStateException; /** @@ -69,10 +71,81 @@ public abstract class ProjectionRepository, S e private RecordStorage recordStorage; /** - * Creates a new {@code ProjectionRepository}. + * Initializes the repository. + * + *

Ensures there is at least one event subscriber method (external or domestic) declared + * by the class of the projection. Throws an {@code IllegalStateException} otherwise. + * + *

If projections of this repository are {@linkplain io.spine.core.Subscribe subscribed} to + * entity state updates, a routing for state updates is created and + * {@linkplain #setupStateRouting(StateUpdateRouting) configured}. + * If one of the states of entities cannot be routed during the created schema, + * {@code IllegalStateException} will be thrown. + * + * @param context + * the {@code BoundedContext} of this repository + * @throws IllegalStateException + * if the state routing does not cover one of the entity state types to which + * the entities are subscribed + */ + @Override + @OverridingMethodsMustInvokeSuper + protected void init(BoundedContext context) throws IllegalStateException { + super.init(context); + ensureDispatchesEvents(); + subscribeToSystemEvents(); + } + + @Override + @OverridingMethodsMustInvokeSuper + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + if (projectionClass().subscribesToStates()) { + StateUpdateRouting stateRouting = createStateRouting(); + routing.routeStateUpdates(stateRouting); + } + } + + private void ensureDispatchesEvents() { + boolean noEventSubscriptions = !dispatchesEvents(); + if (noEventSubscriptions) { + boolean noExternalSubscriptions = !dispatchesExternalEvents(); + if (noExternalSubscriptions) { + throw newIllegalStateException( + "Projections of the repository `%s` have neither domestic nor external" + + " event subscriptions.", this); + } + } + } + + /** + * Creates and configures the {@code StateUpdateRouting} used by this repository. + * + *

This method {@linkplain StateUpdateRouting#validate(Set) validates} the created state + * routing for serving all the state classes to which projections subscribe. + * + * @throws IllegalStateException + * if one of the subscribed state classes cannot be served by the created state routing + */ + private StateUpdateRouting createStateRouting() { + ProjectionClass

cls = projectionClass(); + StateUpdateRouting routing = StateUpdateRouting.newInstance(); + setupStateRouting(routing); + routing.validate(union(cls.domesticStates(), cls.externalStates())); + return routing; + } + + /** + * A callback for derived repository classes to customize routing schema for delivering + * updated state to subscribed entities, if the default schema does not satisfy + * the routing needs. + * + * @param routing + * the routing to customize */ - protected ProjectionRepository() { - super(ignoreEntityUpdates(byProducerId())); + @SuppressWarnings("NoopMethodInAbstractClass") // see Javadoc + protected void setupStateRouting(StateUpdateRouting routing) { + // Do nothing by default. } @VisibleForTesting @@ -84,7 +157,7 @@ static Timestamp nullToDefault(@Nullable Timestamp timestamp) { /** Obtains {@link EventStore} from which to get events during catch-up. */ EventStore eventStore() { - return boundedContext() + return context() .eventBus() .eventStore(); } @@ -107,35 +180,10 @@ public P create(I id) { return projection; } - /** - * {@inheritDoc} - * - *

Ensures there is at least one event subscriber method (external or domestic) declared - * by the class of the projection. Throws an {@code IllegalStateException} otherwise. - */ - @Override - public void onRegistered() { - super.onRegistered(); - ensureDispatchesEvents(); - subscribeToSystemEvents(); - } - - private void ensureDispatchesEvents() { - boolean noEventSubscriptions = !dispatchesEvents(); - if (noEventSubscriptions) { - boolean noExternalSubscriptions = !dispatchesExternalEvents(); - if (noExternalSubscriptions) { - throw newIllegalStateException( - "Projections of the repository `%s` have neither domestic nor external " + - "event subscriptions.", this); - } - } - } - private void subscribeToSystemEvents() { ProjectionSystemEventWatcher systemSubscriber = new ProjectionSystemEventWatcher<>(this); - systemSubscriber.registerIn(boundedContext()); + systemSubscriber.registerIn(context()); } @Override @@ -166,7 +214,7 @@ private Set createEventFilters() { * Obtains the {@code Stand} from the {@code BoundedContext} of this repository. */ protected final Stand stand() { - return boundedContext().stand(); + return context().stand(); } /** diff --git a/server/src/main/java/io/spine/server/projection/model/ProjectionClass.java b/server/src/main/java/io/spine/server/projection/model/ProjectionClass.java index 36131427223..22e4828c2b1 100644 --- a/server/src/main/java/io/spine/server/projection/model/ProjectionClass.java +++ b/server/src/main/java/io/spine/server/projection/model/ProjectionClass.java @@ -67,28 +67,28 @@ public static

ProjectionClass

asProjectionClass(Class< } @Override - public Set domesticEvents() { + public final Set domesticEvents() { return delegate.domesticEvents(); } @Override - public Set externalEvents() { - return delegate.externalEvents(); + public final Set domesticStates() { + return delegate.domesticStates(); } @Override - public Collection - subscribersOf(EventClass eventClass, MessageClass originClass) { - return delegate.handlersOf(eventClass, originClass); + public final Set externalEvents() { + return delegate.externalEvents(); } @Override - public Set domesticStates() { - return delegate.domesticStates(); + public final Set externalStates() { + return delegate.externalStates(); } @Override - public Set externalStates() { - return delegate.externalStates(); + public final Collection + subscribersOf(EventClass eventClass, MessageClass originClass) { + return delegate.handlersOf(eventClass, originClass); } } diff --git a/server/src/main/java/io/spine/server/route/ByContext.java b/server/src/main/java/io/spine/server/route/ByContext.java new file mode 100644 index 00000000000..41c04b53f6c --- /dev/null +++ b/server/src/main/java/io/spine/server/route/ByContext.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019, TeamDev. 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.route; + +import io.spine.base.EventMessage; +import io.spine.core.EventContext; + +import java.util.Set; + +/** + * Obtains an event producer ID from the context of the event. + */ +final class ByContext implements EventRoute { + + private static final long serialVersionUID = 0L; + + @Override + public Set apply(EventMessage message, EventContext context) { + @SuppressWarnings("unchecked") I id = (I) context.producer(); + return EventRoute.withId(id); + } +} diff --git a/server/src/main/java/io/spine/server/route/ByFirstMessageField.java b/server/src/main/java/io/spine/server/route/ByFirstMessageField.java new file mode 100644 index 00000000000..960db92883e --- /dev/null +++ b/server/src/main/java/io/spine/server/route/ByFirstMessageField.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019, TeamDev. 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.route; + +import io.spine.base.EventMessage; +import io.spine.core.EventContext; + +import java.util.Set; + +/** + * The route that obtains a producer ID from the first field of the event message. + */ +final class ByFirstMessageField implements EventRoute { + + private static final long serialVersionUID = 0L; + + private final FirstField field; + + ByFirstMessageField(Class idClass) { + this.field = new FirstField<>(idClass); + } + + @Override + public Set apply(EventMessage message, EventContext context) { + I id = field.apply(message, context); + return EventRoute.withId(id); + } +} diff --git a/server/src/main/java/io/spine/server/route/CommandRouting.java b/server/src/main/java/io/spine/server/route/CommandRouting.java index 25f0520946c..bd7244f3abc 100644 --- a/server/src/main/java/io/spine/server/route/CommandRouting.java +++ b/server/src/main/java/io/spine/server/route/CommandRouting.java @@ -51,11 +51,14 @@ private CommandRouting(CommandRoute defaultRoute) { /** * Creates a new command routing. * - * @param the type of entity identifiers returned by new routing + * @param + * the type of entity identifiers returned by new routing + * @param idClass + * the class of target entity identifiers * @return new routing instance */ - public static CommandRouting newInstance() { - CommandRoute defaultRoute = DefaultCommandRoute.newInstance(); + public static CommandRouting newInstance(Class idClass) { + CommandRoute defaultRoute = DefaultCommandRoute.newInstance(idClass); return new CommandRouting<>(defaultRoute); } diff --git a/server/src/main/java/io/spine/server/route/DefaultCommandRoute.java b/server/src/main/java/io/spine/server/route/DefaultCommandRoute.java index 7a2c6d692f5..eed3884c24b 100644 --- a/server/src/main/java/io/spine/server/route/DefaultCommandRoute.java +++ b/server/src/main/java/io/spine/server/route/DefaultCommandRoute.java @@ -20,48 +20,53 @@ package io.spine.server.route; -import com.google.protobuf.Message; import io.spine.base.CommandMessage; import io.spine.core.CommandContext; import io.spine.protobuf.MessageFieldException; -import java.util.Optional; +import static com.google.common.base.Preconditions.checkNotNull; /** * Obtains an ID of a command target entity from the first field of the command message. * * @param the type of target entity IDs */ -public class DefaultCommandRoute extends FieldAtIndex - implements CommandRoute { +public final class DefaultCommandRoute implements CommandRoute { private static final long serialVersionUID = 0L; - private static final int ID_FIELD_INDEX = 0; - private DefaultCommandRoute() { - super(ID_FIELD_INDEX); - } + private final FirstField field; - /** Creates a new instance. */ - public static DefaultCommandRoute newInstance() { - return new DefaultCommandRoute<>(); + private DefaultCommandRoute(Class cls) { + this.field = new FirstField<>(cls); } /** - * Tries to obtain a target ID from the passed command message. + * Creates a new instance. * - * @param commandMessage the message to get ID from - * @return an {@link Optional} of the ID or {@code Optional.empty()} - * if {@link DefaultCommandRoute#apply(Message, Message)} throws an exception - * if the command is not for an entity + * @param idClass + * the class of identifiers used for the routing + */ + public static DefaultCommandRoute newInstance(Class idClass) { + checkNotNull(idClass); + return new DefaultCommandRoute<>(idClass); + } + + @Override + public I apply(CommandMessage message, CommandContext ignored) throws MessageFieldException { + checkNotNull(message); + I result = field.apply(message, ignored); + return result; + } + + /** + * Verifies of the passed command message potentially has a field with an entity ID. */ - public static Optional asOptional(CommandMessage commandMessage) { - try { - DefaultCommandRoute function = newInstance(); - I id = function.apply(commandMessage, CommandContext.getDefaultInstance()); - return Optional.of(id); - } catch (MessageFieldException | ClassCastException ignored) { - return Optional.empty(); - } + public static boolean exists(CommandMessage commandMessage) { + boolean hasAtLeastOneField = + !commandMessage.getDescriptorForType() + .getFields() + .isEmpty(); + return hasAtLeastOneField; } } diff --git a/server/src/main/java/io/spine/server/route/EventProducers.java b/server/src/main/java/io/spine/server/route/EventProducers.java deleted file mode 100644 index 88b90e36f9f..00000000000 --- a/server/src/main/java/io/spine/server/route/EventProducers.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2019, TeamDev. 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.route; - -import com.google.common.collect.ImmutableSet; -import io.spine.base.EventMessage; -import io.spine.core.EventContext; - -import java.util.Set; - -/** - * Provides default {@link EventRoute}s for obtaining a producer ID from an event. - */ -final class EventProducers { - - /** Prevents instantiation of this utility class. */ - private EventProducers() { - } - - /** - * Obtains an event producer ID from the context of the event. - */ - static final class FromContext implements EventRoute { - - private static final long serialVersionUID = 0L; - - @Override - public Set apply(EventMessage message, EventContext context) { - @SuppressWarnings("unchecked") // The route creator is responsible for the type check. - I id = (I) context.producer(); - return ImmutableSet.of(id); - } - - @Override - public String toString() { - return "EventProducers.fromContext()"; - } - } - - /** - * The route that obtains a producer ID from the first field of the event message. - */ - static final class FromFirstMessageField implements EventRoute { - - private static final long serialVersionUID = 0L; - - private final FromEventMessage func = FromEventMessage.fieldAt(0); - - @Override - public Set apply(EventMessage message, EventContext context) { - I id = func.apply(message, context); - return ImmutableSet.of(id); - } - - @Override - public String toString() { - return "EventProducers.fromFirstMessageField()"; - } - } - - /** - * Obtains an event producer ID from a field of an event message. - */ - static final class FromEventMessage extends FieldAtIndex { - - private static final long serialVersionUID = 0L; - - private FromEventMessage(int idIndex) { - super(idIndex); - } - - /** - * Creates a new instance. - * - * @param index a zero-based index of an ID field in this type of messages - */ - static FromEventMessage fieldAt(int index) { - return new FromEventMessage<>(index); - } - } -} diff --git a/server/src/main/java/io/spine/server/route/EventRoute.java b/server/src/main/java/io/spine/server/route/EventRoute.java index 8b5fa27c1ec..093e6bdd1e6 100644 --- a/server/src/main/java/io/spine/server/route/EventRoute.java +++ b/server/src/main/java/io/spine/server/route/EventRoute.java @@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableSet; import io.spine.base.EventMessage; import io.spine.core.EventContext; -import io.spine.system.server.event.EntityStateChanged; import java.util.Set; @@ -46,36 +45,21 @@ public interface EventRoute extends Multicast EventRoute byProducerId() { - return new EventProducers.FromContext<>(); + return new ByContext<>(); } /** * Creates an event route that obtains event producer ID from an {@code EventContext} and * returns it as a sole element of the the immutable set. * - * @param the type of the IDs for which the event would be routed - * @return new route instance - */ - static EventRoute byFirstMessageField() { - return new EventProducers.FromFirstMessageField<>(); - } - - /** - * Creates an event route that ignores {@link EntityStateChanged} events and delegates all - * the other event routing to the given instance. - * - * @param forOthers - * the route for all the other events * @param - * the type of the event target IDs + * the type of the IDs of entities to which the event would be routed + * @param idClass + * the class of identifiers * @return new route instance */ - static EventRoute - ignoreEntityUpdates(EventRoute forOthers) { - checkNotNull(forOthers); - return (message, context) -> message instanceof EntityStateChanged - ? noTargets() - : forOthers.apply(message, context); + static EventRoute byFirstMessageField(Class idClass) { + return new ByFirstMessageField<>(idClass); } /** diff --git a/server/src/main/java/io/spine/server/route/EventRouting.java b/server/src/main/java/io/spine/server/route/EventRouting.java index 43570bc0e98..9277a385d92 100644 --- a/server/src/main/java/io/spine/server/route/EventRouting.java +++ b/server/src/main/java/io/spine/server/route/EventRouting.java @@ -72,6 +72,16 @@ public static EventRouting withDefault(EventRoute defaul return new EventRouting<>(defaultRoute); } + /** + * Creates a new event routing with the default one by event producer ID. + * + * @see #withDefault(EventRoute) + * @see EventRoute#byProducerId() + */ + public static EventRouting withDefaultByProducerId() { + return withDefault(EventRoute.byProducerId()); + } + /** * {@inheritDoc} * @@ -144,10 +154,6 @@ EventRouting route(Class eventType, EventRoute via) /** * Sets a custom routing schema for entity state updates. * - *

Setting a routing for state updates is equivalent to setting a route for events of type - * {@link EntityStateChanged io.spine.system.server.event.EntityStateChanged}. - * It is illegal to do both things simultaneously. - * * @param routing * the routing schema for entity state updates * @return {@code this} to allow chained calls when configuring the routing @@ -155,7 +161,7 @@ EventRouting route(Class eventType, EventRoute via) * if a route for {@link EntityStateChanged} is already set */ @CanIgnoreReturnValue - public EventRouting routeEntityStateUpdates(StateUpdateRouting routing) { + public EventRouting routeStateUpdates(StateUpdateRouting routing) { checkNotNull(routing); return route(EntityStateChanged.class, routing.eventRoute()); } diff --git a/server/src/main/java/io/spine/server/route/FieldAtIndex.java b/server/src/main/java/io/spine/server/route/FieldAtIndex.java deleted file mode 100644 index a93012bbde0..00000000000 --- a/server/src/main/java/io/spine/server/route/FieldAtIndex.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2019, TeamDev. 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.route; - -import com.google.protobuf.Message; -import io.spine.protobuf.MessageField; -import io.spine.protobuf.MessageFieldException; - -/** - * Obtains an entity ID based on an event/command message. - * - * @param the type of entity IDs - * @param the type of messages to get IDs from - * @param the type of the message context - */ -abstract class FieldAtIndex implements Unicast { - - private static final long serialVersionUID = 0L; - private final EntityIdField idField; - - /** - * Creates a new instance. - * - * @param idIndex a zero-based index of an ID field in this type of messages - */ - FieldAtIndex(int idIndex) { - this.idField = new EntityIdField(idIndex); - } - - /** - * Obtains the ID from the message field at the configured index. - * - *

Casts the value obtained from the field to the type of the generic - * parameter {@code }. - * - * @throws MessageFieldException if there is no field with required index - * @throws ClassCastException if the field type is not of the required type - */ - @Override - public I apply(M message, C ignored) throws MessageFieldException { - @SuppressWarnings("unchecked") // we expect that the field is of this type - I id = (I) idField.getValue(message); - return id; - } - - /** Accessor object for entity ID fields. */ - private static class EntityIdField extends MessageField { - - private static final long serialVersionUID = 0L; - - private EntityIdField(int index) { - super(index); - } - - @Override - protected MessageFieldException createUnavailableFieldException(Message message) { - return new MessageFieldException(message, "There's no field with index %d", getIndex()); - } - - @Override - protected boolean isFieldAvailable(Message message) { - boolean result = MessageField.getFieldCount(message) > getIndex(); - return result; - } - } -} diff --git a/server/src/main/java/io/spine/server/route/FirstField.java b/server/src/main/java/io/spine/server/route/FirstField.java new file mode 100644 index 00000000000..4af71c194bc --- /dev/null +++ b/server/src/main/java/io/spine/server/route/FirstField.java @@ -0,0 +1,113 @@ +/* + * Copyright 2019, TeamDev. 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.route; + +import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Message; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.util.Exceptions.newIllegalStateException; + +/** + * Routes messages to a single target, which ID is the same as the first field of + * the routed message. + * + *

It is expected that the types of the first field and the identifier are the same. + * + * @param + * the type of the identifiers + * @param + * the common supertype for messages + * @param + * the type of contexts of the messages + */ +final class FirstField implements Unicast { + + private static final long serialVersionUID = 0L; + private final Class idClass; + + FirstField(Class idClass) { + this.idClass = checkNotNull(idClass); + } + + @Override + public I apply(M message, C context) { + checkNotNull(message); + FieldDescriptor field = fieldIn(message); + I result = getValue(field, message); + return result; + } + + /** + * Obtains a descriptor of the first field of the passed. + * + * @throws IllegalStateException + * if the passed message does not declare fields, or + * the field is a repeated field or a map + */ + private FieldDescriptor fieldIn(M message) { + Descriptor type = message.getDescriptorForType(); + List fields = type.getFields(); + if (fields.isEmpty()) { + throw error("Cannot use the type `%s` for routing: it does not declare any field.", + type.getFullName()); + } + FieldDescriptor field = fields.get(0); + if (field.isMapField()) { + throw error("The field `%s` is a map and cannot be used for routing.", + field.getFullName()); + } + if (field.isRepeated()) { + throw error("The field `%s` is repeated and cannot be used for routing.", + field.getFullName()); + } + return field; + } + + private IllegalStateException error(String messageFormat, String firstArg) { + throw newIllegalStateException( + messageFormat + " Please declare a field with the type `%s`.", + firstArg, + idClass.getCanonicalName()); + } + + /** + * Obtains the value of first field making sure the value is of the expected type. + */ + private I getValue(FieldDescriptor field, M message) { + Object value = message.getField(field); + Class valueClass = value.getClass(); + if (!idClass.isAssignableFrom(valueClass)) { + throw newIllegalStateException( + "The field `%s` has the type `%s` which is not assignable" + + " from the expected ID type `%s`.", + field.getFullName(), + valueClass.getName(), + idClass.getName() + ); + } + I result = idClass.cast(value); + return result; + } +} diff --git a/server/src/main/java/io/spine/server/route/StateUpdateRouting.java b/server/src/main/java/io/spine/server/route/StateUpdateRouting.java index c2d1af81c67..99393baeb30 100644 --- a/server/src/main/java/io/spine/server/route/StateUpdateRouting.java +++ b/server/src/main/java/io/spine/server/route/StateUpdateRouting.java @@ -24,10 +24,12 @@ import com.google.protobuf.Message; import io.spine.core.EventContext; import io.spine.protobuf.AnyPacker; +import io.spine.server.entity.model.StateClass; import io.spine.system.server.event.EntityStateChanged; import java.util.Set; +import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.server.route.EventRoute.noTargets; /** @@ -48,7 +50,7 @@ public class StateUpdateRouting private static final long serialVersionUID = 0L; private StateUpdateRouting() { - super((message, context) -> noTargets()); + super(defaultStateRoute()); } /** @@ -104,4 +106,21 @@ EventRoute eventRoute() { return apply(state, context); }; } + + private static Route> defaultStateRoute() { + return (message, context) -> noTargets(); + } + + /** + * Validates routing schema for types of state messages. + * + * @param stateClasses + * the set of classes that this routing is expected to serve + * @throws IllegalStateException + * if one of the state type cannot be dispatched by the current schema configuration + */ + public void validate(Set stateClasses) throws IllegalStateException { + checkNotNull(stateClasses); + //TODO:2019-05-15:alexander.yevsyukov: See https://github.com/SpineEventEngine/core-java/issues/1067 + } } diff --git a/server/src/main/java/io/spine/server/storage/RecordStorage.java b/server/src/main/java/io/spine/server/storage/RecordStorage.java index 029a6b2274a..ddf917be76f 100644 --- a/server/src/main/java/io/spine/server/storage/RecordStorage.java +++ b/server/src/main/java/io/spine/server/storage/RecordStorage.java @@ -199,7 +199,7 @@ public void writeLifecycleFlags(I id, LifecycleFlags flags) { Optional optional = read(request); if (optional.isPresent()) { EntityRecord record = optional.get(); - EntityRecord updated = record.toVBuilder() + EntityRecord updated = record.toBuilder() .setLifecycleFlags(flags) .build(); write(id, updated); diff --git a/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java b/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java index 360a1a0cdec..4b30f6e1228 100644 --- a/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java +++ b/server/src/main/java/io/spine/server/storage/memory/InMemoryStorageFactory.java @@ -73,7 +73,7 @@ InMemoryStorageFactory newInstance(BoundedContextName context, boolean multitena InMemoryStorageFactory newInstance(String boundedContextName, boolean multitenant) { checkValid(boundedContextName); BoundedContextName name = BoundedContextName - .vBuilder() + .newBuilder() .setValue(boundedContextName) .build(); return newInstance(name, multitenant); diff --git a/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java b/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java index 1bf91b66081..da467a56697 100644 --- a/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java +++ b/server/src/main/java/io/spine/server/storage/memory/TenantRecords.java @@ -140,7 +140,7 @@ EntityRecord findAndApplyFieldMask(I targetId, FieldMask fieldMask) { EntityRecord record = recordWithColumns.getRecord(); Any recordState = record.getState(); Any maskedState = new FieldMaskApplier(fieldMask).maskAny(recordState); - EntityRecord maskedRecord = record.toVBuilder() + EntityRecord maskedRecord = record.toBuilder() .setState(maskedState) .build(); return maskedRecord; diff --git a/server/src/main/java/io/spine/server/type/EventEnvelope.java b/server/src/main/java/io/spine/server/type/EventEnvelope.java index 9f8557ddd25..deac48abe66 100644 --- a/server/src/main/java/io/spine/server/type/EventEnvelope.java +++ b/server/src/main/java/io/spine/server/type/EventEnvelope.java @@ -200,11 +200,11 @@ private Enrichment enrichment() { private EventEnvelope withEnrichment(Enrichment enrichment) { EventContext context = this.context() - .toVBuilder() + .toBuilder() .setEnrichment(enrichment) .build(); Event enrichedCopy = - outerObject().toVBuilder() + outerObject().toBuilder() .setContext(context) .build(); return of(enrichedCopy); diff --git a/server/src/main/java/io/spine/system/server/CommandLifecycleAggregate.java b/server/src/main/java/io/spine/system/server/CommandLifecycleAggregate.java index 21ecf073d54..8e7b9186837 100644 --- a/server/src/main/java/io/spine/system/server/CommandLifecycleAggregate.java +++ b/server/src/main/java/io/spine/system/server/CommandLifecycleAggregate.java @@ -57,7 +57,7 @@ final class CommandLifecycleAggregate @Assign CommandScheduled handle(ScheduleCommand command) { - return CommandScheduled.vBuilder() + return CommandScheduled.newBuilder() .setId(command.getId()) .setSchedule(command.getSchedule()) .build(); @@ -65,7 +65,7 @@ CommandScheduled handle(ScheduleCommand command) { @Assign TargetAssignedToCommand handle(AssignTargetToCommand event) { - return TargetAssignedToCommand.vBuilder() + return TargetAssignedToCommand.newBuilder() .setId(event.getId()) .setTarget(event.getTarget()) .build(); @@ -81,7 +81,7 @@ TargetAssignedToCommand handle(AssignTargetToCommand event) { private void on(CommandReceived event) { ensureId(); CommandTimeline status = CommandTimeline - .vBuilder() + .newBuilder() .setWhenReceived(currentTime()) .build(); builder().setCommand(event.getPayload()) @@ -140,7 +140,7 @@ private void on(TargetAssignedToCommand event) { CommandLifecycleVBuilder builder = builder(); CommandTimeline status = builder.getStatus() - .toVBuilder() + .toBuilder() .setWhenTargetAssigned(currentTime()) .build(); builder.setStatus(status) @@ -167,7 +167,7 @@ private void on(@SuppressWarnings("unused") CommandHandled event) { private void on(CommandErrored event) { ensureId(); Status status = Status - .vBuilder() + .newBuilder() .setError(event.getError()) .build(); setStatus(status); @@ -182,7 +182,7 @@ private void on(CommandErrored event) { private void on(CommandRejected event) { ensureId(); Status status = Status - .vBuilder() + .newBuilder() .setRejection(event.getRejectionEvent()) .build(); setStatus(status); @@ -192,12 +192,12 @@ private void on(CommandRejected event) { private void on(CommandTransformed event) { ensureId(); Substituted substituted = Substituted - .vBuilder() + .newBuilder() .setCommand(event.getId()) .build(); CommandTimeline newStatus = state().getStatus() - .toVBuilder() + .toBuilder() .setSubstituted(substituted) .build(); builder().setStatus(newStatus); @@ -207,16 +207,16 @@ private void on(CommandTransformed event) { private void on(CommandSplit event) { ensureId(); Sequence sequence = Sequence - .vBuilder() + .newBuilder() .addAllItem(event.getProducedList()) .build(); Substituted substituted = Substituted - .vBuilder() + .newBuilder() .setSequence(sequence) .build(); CommandTimeline newStatus = state().getStatus() - .toVBuilder() + .toBuilder() .setSubstituted(substituted) .build(); builder().setStatus(newStatus); @@ -226,19 +226,19 @@ private Command updateSchedule(Schedule schedule) { Command command = builder().getCommand(); CommandContext updatedContext = command.context() - .toVBuilder() + .toBuilder() .setSchedule(schedule) .build(); Command updatedCommand = - command.toVBuilder() + command.toBuilder() .setContext(updatedContext) .build(); return updatedCommand; } - private CommandTimelineVBuilder statusBuilder() { + private CommandTimeline.Builder statusBuilder() { return builder().getStatus() - .toVBuilder(); + .toBuilder(); } private void setStatus(Status status) { diff --git a/server/src/main/java/io/spine/system/server/EntityHistoryAggregate.java b/server/src/main/java/io/spine/system/server/EntityHistoryAggregate.java index 09875e5f05b..b1ce42e95f6 100644 --- a/server/src/main/java/io/spine/system/server/EntityHistoryAggregate.java +++ b/server/src/main/java/io/spine/system/server/EntityHistoryAggregate.java @@ -28,7 +28,6 @@ import io.spine.server.aggregate.Apply; import io.spine.server.command.Assign; import io.spine.server.entity.LifecycleFlags; -import io.spine.server.entity.LifecycleFlagsVBuilder; import io.spine.system.server.command.DispatchCommandToHandler; import io.spine.system.server.command.DispatchEventToReactor; import io.spine.system.server.command.DispatchEventToSubscriber; @@ -195,25 +194,25 @@ private void checkNotDuplicate(Command command) throws CannotDispatchCommandTwic } } - private void updateLifecycleFlags(UnaryOperator mutation) { + private void updateLifecycleFlags(UnaryOperator mutation) { LifecycleHistory oldLifecycleHistory = builder().getLifecycle(); - LifecycleFlagsVBuilder flagsBuilder = + LifecycleFlags.Builder flagsBuilder = oldLifecycleHistory.getLifecycleFlags() - .toVBuilder(); + .toBuilder(); LifecycleFlags newFlags = mutation.apply(flagsBuilder) .build(); LifecycleHistory newLifecycleHistory = - oldLifecycleHistory.toVBuilder() + oldLifecycleHistory.toBuilder() .setLifecycleFlags(newFlags) .build(); builder().setLifecycle(newLifecycleHistory); } - private void updateLifecycleTimestamp(UnaryOperator mutation) { + private void updateLifecycleTimestamp(UnaryOperator mutation) { EntityHistoryVBuilder builder = builder(); - LifecycleHistoryVBuilder history = + LifecycleHistory.Builder history = builder.getLifecycle() - .toVBuilder(); + .toBuilder(); LifecycleHistory newHistory = mutation.apply(history) .build(); @@ -236,11 +235,11 @@ private void updateLastCommandTime(Timestamp newCommand) { } } - private void updateDispatchingHistory(UnaryOperator mutation) { + private void updateDispatchingHistory(UnaryOperator mutation) { EntityHistoryVBuilder builder = builder(); - DispatchingHistoryVBuilder history = + DispatchingHistory.Builder history = builder.getDispatching() - .toVBuilder(); + .toBuilder(); DispatchingHistory newHistory = mutation.apply(history) .build(); diff --git a/server/src/main/java/io/spine/system/server/MirrorProjection.java b/server/src/main/java/io/spine/system/server/MirrorProjection.java index adf1f7e0e15..fcb9a4d8daa 100644 --- a/server/src/main/java/io/spine/system/server/MirrorProjection.java +++ b/server/src/main/java/io/spine/system/server/MirrorProjection.java @@ -63,7 +63,7 @@ * subscriber method is an event used by the framework to bind the method to the event type. * The content of the event, in those cases, is irrelevant. */ -public final class MirrorProjection extends Projection { +final class MirrorProjection extends Projection { private static final String TYPE_COLUMN_NAME = "aggregate_type"; private static final String TYPE_COLUMN_QUERY_NAME = "aggregateType"; @@ -84,7 +84,7 @@ void on(EntityArchived event) { MirrorVBuilder builder = builder(); LifecycleFlags flags = builder .getLifecycle() - .toVBuilder() + .toBuilder() .setArchived(true) .build(); builder.setId(id()) @@ -98,7 +98,7 @@ void on(EntityDeleted event) { MirrorVBuilder builder = builder(); LifecycleFlags flags = builder .getLifecycle() - .toVBuilder() + .toBuilder() .setDeleted(true) .build(); builder.setId(id()) @@ -112,7 +112,7 @@ void on(EntityUnarchived event) { MirrorVBuilder builder = builder(); LifecycleFlags flags = builder .getLifecycle() - .toVBuilder() + .toBuilder() .setArchived(false) .build(); builder.setId(id()) @@ -126,7 +126,7 @@ void on(EntityRestored event) { MirrorVBuilder builder = builder(); LifecycleFlags flags = builder .getLifecycle() - .toVBuilder() + .toBuilder() .setDeleted(false) .build(); builder.setId(id()) @@ -148,7 +148,7 @@ static TargetFilters buildFilters(Target target) { TargetFilters filters = target.getFilters(); CompositeFilter typeFilter = all(eq(TYPE_COLUMN_QUERY_NAME, target.getType())); TargetFilters appendedFilters = filters - .toVBuilder() + .toBuilder() .setIdFilter(idFilter) .addFilter(typeFilter) .build(); diff --git a/server/src/main/java/io/spine/system/server/MirrorRepository.java b/server/src/main/java/io/spine/system/server/MirrorRepository.java index b64676c4a74..a7bb32f8632 100644 --- a/server/src/main/java/io/spine/system/server/MirrorRepository.java +++ b/server/src/main/java/io/spine/system/server/MirrorRepository.java @@ -35,6 +35,7 @@ import io.spine.option.EntityOption; import io.spine.option.EntityOption.Kind; import io.spine.server.entity.EntityVisibility; +import io.spine.server.route.EventRouting; import io.spine.system.server.event.EntityArchived; import io.spine.system.server.event.EntityDeleted; import io.spine.system.server.event.EntityRestored; @@ -71,30 +72,24 @@ final class MirrorRepository extends SystemProjectionRepository { - private static final FieldMask AGGREGATE_STATE_WITH_VERSION = - fromFieldNumbers(Mirror.class, - ID_FIELD_NUMBER, STATE_FIELD_NUMBER, VERSION_FIELD_NUMBER); - private static final Logger log = Logging.get(MirrorRepository.class); + private static final FieldMask AGGREGATE_STATE_WITH_VERSION = fromFieldNumbers( + Mirror.class, ID_FIELD_NUMBER, STATE_FIELD_NUMBER, VERSION_FIELD_NUMBER + ); @Override - public void onRegistered() { - super.onRegistered(); - prepareRouting(); - } - - private void prepareRouting() { - eventRouting() - .route(EntityStateChanged.class, - (message, context) -> targetsFrom(message.getId())) - .route(EntityArchived.class, - (message, context) -> targetsFrom(message.getId())) - .route(EntityDeleted.class, - (message, context) -> targetsFrom(message.getId())) - .route(EntityUnarchived.class, - (message, context) -> targetsFrom(message.getId())) - .route(EntityRestored.class, - (message, context) -> targetsFrom(message.getId())); + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(EntityStateChanged.class, + (message, context) -> targetsFrom(message.getId())) + .route(EntityArchived.class, + (message, context) -> targetsFrom(message.getId())) + .route(EntityDeleted.class, + (message, context) -> targetsFrom(message.getId())) + .route(EntityUnarchived.class, + (message, context) -> targetsFrom(message.getId())) + .route(EntityRestored.class, + (message, context) -> targetsFrom(message.getId())); } private static Set targetsFrom(EntityHistoryId historyId) { diff --git a/server/src/main/java/io/spine/system/server/ScheduledCommand.java b/server/src/main/java/io/spine/system/server/ScheduledCommand.java index 70a07dca14f..a62004e57a2 100644 --- a/server/src/main/java/io/spine/system/server/ScheduledCommand.java +++ b/server/src/main/java/io/spine/system/server/ScheduledCommand.java @@ -51,11 +51,11 @@ void on(CommandScheduled event, EventContext context) { private static Command withSchedule(Command source, CommandContext.Schedule schedule) { CommandContext updatedContext = source.context() - .toVBuilder() + .toBuilder() .setSchedule(schedule) .build(); Command updatedCommand = - source.toVBuilder() + source.toBuilder() .setContext(updatedContext) .build(); return updatedCommand; diff --git a/server/src/main/java/io/spine/system/server/ScheduledCommandRepository.java b/server/src/main/java/io/spine/system/server/ScheduledCommandRepository.java index 5ca848971ea..d52f8f6477b 100644 --- a/server/src/main/java/io/spine/system/server/ScheduledCommandRepository.java +++ b/server/src/main/java/io/spine/system/server/ScheduledCommandRepository.java @@ -36,9 +36,9 @@ final class ScheduledCommandRepository extends SystemProjectionRepository { - ScheduledCommandRepository() { - super(); - EventRouting routing = eventRouting(); + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); routing.route(CommandDispatched.class, (message, context) -> routeToExisting(message)); } diff --git a/server/src/main/java/io/spine/system/server/SystemEventFactory.java b/server/src/main/java/io/spine/system/server/SystemEventFactory.java index dcf89a7e817..678d232fe90 100644 --- a/server/src/main/java/io/spine/system/server/SystemEventFactory.java +++ b/server/src/main/java/io/spine/system/server/SystemEventFactory.java @@ -54,19 +54,19 @@ private SystemEventFactory(Message aggregateId, boolean multitenant) { * @return new instance of {@code SystemEventFactory} */ static SystemEventFactory forMessage(EventMessage message, boolean multitenant) { - Message aggregateId = getAggregateId(message); + Message aggregateId = aggregateIdFrom(message); return new SystemEventFactory(aggregateId, multitenant); } - private static Message getAggregateId(EventMessage systemEvent) { + private static Message aggregateIdFrom(EventMessage systemEvent) { Set routingOut = - EventRoute.byFirstMessageField() + EventRoute.byFirstMessageField(Object.class) .apply(systemEvent, EventContext.getDefaultInstance()); checkArgument(routingOut.size() == 1, "System event message must have aggregate ID in the first field."); Object id = routingOut.iterator() .next(); - checkArgument(id instanceof Message, "System aggregate ID must be a Message"); + checkArgument(id instanceof Message, "System aggregate ID must be a `Message`."); return (Message) id; } diff --git a/server/src/main/java/io/spine/system/server/SystemProjectionRepository.java b/server/src/main/java/io/spine/system/server/SystemProjectionRepository.java index f4f43692f48..d106aaca581 100644 --- a/server/src/main/java/io/spine/system/server/SystemProjectionRepository.java +++ b/server/src/main/java/io/spine/system/server/SystemProjectionRepository.java @@ -27,12 +27,13 @@ import io.spine.server.type.EventEnvelope; /** - * A repository for projections in a system bounded context. + * A repository for projections in a System Context. * *

Unlike an arbitrary {@link ProjectionRepository}, a {@code SystemProjectionRepository} * dispatches the events directly to the target projections. */ -public class SystemProjectionRepository, S extends Message> +public +abstract class SystemProjectionRepository, S extends Message> extends ProjectionRepository { /** diff --git a/server/src/main/java/io/spine/system/server/SystemRepository.java b/server/src/main/java/io/spine/system/server/SystemRepository.java index fb266ff58d6..c9c2df87468 100644 --- a/server/src/main/java/io/spine/system/server/SystemRepository.java +++ b/server/src/main/java/io/spine/system/server/SystemRepository.java @@ -24,6 +24,7 @@ import io.spine.server.aggregate.AggregateRepository; import io.spine.server.entity.EventFilter; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; /** * Abstract base for system aggregate repositories. @@ -33,9 +34,10 @@ abstract class SystemRepository> extends AggregateRepository { - SystemRepository() { - super(); - eventImportRouting().replaceDefault(EventRoute.byFirstMessageField()); + @Override + protected void setupImportRouting(EventRouting routing) { + super.setupImportRouting(routing); + routing.replaceDefault(EventRoute.byFirstMessageField(idClass())); } @Override diff --git a/server/src/main/proto/spine/server/entity/standard_rejections.proto b/server/src/main/proto/spine/server/entity/standard_rejections.proto index a57d3603d9b..39f829902d9 100644 --- a/server/src/main/proto/spine/server/entity/standard_rejections.proto +++ b/server/src/main/proto/spine/server/entity/standard_rejections.proto @@ -30,6 +30,8 @@ option java_package = "io.spine.server.entity.rejection"; import "google/protobuf/any.proto"; +option (every_is).java_type = "StandardRejection"; + // The rejection thrown when a request for modification of an entity cannot be satisfied // because the entity is marked as `archived`. message CannotModifyArchivedEntity { diff --git a/server/src/test/java/io/spine/server/BoundedContextTest.java b/server/src/test/java/io/spine/server/BoundedContextTest.java index 990f9ac6835..f33a1bd1a5b 100644 --- a/server/src/test/java/io/spine/server/BoundedContextTest.java +++ b/server/src/test/java/io/spine/server/BoundedContextTest.java @@ -72,6 +72,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.spine.server.event.given.EventStoreTestEnv.eventStore; +import static io.spine.testing.TestValues.randomString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -101,32 +102,33 @@ class BoundedContextTest { private final TestEventSubscriber subscriber = new TestEventSubscriber(); - private BoundedContext boundedContext; + private BoundedContext context; private boolean handlersRegistered = false; @BeforeEach void setUp() { ModelTests.dropAllModels(); - boundedContext = BoundedContext.newBuilder() - .setMultitenant(true) - .build(); + context = BoundedContext + .newBuilder() + .setMultitenant(true) + .build(); } @AfterEach void tearDown() throws Exception { if (handlersRegistered) { - boundedContext.eventBus() - .unregister(subscriber); + context.eventBus() + .unregister(subscriber); } - boundedContext.close(); + context.close(); } /** Registers all test repositories, handlers etc. */ private void registerAll() { - boundedContext.register(DefaultRepository.of(ProjectAggregate.class)); - boundedContext.eventBus() - .register(subscriber); + context.register(DefaultRepository.of(ProjectAggregate.class)); + context.eventBus() + .register(subscriber); handlersRegistered = true; } @@ -137,27 +139,28 @@ class Return { @Test @DisplayName("EventBus") void eventBus() { - assertNotNull(boundedContext.eventBus()); + assertNotNull(context.eventBus()); } @Test @DisplayName("IntegrationBus") void integrationBus() { - assertNotNull(boundedContext.integrationBus()); + assertNotNull(context.integrationBus()); } @Test @DisplayName("CommandDispatcher") void commandDispatcher() { - assertNotNull(boundedContext.commandBus()); + assertNotNull(context.commandBus()); } @Test @DisplayName("multitenancy state") void ifSetMultitenant() { - BoundedContext bc = BoundedContext.newBuilder() - .setMultitenant(true) - .build(); + BoundedContext bc = BoundedContext + .newBuilder() + .setMultitenant(true) + .build(); assertTrue(bc.isMultitenant()); } } @@ -185,15 +188,15 @@ void projectionRepository() { } > void registerAndAssertRepository(Class cls) { - boundedContext.register(DefaultRepository.of(cls)); - assertTrue(boundedContext.hasEntitiesOfType(cls)); + context.register(DefaultRepository.of(cls)); + assertTrue(context.hasEntitiesOfType(cls)); } @Test @DisplayName("DefaultRepository via passed entity class") void entityClass() { - boundedContext.register(ProjectAggregate.class); - assertTrue(boundedContext.hasEntitiesOfType(ProjectAggregate.class)); + context.register(ProjectAggregate.class); + assertTrue(context.hasEntitiesOfType(ProjectAggregate.class)); } } @@ -208,15 +211,15 @@ class ByEntityStateClass { @Test @DisplayName("visible entities") void visible() { - boundedContext.register(ProjectAggregate.class); - assertTrue(boundedContext.hasEntitiesWithState(Project.class)); + context.register(ProjectAggregate.class); + assertTrue(context.hasEntitiesWithState(Project.class)); } @Test @DisplayName("invisible entities") void invisible() { - boundedContext.register(new SecretProjectRepository()); - assertTrue(boundedContext.hasEntitiesWithState(SecretProject.class)); + context.register(new SecretProjectRepository()); + assertTrue(context.hasEntitiesWithState(SecretProject.class)); } } @@ -227,16 +230,16 @@ class ByEntityClass { @Test @DisplayName("visible entities") void visible() { - boundedContext.register(ProjectAggregate.class); - assertTrue(boundedContext.hasEntitiesOfType(ProjectAggregate.class)); + context.register(ProjectAggregate.class); + assertTrue(context.hasEntitiesOfType(ProjectAggregate.class)); } @Test @DisplayName("invisible entities") void invisible() { // Process Managers are invisible by default. - boundedContext.register(ProjectProcessManager.class); - assertTrue(boundedContext.hasEntitiesOfType(ProjectProcessManager.class)); + context.register(ProjectProcessManager.class); + assertTrue(context.hasEntitiesOfType(ProjectProcessManager.class)); } } } @@ -271,9 +274,10 @@ void registerStandAsEventDispatcher() { @ParameterizedTest @MethodSource("sameStateRepositories") @DisplayName("not allow two entity repositories with entities of same state") - void throwOnSameEntityState(Repository firstRepo, Repository secondRepo) { - boundedContext.register(firstRepo); - assertThrows(IllegalStateException.class, () -> boundedContext.register(secondRepo)); + void throwOnSameEntityState(Repository firstRepo, + Repository secondRepo) { + context.register(firstRepo); + assertThrows(IllegalStateException.class, () -> context.register(secondRepo)); } /** @@ -306,9 +310,9 @@ private static Stream sameStateRepositories() { Set>> cartesianProduct = Sets.cartesianProduct(repositories, sameStateRepositories); - Stream result = cartesianProduct.stream() - .map(repos -> Arguments.of(repos.get(0), - repos.get(1))); + Stream result = + cartesianProduct.stream() + .map(repos -> Arguments.of(repos.get(0), repos.get(1))); return result; } @@ -317,7 +321,7 @@ private static Stream sameStateRepositories() { void setStorageOnRegister() { Repository repository = DefaultRepository.of(ProjectAggregate.class); - boundedContext.register(repository); + context.register(repository); assertTrue(repository.isStorageAssigned()); } @@ -326,21 +330,22 @@ void setStorageOnRegister() { void notOverrideStorage() { ProjectAggregateRepository repository = new ProjectAggregateRepository(); Repository spy = spy(repository); - boundedContext.register(repository); + context.register(repository); verify(spy, never()).initStorage(any(StorageFactory.class)); } @Test - @DisplayName("set storage factory for EventBus") + @DisplayName("allow custom EventBus") void setEventBusStorageFactory() { - BoundedContext bc = BoundedContext.newBuilder() - .setEventBus(EventBus.newBuilder()) - .build(); + BoundedContext bc = BoundedContext + .newBuilder() + .setEventBus(EventBus.newBuilder()) + .build(); assertNotNull(bc.eventBus()); } @Test - @DisplayName("not set storage factory for EventBus if EventStore is set") + @DisplayName("not overwrite EventStore if already set in EventBus.Builder") void useEventStoreIfSet() { EventStore eventStore = eventStore(); BoundedContext bc = BoundedContext.newBuilder() @@ -438,12 +443,12 @@ private BoundedContext singleTenant() { @Test @DisplayName("obtain entity types by visibility") void getEntityTypesByVisibility() { - assertThat(boundedContext.stateTypes(EntityOption.Visibility.FULL)) + assertThat(context.stateTypes(EntityOption.Visibility.FULL)) .isEmpty(); registerAll(); - assertThat(boundedContext.stateTypes(EntityOption.Visibility.FULL)) + assertThat(context.stateTypes(EntityOption.Visibility.FULL)) .isNotEmpty(); } @@ -452,7 +457,7 @@ void getEntityTypesByVisibility() { void throwOnNoRepoFound() { // Attempt to get a repository without registering. assertThrows(IllegalStateException.class, - () -> boundedContext.findRepository(Project.class)); + () -> context.findRepository(Project.class)); } @Test @@ -460,9 +465,9 @@ void throwOnNoRepoFound() { void notExposeInvisibleAggregates() { ModelTests.dropAllModels(); - boundedContext.register(new SecretProjectRepository()); + context.register(new SecretProjectRepository()); - Truth8.assertThat(boundedContext.findRepository(SecretProject.class)) + Truth8.assertThat(context.findRepository(SecretProject.class)) .isEmpty(); } @@ -508,4 +513,16 @@ void closeSystemWhenDomainIsClosed() throws Exception { assertThat(systemLogEvent.getMessage()).contains(systemContextName.getValue()); assertThat(systemLogEvent.getLevel()).isAtLeast(DEBUG); } + + @Test + @DisplayName("return its name in `toString()`") + void stringForm() { + String name = randomString(); + + assertThat(BoundedContext.newBuilder() + .setName(name) + .build() + .toString()) + .isEqualTo(name); + } } diff --git a/server/src/test/java/io/spine/server/aggregate/AggregateRepositoryTest.java b/server/src/test/java/io/spine/server/aggregate/AggregateRepositoryTest.java index f6d62c9e857..2e75c02dbf8 100644 --- a/server/src/test/java/io/spine/server/aggregate/AggregateRepositoryTest.java +++ b/server/src/test/java/io/spine/server/aggregate/AggregateRepositoryTest.java @@ -509,6 +509,20 @@ void onRejections() { @DisplayName("post produced events to EventBus") class PostEventsToBus { + private AggregateRepository repository; + + /** + * Create a fresh instance of the repository since this nested class uses + * {@code BlackBoxBoundedContext}. We cannot use the instance of the repository created by + * {@link AggregateRepositoryTest#setUp()} because this method registers it with another + * {@code BoundedContext}. + */ + @BeforeEach + void createAnotherRepository() { + resetRepository(); + repository = repository(); + } + @Test @DisplayName("after command dispatching") void afterCommand() { @@ -532,11 +546,12 @@ void afterCommand() { .newBuilder() .setProjectId(id) .build(); - BlackBoxBoundedContext.singleTenant() - .with(repository()) - .receivesCommands(create, addTask, start) - .assertThat(emittedEventsHadVersions(1, 2, 3)) - .close(); + BlackBoxBoundedContext + .singleTenant() + .with(repository) + .receivesCommands(create, addTask, start) + .assertThat(emittedEventsHadVersions(1, 2, 3)) + .close(); } @Test @@ -558,15 +573,16 @@ void afterEvent() { .setProjectId(parent) .addChildProjectId(id) .build(); - BlackBoxBoundedContext.singleTenant() - .with(repository()) - .receivesCommands(create, start) - .receivesEvent(archived) - .assertThat(emittedEventsHadVersions( - 1, 2, // Product creation - 3 // Event produced in response to `archived` event - )) - .close(); + BlackBoxBoundedContext + .singleTenant() + .with(repository) + .receivesCommands(create, start) + .receivesEvent(archived) + .assertThat(emittedEventsHadVersions( + 1, 2, // Product creation + 3 // Event produced in response to `archived` event + )) + .close(); } @Test @@ -588,13 +604,14 @@ void throughEventFilter() { .setProjectId(parent) .addChildProjectId(id) .build(); - BlackBoxBoundedContext.singleTenant() - .with(new EventDiscardingAggregateRepository()) - .receivesCommands(create, start) - .receivesEvent(archived) - .assertThat(emittedEvent(none())) - .assertThat(acked(thrice()).withoutErrorsOrRejections()) - .close(); + BlackBoxBoundedContext + .singleTenant() + .with(new EventDiscardingAggregateRepository()) + .receivesCommands(create, start) + .receivesEvent(archived) + .assertThat(emittedEvent(none())) + .assertThat(acked(thrice()).withoutErrorsOrRejections()) + .close(); } } diff --git a/server/src/test/java/io/spine/server/aggregate/AggregateStorageTest.java b/server/src/test/java/io/spine/server/aggregate/AggregateStorageTest.java index 01ff6122a9d..bdec1a3c0b5 100644 --- a/server/src/test/java/io/spine/server/aggregate/AggregateStorageTest.java +++ b/server/src/test/java/io/spine/server/aggregate/AggregateStorageTest.java @@ -546,14 +546,14 @@ class NotStoreEnrichment { @DisplayName("for EventContext") void forEventContext() { EventContext enrichedContext = EventContext - .vBuilder() + .newBuilder() .setEnrichment(withOneAttribute()) .setTimestamp(Time.currentTime()) .setProducerId(AnyPacker.pack(TestValues.newUuidValue())) .setCommandContext(GivenCommandContext.withRandomActor()) .build(); Event event = Event - .vBuilder() + .newBuilder() .setId(newEventId()) .setContext(enrichedContext) .setMessage(AnyPacker.pack(TestValues.newUuidValue())) @@ -570,20 +570,20 @@ void forEventContext() { @DisplayName("for origin of EventContext type") void forEventContextOrigin() { EventContext origin = EventContext - .vBuilder() + .newBuilder() .setEnrichment(withOneAttribute()) .setTimestamp(Time.currentTime()) .setProducerId(AnyPacker.pack(TestValues.newUuidValue())) .setCommandContext(GivenCommandContext.withRandomActor()) .build(); EventContext context = EventContext - .vBuilder() + .newBuilder() .setEventContext(origin) .setTimestamp(Time.currentTime()) .setProducerId(AnyPacker.pack(TestValues.newUuidValue())) .build(); Event event = Event - .vBuilder() + .newBuilder() .setId(newEventId()) .setContext(context) .setMessage(AnyPacker.pack(TestValues.newUuidValue())) diff --git a/server/src/test/java/io/spine/server/aggregate/ApplyAllowImportTest.java b/server/src/test/java/io/spine/server/aggregate/ApplyAllowImportTest.java index f388c6e31bc..09b3d4252ff 100644 --- a/server/src/test/java/io/spine/server/aggregate/ApplyAllowImportTest.java +++ b/server/src/test/java/io/spine/server/aggregate/ApplyAllowImportTest.java @@ -47,18 +47,18 @@ @DisplayName("Aggregate which supports event import should") class ApplyAllowImportTest { - private SingleTenantBlackBoxContext boundedContext; + private SingleTenantBlackBoxContext context; @BeforeEach void setUp() { - boundedContext = BlackBoxBoundedContext + context = BlackBoxBoundedContext .singleTenant() .with(new DotSpace()); } @AfterEach void tearDown() { - boundedContext.close(); + context.close(); } /** @@ -67,24 +67,24 @@ void tearDown() { @Test @DisplayName("use event appliers in a traditional way") void normalApply() { - ObjectId id = ObjectId.newBuilder() - .setValue("Луноход-1") - .build(); + ObjectId id = ObjectId + .newBuilder() + .setValue("Луноход-1") + .build(); - boundedContext - .receivesCommands(move(id, NORTH), move(id, EAST)) - .assertThat(emittedEvent(Moved.class, twice())); + context.receivesCommands(move(id, NORTH), move(id, EAST)) + .assertThat(emittedEvent(Moved.class, twice())); } @Test @DisplayName("use event appliers for import") void importingApply() { - ObjectId id = ObjectId.newBuilder() - .setValue("LRV") - .build(); + ObjectId id = ObjectId + .newBuilder() + .setValue("LRV") + .build(); - boundedContext - .importsEvents(moved(id, SOUTH), moved(id, WEST), moved(id, WEST)) - .assertThat(emittedEvent(Moved.class, thrice())); + context.importsEvents(moved(id, SOUTH), moved(id, WEST), moved(id, WEST)) + .assertThat(emittedEvent(Moved.class, thrice())); } } diff --git a/server/src/test/java/io/spine/server/aggregate/EventImportTest.java b/server/src/test/java/io/spine/server/aggregate/EventImportTest.java index 942c0d62f71..20e0369845b 100644 --- a/server/src/test/java/io/spine/server/aggregate/EventImportTest.java +++ b/server/src/test/java/io/spine/server/aggregate/EventImportTest.java @@ -21,6 +21,7 @@ package io.spine.server.aggregate; import io.spine.base.EventMessage; +import io.spine.server.aggregate.given.klasse.EngineAggregate; import io.spine.server.aggregate.given.klasse.EngineId; import io.spine.server.aggregate.given.klasse.EngineRepository; import io.spine.server.aggregate.given.klasse.event.EngineStopped; @@ -28,12 +29,13 @@ import io.spine.server.aggregate.given.klasse.event.UnsupportedEngineEvent; import io.spine.server.type.EventClass; import io.spine.server.type.EventEnvelope; +import io.spine.testing.server.EventSubject; import io.spine.testing.server.TestEventFactory; import io.spine.testing.server.blackbox.BlackBoxBoundedContext; import io.spine.testing.server.blackbox.SingleTenantBlackBoxContext; +import io.spine.testing.server.entity.EntitySubject; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -41,9 +43,7 @@ import java.util.Set; import static com.google.common.truth.Truth.assertThat; -import static io.spine.testing.client.blackbox.Count.once; import static io.spine.testing.client.blackbox.VerifyAcknowledgements.ackedWithErrors; -import static io.spine.testing.server.blackbox.VerifyEvents.emittedEvent; /** * Test support of event import in {@link AggregateRepository}. @@ -51,115 +51,159 @@ @DisplayName("For event import AggregateRepository should") class EventImportTest { - private SingleTenantBlackBoxContext boundedContext; private EngineRepository repository; + private SingleTenantBlackBoxContext context; - @BeforeEach - void setUp() { - repository = new EngineRepository(); - boundedContext = BlackBoxBoundedContext + void createRepository(boolean routeByFirstMessageField) { + repository = new EngineRepository(routeByFirstMessageField); + context = BlackBoxBoundedContext .singleTenant() .with(repository); } @AfterEach void tearDown() { - boundedContext.close(); + context.close(); } + static EngineId engineId(String value) { + return EngineId + .newBuilder() + .setValue(value) + .build(); + } + + protected final EngineRepository repository() { + return this.repository; + } + + protected final SingleTenantBlackBoxContext context() { + return this.context; + } + + /** + * Creates a new event for the passed message. + * + * @param eventMessage + * the event message + * @param producerId + * the producer for the event. If {@code null}, a test class name will be the ID + * of the producer. + * @return generated event wrapped into the envelope + */ + EventEnvelope createEvent(EventMessage eventMessage, @Nullable EngineId producerId) { + TestEventFactory eventFactory = producerId == null + ? TestEventFactory.newInstance(getClass()) + : TestEventFactory.newInstance(producerId, getClass()); + EventEnvelope result = EventEnvelope.of(eventFactory.createEvent(eventMessage)); + return result; + } + @Test @DisplayName("Obtain importable event classes") void importableEventClasses() { - Set importableEventClasses = repository.importableEvents(); - Set exposedByAggregateClass = repository.aggregateClass() - .importableEvents(); - assertThat(importableEventClasses).isEqualTo(exposedByAggregateClass); + createRepository(false); + Set importableEventClasses = + repository().importableEvents(); + Set exposedByAggregateClass = + repository().aggregateClass() + .importableEvents(); + assertThat(importableEventClasses) + .isEqualTo(exposedByAggregateClass); } @Nested - @DisplayName("route imported events") - class Routing { + @DisplayName("dispatch imported events") + class Dispatching { - private final EngineId engineId = engineId("AFB"); - private final SettingsAdjusted eventMessage = SettingsAdjusted + private final EngineId engineId = engineId("AEL"); + private final EngineStopped eventMessage = EngineStopped .newBuilder() .setId(engineId) .build(); + @Test - @DisplayName("route imported events by Producer ID by default") - void routeById() { - // Create event with the producer ID of the target aggregate. + @DisplayName("by producer ID") + void producerId() { + createRepository(false); + + // Create event with producer ID, which is the target aggregate ID. EventEnvelope event = createEvent(eventMessage, engineId); - // Apply routing to the generated event. - assertRouted(event); + assertImports(event); } @Test - @DisplayName("route imported event by first message field, if configured") - void routeByFirstMessageField() { - repository.routeImportByFirstMessageField(); + @DisplayName("by first message field") + void firstMessageField() { + createRepository(true); - // Create event with the producer ID, which is NOT the target aggregate ID. + // Create event with producer ID, which is NOT the target aggregate ID. EventEnvelope event = createEvent(eventMessage, null); - assertRouted(event); + assertImports(event); } - /** - * Asserts that the import routing resulted in {@link #engineId}. - */ - private void assertRouted(EventEnvelope event) { - Set targets = - repository.eventImportRouting() - .apply(event.message(), event.context()); + private void assertImports(EventEnvelope event) { + EventSubject assertEvents = + context().importsEvent(event.outerObject()) + .assertEvents() + .withType(EngineStopped.class); - assertThat(targets).hasSize(1); - assertThat(targets).containsExactly(engineId); + assertEvents.hasSize(1); } } @Nested - @DisplayName("dispatch imported events") - class Dispatching { - - private final EngineId engineId = engineId("AEL"); - private final EngineStopped eventMessage = EngineStopped + @DisplayName("route imported events by") + class Routing { + private final EngineId engineId = engineId("AFB"); + private final SettingsAdjusted eventMessage = SettingsAdjusted .newBuilder() .setId(engineId) .build(); - @Test - @DisplayName("by producer ID") - void producerId() { - // Create event with producer ID, which is the target aggregate ID. + @DisplayName("Producer ID by default") + void routeById() { + createRepository(false); + + // Create event with the producer ID of the target aggregate. EventEnvelope event = createEvent(eventMessage, engineId); - assertImports(event); + // Apply routing to the generated event. + assertRouted(event); } @Test - @DisplayName("by first message field") - void firstMessageField() { - repository.routeImportByFirstMessageField(); + @DisplayName("first message field, if configured") + void routeByFirstMessageField() { + createRepository(true); - // Create event with producer ID, which is NOT the target aggregate ID. + // Create event with the producer ID, which is NOT the target aggregate ID. EventEnvelope event = createEvent(eventMessage, null); - assertImports(event); + assertRouted(event); } - private void assertImports(EventEnvelope event) { - boundedContext.importsEvent(event.outerObject()) - .assertThat(emittedEvent(EngineStopped.class, once())); + /** + * Asserts that the import routing resulted in {@link #engineId}. + */ + private void assertRouted(EventEnvelope event) { + EntitySubject assertEntity = + context().importsEvent(event.outerObject()) + .assertEntity(EngineAggregate.class, engineId); + + assertEntity.exists(); } } @Test @DisplayName("fail with exception when importing unsupported event") void importUnsupported() { + createRepository(false); + EngineId id = engineId("AGR"); UnsupportedEngineEvent eventMessage = UnsupportedEngineEvent .newBuilder() @@ -167,32 +211,7 @@ void importUnsupported() { .build(); EventEnvelope unsupported = createEvent(eventMessage, id); - boundedContext.importsEvent(unsupported.outerObject()) - .assertThat(ackedWithErrors()); - } - - /** - * Creates a new event for the passed message. - * - * @param eventMessage - * the event message - * @param producerId - * the producer for the event. If {@code null}, a test class name will be the ID - * of the producer. - * @return generated event wrapped into the envelope - */ - private EventEnvelope createEvent(EventMessage eventMessage, @Nullable EngineId producerId) { - TestEventFactory eventFactory = producerId == null - ? TestEventFactory.newInstance(getClass()) - : TestEventFactory.newInstance(producerId, getClass()); - EventEnvelope result = EventEnvelope.of(eventFactory.createEvent(eventMessage)); - return result; - } - - private static EngineId engineId(String value) { - return EngineId - .newBuilder() - .setValue(value) - .build(); + context().importsEvent(unsupported.outerObject()) + .assertThat(ackedWithErrors()); } } diff --git a/server/src/test/java/io/spine/server/aggregate/given/aggregate/IgTestAggregateRepository.java b/server/src/test/java/io/spine/server/aggregate/given/aggregate/IgTestAggregateRepository.java index 3d5a699c42b..2c97bbf4d90 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/aggregate/IgTestAggregateRepository.java +++ b/server/src/test/java/io/spine/server/aggregate/given/aggregate/IgTestAggregateRepository.java @@ -20,6 +20,7 @@ package io.spine.server.aggregate.given.aggregate; +import io.spine.server.route.EventRouting; import io.spine.test.aggregate.ProjectId; import io.spine.test.aggregate.event.AggProjectPaused; import io.spine.test.aggregate.event.AggTaskStarted; @@ -30,16 +31,15 @@ * Test environment repository for {@linkplain io.spine.server.aggregate.IdempotencyGuardTest * IdempotencyGuard tests}. */ -public class IgTestAggregateRepository +public final class IgTestAggregateRepository extends AbstractAggregateTestRepository { @Override - public void onRegistered() { - super.onRegistered(); - - eventRouting().route(AggTaskStarted.class, - (message, context) -> withId(message.getProjectId())) - .route(AggProjectPaused.class, - (message, context) -> withId(message.getProjectId())); + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(AggTaskStarted.class, + (message, ctx) -> withId(message.getProjectId())) + .route(AggProjectPaused.class, + (message, ctx) -> withId(message.getProjectId())); } } diff --git a/server/src/test/java/io/spine/server/aggregate/given/aggregate/TestAggregate.java b/server/src/test/java/io/spine/server/aggregate/given/aggregate/TestAggregate.java index 6f9d1f3aedf..ce149a2f4ba 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/aggregate/TestAggregate.java +++ b/server/src/test/java/io/spine/server/aggregate/given/aggregate/TestAggregate.java @@ -92,7 +92,7 @@ AggProjectCreated handle(AggCreateProject cmd, CommandContext ctx) { AggTaskAdded handle(AggAddTask cmd, CommandContext ctx) { isAddTaskCommandHandled = true; AggTaskAdded event = taskAdded(cmd.getProjectId()); - return event.toVBuilder() + return event.toBuilder() .setTask(cmd.getTask()) .build(); } diff --git a/server/src/test/java/io/spine/server/aggregate/given/importado/DotSpace.java b/server/src/test/java/io/spine/server/aggregate/given/importado/DotSpace.java index 16dc3b19652..62a83005628 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/importado/DotSpace.java +++ b/server/src/test/java/io/spine/server/aggregate/given/importado/DotSpace.java @@ -22,6 +22,7 @@ import io.spine.server.aggregate.AggregateRepository; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; /** * A repository for {@link Dot} objects. @@ -31,11 +32,15 @@ public final class DotSpace extends AggregateRepository { /** * Replaces event import routing to take first message field. * + * @param context + * the {@code BoundedContext} of this repository * @implNote Default behaviour defined in {@link AggregateRepository#eventImportRoute} - * is to take producer ID from an {@code EventContext}. We redefine this to avoid the need - * of creating {@code Event} instances. Real imports would need to create those. + * is to take producer ID from an {@code EventContext}. We redefine this to avoid the + * need of creating {@code Event} instances. Real imports would need to create those. */ - public DotSpace() { - eventImportRouting().replaceDefault(EventRoute.byFirstMessageField()); + @Override + protected void setupImportRouting(EventRouting routing) { + super.setupImportRouting(routing); + routing.replaceDefault(EventRoute.byFirstMessageField(idClass())); } } diff --git a/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineAggregate.java b/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineAggregate.java index 19d8c1d9da6..8a2f878938e 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineAggregate.java +++ b/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineAggregate.java @@ -84,7 +84,7 @@ private void on(EngineStopped event) { */ @Apply(allowImport = true) private void on(SettingsAdjusted event) { - // Do nothing for now. + builder().setStatus(STOPPED); } /* @@ -115,8 +115,8 @@ Nothing on(EmissionTestStopped event) { * * Since this class reacts on own rejections (which are derived from * ThrowableMessage and have the same names as corresponding rejection - * message classes), we cannot import the outer class in which they - * are declared. + * message classes), we cannot import the nested classes because of + * the name clash. *********************************************************************/ @React diff --git a/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineRepository.java b/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineRepository.java index 7a56f02a943..8abf391143a 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineRepository.java +++ b/server/src/test/java/io/spine/server/aggregate/given/klasse/EngineRepository.java @@ -22,6 +22,7 @@ import io.spine.server.aggregate.AggregateRepository; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; /** * Test environment aggregate repository which can switch default routing of importable events. @@ -30,7 +31,17 @@ */ public class EngineRepository extends AggregateRepository { - public void routeImportByFirstMessageField() { - eventImportRouting().replaceDefault(EventRoute.byFirstMessageField()); + private final boolean routeByFirstField; + + public EngineRepository(boolean routeByFirstField) { + this.routeByFirstField = routeByFirstField; + } + + @Override + protected void setupImportRouting(EventRouting routing) { + super.setupImportRouting(routing); + if (routeByFirstField) { + routing.replaceDefault(EventRoute.byFirstMessageField(idClass())); + } } } diff --git a/server/src/test/java/io/spine/server/aggregate/given/repo/FailingAggregateRepository.java b/server/src/test/java/io/spine/server/aggregate/given/repo/FailingAggregateRepository.java index 5cc65f21709..1f4420e3edb 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/repo/FailingAggregateRepository.java +++ b/server/src/test/java/io/spine/server/aggregate/given/repo/FailingAggregateRepository.java @@ -27,7 +27,9 @@ import io.spine.core.EventContext; import io.spine.server.aggregate.AggregateRepository; import io.spine.server.route.CommandRoute; +import io.spine.server.route.CommandRouting; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.server.type.MessageEnvelope; import io.spine.test.aggregate.number.FloatEncountered; import io.spine.test.aggregate.number.RejectNegativeInt; @@ -38,7 +40,7 @@ /** * The repository of {@link io.spine.server.aggregate.given.repo.FailingAggregate}s. */ -public class FailingAggregateRepository +public final class FailingAggregateRepository extends AggregateRepository { private boolean errorLogged; @@ -46,9 +48,10 @@ public class FailingAggregateRepository private @Nullable RuntimeException lastException; @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") - public FailingAggregateRepository() { - super(); - commandRouting().replaceDefault( + @Override + protected void setupCommandRouting(CommandRouting routing) { + super.setupCommandRouting(routing); + routing.replaceDefault( // Simplistic routing function that takes absolute value as ID. new CommandRoute() { private static final long serialVersionUID = 0L; @@ -61,10 +64,14 @@ public Long apply(CommandMessage message, CommandContext context) { } return 0L; } - } - ); + }); + } - eventRouting().replaceDefault( + @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.replaceDefault( new EventRoute() { private static final long serialVersionUID = 0L; diff --git a/server/src/test/java/io/spine/server/aggregate/given/repo/ProjectAggregateRepository.java b/server/src/test/java/io/spine/server/aggregate/given/repo/ProjectAggregateRepository.java index 1836ecf0154..ed2b081eb42 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/repo/ProjectAggregateRepository.java +++ b/server/src/test/java/io/spine/server/aggregate/given/repo/ProjectAggregateRepository.java @@ -25,6 +25,7 @@ import io.spine.server.aggregate.AggregateRepository; import io.spine.server.aggregate.AggregateStorage; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.test.aggregate.ProjectId; import io.spine.test.aggregate.event.AggProjectArchived; import io.spine.test.aggregate.event.AggProjectDeleted; @@ -50,27 +51,27 @@ public class ProjectAggregateRepository .build(); @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") - public ProjectAggregateRepository() { - super(); - eventRouting() - .route(AggProjectArchived.class, - new EventRoute() { - private static final long serialVersionUID = 0L; + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(AggProjectArchived.class, + new EventRoute() { + private static final long serialVersionUID = 0L; - @Override - public Set apply(AggProjectArchived msg, EventContext ctx) { - return ImmutableSet.copyOf(msg.getChildProjectIdList()); - } - }) - .route(AggProjectDeleted.class, - new EventRoute() { - private static final long serialVersionUID = 0L; + @Override + public Set apply(AggProjectArchived msg, EventContext ctx) { + return ImmutableSet.copyOf(msg.getChildProjectIdList()); + } + }) + .route(AggProjectDeleted.class, + new EventRoute() { + private static final long serialVersionUID = 0L; - @Override - public Set apply(AggProjectDeleted msg, EventContext ctx) { - return ImmutableSet.copyOf(msg.getChildProjectIdList()); - } - }); + @Override + public Set apply(AggProjectDeleted msg, EventContext ctx) { + return ImmutableSet.copyOf(msg.getChildProjectIdList()); + } + }); } @Override diff --git a/server/src/test/java/io/spine/server/aggregate/given/repo/ReactingRepository.java b/server/src/test/java/io/spine/server/aggregate/given/repo/ReactingRepository.java index cd3ddf0ea59..e41928120e0 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/repo/ReactingRepository.java +++ b/server/src/test/java/io/spine/server/aggregate/given/repo/ReactingRepository.java @@ -24,6 +24,7 @@ import io.spine.core.EventContext; import io.spine.server.aggregate.AggregateRepository; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.test.aggregate.ProjectId; import io.spine.test.aggregate.event.AggProjectArchived; @@ -32,22 +33,22 @@ /** * The repository of {@link io.spine.server.aggregate.given.repo.ReactingAggregate}. */ -@SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") public class ReactingRepository extends AggregateRepository { - public ReactingRepository() { - super(); - eventRouting() - .route(AggProjectArchived.class, - new EventRoute() { - private static final long serialVersionUID = 0L; + @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(AggProjectArchived.class, + new EventRoute() { + private static final long serialVersionUID = 0L; - @Override - public Set apply(AggProjectArchived message, - EventContext context) { - return ImmutableSet.copyOf(message.getChildProjectIdList()); - } - }); + @Override + public Set apply(AggProjectArchived message, + EventContext context) { + return ImmutableSet.copyOf(message.getChildProjectIdList()); + } + }); } } diff --git a/server/src/test/java/io/spine/server/aggregate/given/repo/RejectionReactingRepository.java b/server/src/test/java/io/spine/server/aggregate/given/repo/RejectionReactingRepository.java index 7f244499999..fa593eae76e 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/repo/RejectionReactingRepository.java +++ b/server/src/test/java/io/spine/server/aggregate/given/repo/RejectionReactingRepository.java @@ -24,18 +24,19 @@ import io.spine.core.EventContext; import io.spine.server.aggregate.AggregateRepository; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.test.aggregate.ProjectId; import io.spine.test.aggregate.rejection.Rejections.AggCannotStartArchivedProject; import java.util.Set; -public class RejectionReactingRepository +public final class RejectionReactingRepository extends AggregateRepository { - public RejectionReactingRepository() { - super(); - eventRouting() - .route(AggCannotStartArchivedProject.class, routeRejection()); + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(AggCannotStartArchivedProject.class, routeRejection()); } private static EventRoute routeRejection() { diff --git a/server/src/test/java/io/spine/server/aggregate/given/repo/RepoOfAggregateWithLifecycle.java b/server/src/test/java/io/spine/server/aggregate/given/repo/RepoOfAggregateWithLifecycle.java index 2237e4fcacd..be904565e96 100644 --- a/server/src/test/java/io/spine/server/aggregate/given/repo/RepoOfAggregateWithLifecycle.java +++ b/server/src/test/java/io/spine/server/aggregate/given/repo/RepoOfAggregateWithLifecycle.java @@ -25,6 +25,7 @@ import io.spine.core.CommandContext; import io.spine.server.aggregate.AggregateRepository; import io.spine.server.route.CommandRoute; +import io.spine.server.route.CommandRouting; import io.spine.test.aggregate.cli.Evaluate; import io.spine.test.aggregate.cli.Evaluated; @@ -55,9 +56,10 @@ public Long apply(CommandMessage message, CommandContext context) { } }; - public RepoOfAggregateWithLifecycle() { - super(); - commandRouting().replaceDefault(parsingRoute); + @Override + protected void setupCommandRouting(CommandRouting routing) { + super.setupCommandRouting(routing); + routing.replaceDefault(parsingRoute); } /** diff --git a/server/src/test/java/io/spine/server/command/AbstractCommanderTest.java b/server/src/test/java/io/spine/server/command/AbstractCommanderTest.java index 9c9077f2e88..677a61ed928 100644 --- a/server/src/test/java/io/spine/server/command/AbstractCommanderTest.java +++ b/server/src/test/java/io/spine/server/command/AbstractCommanderTest.java @@ -84,7 +84,7 @@ void setUp() { @DisplayName("create a command in response to a command") void commandOnCommand() { CmdCreateProject commandMessage = CmdCreateProject - .vBuilder() + .newBuilder() .setProjectId(newProjectId()) .build(); createCommandAndPost(commandMessage); @@ -96,9 +96,9 @@ void commandOnCommand() { @DisplayName("create a command on an event") void commandOnEvent() { CmdTaskAdded eventMessage = CmdTaskAdded - .vBuilder() + .newBuilder() .setProjectId(newProjectId()) - .setTask(Task.vBuilder() + .setTask(Task.newBuilder() .setTaskId(newTaskId()) .build()) .build(); @@ -133,21 +133,21 @@ void createCommandPairWithNull() { private static ProjectId newProjectId() { return ProjectId - .vBuilder() + .newBuilder() .setId(newUuid()) .build(); } private static TaskId newTaskId() { return TaskId - .vBuilder() + .newBuilder() .setId(random(1, 100)) .build(); } private static UserId newUserId() { return UserId - .vBuilder() + .newBuilder() .setValue(newUuid()) .build(); @@ -169,12 +169,12 @@ private void postCreateTaskCommand(boolean startTask) { TaskId taskId = newTaskId(); UserId userId = newUserId(); Task task = Task - .vBuilder() + .newBuilder() .setTaskId(taskId) .setAssignee(userId) .build(); CmdCreateTask commandMessage = CmdCreateTask - .vBuilder() + .newBuilder() .setTaskId(taskId) .setTask(task) .setStart(startTask) @@ -194,7 +194,7 @@ private Commendatore(CommandBus commandBus, EventBus eventBus) { @Command FirstCmdCreateProject on(CmdCreateProject command) { return FirstCmdCreateProject - .vBuilder() + .newBuilder() .setId(command.getProjectId()) .build(); } @@ -202,7 +202,7 @@ FirstCmdCreateProject on(CmdCreateProject command) { @Command CmdSetTaskDescription on(CmdTaskAdded event) { return CmdSetTaskDescription - .vBuilder() + .newBuilder() .setTaskId(event.getTask() .getTaskId()) .setDescription("Testing command creation on event") @@ -215,13 +215,13 @@ Pair> on(CmdCreateTask command) { UserId assignee = command.getTask() .getAssignee(); CmdAssignTask cmdAssignTask = CmdAssignTask - .vBuilder() + .newBuilder() .setTaskId(taskId) .setAssignee(assignee) .build(); CmdStartTask cmdStartTask = command.getStart() ? CmdStartTask - .vBuilder() + .newBuilder() .setTaskId(taskId) .build() : null; diff --git a/server/src/test/java/io/spine/server/commandbus/CommandAckMonitorTest.java b/server/src/test/java/io/spine/server/commandbus/CommandAckMonitorTest.java index c22dd080a62..ce2a995a092 100644 --- a/server/src/test/java/io/spine/server/commandbus/CommandAckMonitorTest.java +++ b/server/src/test/java/io/spine/server/commandbus/CommandAckMonitorTest.java @@ -255,11 +255,11 @@ private static Ack errorAck(CommandId commandId) { private static Ack rejectionAck(CommandId commandId) { ProjectId projectId = ProjectId - .vBuilder() + .newBuilder() .setId(commandId.getUuid()) .build(); CommandMessage commandMessage = CmdBusStartProject - .vBuilder() + .newBuilder() .setProjectId(projectId) .build(); Command command = Command diff --git a/server/src/test/java/io/spine/server/commandbus/CommandSchedulingTest.java b/server/src/test/java/io/spine/server/commandbus/CommandSchedulingTest.java index 060542920f7..cb0f7776823 100644 --- a/server/src/test/java/io/spine/server/commandbus/CommandSchedulingTest.java +++ b/server/src/test/java/io/spine/server/commandbus/CommandSchedulingTest.java @@ -142,11 +142,11 @@ void schedulingTime() { private Command createCommand() { ProjectId id = ProjectId - .vBuilder() + .newBuilder() .setId(newUuid()) .build(); CmdBusStartProject command = CmdBusStartProject - .vBuilder() + .newBuilder() .setProjectId(id) .build(); CommandMessage commandMessage = toMessage(command, CommandMessage.class); diff --git a/server/src/test/java/io/spine/server/commandbus/CommandValidatorViolationCheckTest.java b/server/src/test/java/io/spine/server/commandbus/CommandValidatorViolationCheckTest.java index d05541ee70a..571b2beb0de 100644 --- a/server/src/test/java/io/spine/server/commandbus/CommandValidatorViolationCheckTest.java +++ b/server/src/test/java/io/spine/server/commandbus/CommandValidatorViolationCheckTest.java @@ -27,6 +27,7 @@ import io.spine.core.CommandId; import io.spine.protobuf.AnyPacker; import io.spine.server.type.CommandEnvelope; +import io.spine.test.command.CmdEmpty; import io.spine.test.commandbus.command.CmdBusCreateProject; import io.spine.testing.client.TestActorRequestFactory; import io.spine.validate.ConstraintViolation; @@ -35,10 +36,11 @@ import java.util.List; +import static com.google.common.truth.Truth.assertThat; import static io.spine.server.commandbus.CommandValidator.inspect; import static io.spine.server.commandbus.Given.CommandMessage.createProjectMessage; import static io.spine.testing.core.given.GivenCommandContext.withRandomActor; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static java.util.stream.Collectors.toList; @DisplayName("CommandValidator violation check should") class CommandValidatorViolationCheckTest { @@ -48,9 +50,10 @@ class CommandValidatorViolationCheckTest { void returnNothingForValidCmd() { Command cmd = Given.ACommand.createProject(); - List violations = inspect(CommandEnvelope.of(cmd)); + List violations = inspectCommand(cmd); - assertEquals(0, violations.size()); + assertThat(violations) + .isEmpty(); } @Test @@ -61,9 +64,10 @@ void notAllowDefaultId() { .toBuilder() .setId(CommandId.getDefaultInstance()) .build(); - List violations = inspect(CommandEnvelope.of(unidentifiableCommand)); + List violations = inspectCommand(unidentifiableCommand); - assertEquals(1, violations.size()); + assertThat(violations) + .hasSize(1); } @Test @@ -77,9 +81,10 @@ void notAllowInvalidMessage() { .setContext(withRandomActor()) .build(); - List violations = inspect(CommandEnvelope.of(commandWithEmptyMessage)); + List violations = inspectCommand(commandWithEmptyMessage); - assertEquals(3, violations.size()); + assertThat(violations) + .hasSize(3); } @Test @@ -92,8 +97,32 @@ void notAllowInvalidContext() { .setContext(CommandContext.getDefaultInstance()) .build(); - List violations = inspect(CommandEnvelope.of(commandWithoutContext)); + List violations = inspectCommand(commandWithoutContext); - assertEquals(1, violations.size()); + assertThat(violations) + .hasSize(1); + } + + @Test + @DisplayName("return violation for an empty command message") + void emptyMessage() { + TestActorRequestFactory factory = new TestActorRequestFactory(getClass()); + Command emptyCommand = factory.createCommand(CmdEmpty.newBuilder().build()); + + List violations = inspectCommand(emptyCommand); + + boolean hasViolationOnCommandTargetId = + !(violations.stream() + .filter(v -> v.getMsgFormat() + .contains("command target ID")) + .collect(toList()) + .isEmpty()); + + assertThat(hasViolationOnCommandTargetId) + .isTrue(); + } + + private static List inspectCommand(Command command) { + return inspect(CommandEnvelope.of(command)); } } diff --git a/server/src/test/java/io/spine/server/commandbus/Given.java b/server/src/test/java/io/spine/server/commandbus/Given.java index 07596e02b53..d17fb5353d5 100644 --- a/server/src/test/java/io/spine/server/commandbus/Given.java +++ b/server/src/test/java/io/spine/server/commandbus/Given.java @@ -76,7 +76,7 @@ private ACommand() { private static Command create(io.spine.base.CommandMessage command, UserId userId, Timestamp when) { TenantId generatedTenantId = TenantId - .vBuilder() + .newBuilder() .setValue(newUuid()) .build(); TestActorRequestFactory factory = @@ -176,7 +176,7 @@ static CmdBusCreateTask createTask(TaskId taskId, UserId userId, boolean startTa .setAssignee(userId) .build(); return CmdBusCreateTask - .vBuilder() + .newBuilder() .setTaskId(taskId) .setTask(task) .setStart(startTask) @@ -207,34 +207,34 @@ public static CmdBusCreateProject createProjectMessage() { static CmdBusCreateProject createProjectMessage(ProjectId id) { return CmdBusCreateProject - .vBuilder() + .newBuilder() .setProjectId(id) .build(); } static CmdBusCreateProject createProjectMessage(String projectId) { - return createProjectMessage(ProjectId.vBuilder() + return createProjectMessage(ProjectId.newBuilder() .setId(projectId) .build()); } static CmdBusStartProject startProject(ProjectId id) { return CmdBusStartProject - .vBuilder() + .newBuilder() .setProjectId(id) .build(); } static FirstCmdBusCreateProject firstCreateProject(ProjectId projectId) { return FirstCmdBusCreateProject - .vBuilder() + .newBuilder() .setId(projectId) .build(); } static SecondCmdBusStartProject secondStartProject(ProjectId projectId) { return SecondCmdBusStartProject - .vBuilder() + .newBuilder() .setId(projectId) .build(); } diff --git a/server/src/test/java/io/spine/server/commandbus/given/CommandHandlerTestEnv.java b/server/src/test/java/io/spine/server/commandbus/given/CommandHandlerTestEnv.java index 89532e10481..20aa85be2cc 100644 --- a/server/src/test/java/io/spine/server/commandbus/given/CommandHandlerTestEnv.java +++ b/server/src/test/java/io/spine/server/commandbus/given/CommandHandlerTestEnv.java @@ -180,11 +180,11 @@ public void onError(CommandEnvelope envelope, RuntimeException exception) { private ImmutableList createEventsOnStartProjectCmd() { ProjectId id = ProjectId - .vBuilder() + .newBuilder() .setId(getId()) .build(); CmdBusProjectStarted startedEvent = CmdBusProjectStarted - .vBuilder() + .newBuilder() .setProjectId(id) .build(); CmdBusProjectStarted defaultEvent = CmdBusProjectStarted.getDefaultInstance(); @@ -195,12 +195,12 @@ private ImmutableList createEventsOnStartProjectCmd() { createEventsOnCreateTaskCmd(CmdBusCreateTask msg) { TaskId taskId = msg.getTaskId(); CmdBusTaskAssigned cmdTaskAssigned = CmdBusTaskAssigned - .vBuilder() + .newBuilder() .setTaskId(taskId) .build(); CmdBusTaskStarted cmdTaskStarted = msg.getStart() ? CmdBusTaskStarted - .vBuilder() + .newBuilder() .setTaskId(taskId) .build() : null; diff --git a/server/src/test/java/io/spine/server/commandbus/given/SingleTenantCommandBusTestEnv.java b/server/src/test/java/io/spine/server/commandbus/given/SingleTenantCommandBusTestEnv.java index c5961a77bc2..22bf384bfe6 100644 --- a/server/src/test/java/io/spine/server/commandbus/given/SingleTenantCommandBusTestEnv.java +++ b/server/src/test/java/io/spine/server/commandbus/given/SingleTenantCommandBusTestEnv.java @@ -103,7 +103,7 @@ CmdBusProjectCreated handle(FirstCmdBusCreateProject command) { commandBus.post(commandToPost, noOpObserver()); handledCommands.add(command); return CmdBusProjectCreated - .vBuilder() + .newBuilder() .setProjectId(command.getId()) .build(); } @@ -112,7 +112,7 @@ CmdBusProjectCreated handle(FirstCmdBusCreateProject command) { CmdBusProjectStarted handle(SecondCmdBusStartProject command) { handledCommands.add(command); return CmdBusProjectStarted - .vBuilder() + .newBuilder() .setProjectId(command.getId()) .build(); } diff --git a/server/src/test/java/io/spine/server/entity/DefaultCommandRouteTest.java b/server/src/test/java/io/spine/server/entity/DefaultCommandRouteTest.java index 0be74b8460d..eaff4a08f78 100644 --- a/server/src/test/java/io/spine/server/entity/DefaultCommandRouteTest.java +++ b/server/src/test/java/io/spine/server/entity/DefaultCommandRouteTest.java @@ -20,15 +20,28 @@ package io.spine.server.entity; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.Descriptors; +import com.google.protobuf.Empty; +import com.google.protobuf.Message; +import com.google.protobuf.Parser; +import com.google.protobuf.Timestamp; +import com.google.protobuf.UnknownFieldSet; +import io.spine.base.CommandMessage; +import io.spine.core.CommandContext; import io.spine.server.route.DefaultCommandRoute; import io.spine.test.entity.command.EntCreateProject; import io.spine.testdata.Sample; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.Optional; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; @DisplayName("DefaultCommandRoute should") @@ -39,9 +52,148 @@ class DefaultCommandRouteTest { void getIdFromCommand() { EntCreateProject msg = Sample.messageOfType(EntCreateProject.class); - Optional id = DefaultCommandRoute.asOptional(msg); + assertTrue(DefaultCommandRoute.exists(msg)); - assertTrue(id.isPresent()); - assertEquals(msg.getProjectId(), id.get()); + assertThat(DefaultCommandRoute + .newInstance(io.spine.test.entity.ProjectId.class) + .apply(msg, CommandContext.getDefaultInstance())) + .isEqualTo(msg.getProjectId()); + } + + @Test + @DisplayName("test if command message is routable") + void emptyOrNot() { + assertThat(DefaultCommandRoute.exists(new StubCommand(true))) + .isFalse(); + assertThat(DefaultCommandRoute.exists(new StubCommand(false))) + .isTrue(); + } + + /** + * Stub class which simulates empty command message. + */ + @Immutable + private static final class StubCommand implements CommandMessage { + + private static final long serialVersionUID = 0L; + private final boolean empty; + + private StubCommand(boolean empty) { + this.empty = empty; + } + + @Override + public void writeTo(CodedOutputStream output) { + // Do nothing. + } + + @Override + public int getSerializedSize() { + return 0; + } + + @Override + public Parser getParserForType() { + return null; + } + + @Override + public ByteString toByteString() { + return null; + } + + @SuppressWarnings("ZeroLengthArrayAllocation") + @Override + public byte[] toByteArray() { + return new byte[0]; + } + + @Override + public void writeTo(OutputStream output) { + // Do nothing. + } + + @Override + public void writeDelimitedTo(OutputStream output) { + // Do nothing. + } + + @Override + public Builder newBuilderForType() { + return null; + } + + @Override + public Builder toBuilder() { + return null; + } + + @Override + public Message getDefaultInstanceForType() { + return null; + } + + @Override + public boolean isInitialized() { + return false; + } + + @Override + public List findInitializationErrors() { + return null; + } + + @Override + public String getInitializationErrorString() { + return null; + } + + @Override + public Descriptors.Descriptor getDescriptorForType() { + return empty + ? Empty.getDescriptor() + : Timestamp.getDescriptor(); + } + + @Override + public Map getAllFields() { + return null; + } + + @Override + public boolean hasOneof(Descriptors.OneofDescriptor oneof) { + return false; + } + + @Override + public Descriptors.FieldDescriptor getOneofFieldDescriptor( + Descriptors.OneofDescriptor oneof) { + return null; + } + + @Override + public boolean hasField(Descriptors.FieldDescriptor field) { + return false; + } + + @Override + public Object getField(Descriptors.FieldDescriptor field) { + return null; + } + + @Override + public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { + return 0; + } + + @Override + public Object getRepeatedField(Descriptors.FieldDescriptor field, int index) { + return null; + } + + @Override + public UnknownFieldSet getUnknownFields() { + return null; + } } } diff --git a/server/src/test/java/io/spine/server/entity/RepositoryTest.java b/server/src/test/java/io/spine/server/entity/RepositoryTest.java index c0b0d0c731c..de7a3cbd3b3 100644 --- a/server/src/test/java/io/spine/server/entity/RepositoryTest.java +++ b/server/src/test/java/io/spine/server/entity/RepositoryTest.java @@ -34,11 +34,13 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.Iterator; import static com.google.common.collect.Iterators.size; +import static com.google.common.truth.Truth.assertThat; import static io.spine.testing.core.given.GivenTenantId.newUuid; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -90,7 +92,7 @@ void beUnregisteredOnInit() { @Test @DisplayName("not allow getting BoundedContext before registration") void notGetBcIfUnregistered() { - assertThrows(IllegalStateException.class, () -> new TestRepo().boundedContext()); + assertThrows(IllegalStateException.class, () -> new TestRepo().context()); } @Test @@ -128,6 +130,44 @@ void provideDefaultEventFilter() { assertNotNull(filter); } + @Nested + @DisplayName("prohibit overwriting already set context") + class OverwritingContext { + + private BoundedContext ctx1; + private BoundedContext ctx2; + + @BeforeEach + void createContexts() { + ctx1 = BoundedContext + .newBuilder() + .setName("Context-1") + .build(); + ctx2 = BoundedContext + .newBuilder() + .setName("Context-2") + .build(); + } + + @Test + @DisplayName("throwing ISE") + void prohibit() { + repository.setContext(ctx1); + assertThrows(IllegalStateException.class, () -> + repository.setContext(ctx2)); + } + + @Test + @DisplayName("allowing passing the same value twice") + void idempotency() { + repository.setContext(ctx1); + repository.setContext(ctx1); + assertThat(repository.context()) + .isEqualTo(ctx1); + } + } + + @Test @DisplayName("close storage on close") void closeStorageOnClose() { diff --git a/server/src/test/java/io/spine/server/entity/TransactionalEntityTest.java b/server/src/test/java/io/spine/server/entity/TransactionalEntityTest.java index 2a031153ba5..09a266981f5 100644 --- a/server/src/test/java/io/spine/server/entity/TransactionalEntityTest.java +++ b/server/src/test/java/io/spine/server/entity/TransactionalEntityTest.java @@ -187,7 +187,7 @@ void returnActiveTxFlags() { TransactionalEntity entity = entityWithInactiveTx(); LifecycleFlags originalFlags = entity.lifecycleFlags(); - LifecycleFlags modifiedFlags = originalFlags.toVBuilder() + LifecycleFlags modifiedFlags = originalFlags.toBuilder() .setDeleted(true) .build(); @@ -220,7 +220,7 @@ void reflectingCurrentState() { Message originalState = entity.builderFromState() .build(); - EmptyEntity newState = EmptyEntity.vBuilder() + EmptyEntity newState = EmptyEntity.newBuilder() .setId(newUuidValue().getValue()) .build(); assertNotEquals(originalState, newState); diff --git a/server/src/test/java/io/spine/server/entity/storage/EntityQueriesTest.java b/server/src/test/java/io/spine/server/entity/storage/EntityQueriesTest.java index 150404939ce..4447bc94a88 100644 --- a/server/src/test/java/io/spine/server/entity/storage/EntityQueriesTest.java +++ b/server/src/test/java/io/spine/server/entity/storage/EntityQueriesTest.java @@ -104,7 +104,7 @@ void checkFilterType() { Filter filter = Filters.gt(archived.name(), 42); CompositeFilter compositeFilter = Filters.all(filter); TargetFilters filters = TargetFilters - .vBuilder() + .newBuilder() .addFilter(compositeFilter) .build(); @@ -118,7 +118,7 @@ void notCreateForNonExisting() { Filter filter = Filters.eq("nonExistingColumn", 42); CompositeFilter compositeFilter = Filters.all(filter); TargetFilters filters = TargetFilters - .vBuilder() + .newBuilder() .addFilter(compositeFilter) .build(); @@ -148,7 +148,7 @@ void constructNonEmptyQueries() { Message someGenericId = Sample.messageOfType(ProjectId.class); Any entityId = AnyPacker.pack(someGenericId); IdFilter idFilter = IdFilter - .vBuilder() + .newBuilder() .addIds(entityId) .build(); BoolValue archived = BoolValue @@ -158,12 +158,12 @@ void constructNonEmptyQueries() { Filter archivedFilter = Filters .eq(LifecycleFlagField.archived.name(), archived); CompositeFilter aggregatingFilter = CompositeFilter - .vBuilder() + .newBuilder() .addFilter(archivedFilter) .setOperator(EITHER) .build(); TargetFilters filters = TargetFilters - .vBuilder() + .newBuilder() .setIdFilter(idFilter) .addFilter(aggregatingFilter) .build(); diff --git a/server/src/test/java/io/spine/server/event/given/EventRootCommandIdTestEnv.java b/server/src/test/java/io/spine/server/event/given/EventRootCommandIdTestEnv.java index a6868e4872d..28c9e69e426 100644 --- a/server/src/test/java/io/spine/server/event/given/EventRootCommandIdTestEnv.java +++ b/server/src/test/java/io/spine/server/event/given/EventRootCommandIdTestEnv.java @@ -21,6 +21,7 @@ package io.spine.server.event.given; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper; import io.spine.base.CommandMessage; import io.spine.core.Command; import io.spine.core.CommandContext; @@ -38,6 +39,7 @@ import io.spine.server.procman.ProcessManager; import io.spine.server.procman.ProcessManagerRepository; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.test.event.EvInvitationAccepted; import io.spine.test.event.EvMember; import io.spine.test.event.EvMemberInvitation; @@ -184,21 +186,23 @@ public static class ProjectAggregateRepository * This is done for the purposes of the * {@linkplain EventRootCommandIdTest.MatchExternalEventHandledBy#aggregate()} test. */ - @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") public static class TeamAggregateRepository extends AggregateRepository { - public TeamAggregateRepository() { - eventRouting() - .route(ProjectCreated.class, - new EventRoute() { - private static final long serialVersionUID = 0L; - - @Override - public Set apply(ProjectCreated msg, EventContext ctx) { - return singleton(msg.getTeamId()); - } - }); + @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(ProjectCreated.class, + new EventRoute() { + private static final long serialVersionUID = 0L; + + @Override + public Set apply(ProjectCreated msg, EventContext ctx) { + return singleton(msg.getTeamId()); + } + }); + } } @@ -207,23 +211,25 @@ public Set apply(ProjectCreated msg, EventContext ctx) { * created the invitation. This is done for the purposes of the * {@linkplain EventRootCommandIdTest.MatchExternalEventHandledBy#processManager()} test. */ - @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") - public static class TeamCreationRepository + public static final class TeamCreationRepository extends ProcessManagerRepository { - public TeamCreationRepository() { - eventRouting() - .route(EvInvitationAccepted.class, - new EventRoute() { - private static final long serialVersionUID = 0L; - - @Override - public Set apply(EvInvitationAccepted msg, - EventContext ctx) { - return singleton(msg.getInvitation() - .getTeamId()); - } - }); + @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") + @OverridingMethodsMustInvokeSuper + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(EvInvitationAccepted.class, + new EventRoute() { + private static final long serialVersionUID = 0L; + + @Override + public Set apply(EvInvitationAccepted msg, + EventContext ctx) { + return singleton(msg.getInvitation() + .getTeamId()); + } + }); } } diff --git a/server/src/test/java/io/spine/server/event/store/EEntityTest.java b/server/src/test/java/io/spine/server/event/store/EEntityTest.java index e60f5a3f40b..7438ec3139c 100644 --- a/server/src/test/java/io/spine/server/event/store/EEntityTest.java +++ b/server/src/test/java/io/spine/server/event/store/EEntityTest.java @@ -20,11 +20,11 @@ void clearEnrichments() { Event event = GivenEvent.arbitrary(); EventContext contextWithEnrichment = event .getContext() - .toVBuilder() + .toBuilder() .setEnrichment(enrichment) .build(); Event eventWithEnrichment = event - .toVBuilder() + .toBuilder() .setContext(contextWithEnrichment) .build(); EEntity entity = EEntity.create(eventWithEnrichment); diff --git a/server/src/test/java/io/spine/server/event/store/EventStoreTest.java b/server/src/test/java/io/spine/server/event/store/EventStoreTest.java index 936d11a08d3..a6c5f314cb6 100644 --- a/server/src/test/java/io/spine/server/event/store/EventStoreTest.java +++ b/server/src/test/java/io/spine/server/event/store/EventStoreTest.java @@ -269,7 +269,7 @@ void eventContextOrigin() { CommandContext commandContext = event.context() .getCommandContext(); EventContext originContext = - EventContext.vBuilder() + EventContext.newBuilder() .setEnrichment(withOneAttribute()) .setCommandContext(commandContext) .setTimestamp(event.context() diff --git a/server/src/test/java/io/spine/server/given/groups/GroupNameProjection.java b/server/src/test/java/io/spine/server/given/groups/GroupNameProjection.java index f6338a53c30..1491ab6e5ff 100644 --- a/server/src/test/java/io/spine/server/given/groups/GroupNameProjection.java +++ b/server/src/test/java/io/spine/server/given/groups/GroupNameProjection.java @@ -37,25 +37,20 @@ private GroupNameProjection(GroupId id) { @Subscribe(external = true) void onUpdate(Organization organization) { - builder() - .setId(id()) - .setName(organization.getName()); + builder().setId(id()) + .setName(organization.getName()); } public static final class Repository extends ProjectionRepository { @Override - public void onRegistered() { - super.onRegistered(); - - StateUpdateRouting routing = StateUpdateRouting.newInstance(); + protected void setupStateRouting(StateUpdateRouting routing) { routing.route(Organization.class, (org, ctx) -> withId( GroupId.newBuilder() .setUuid(org.getId() .getUuid()) .build())); - eventRouting().routeEntityStateUpdates(routing); } } } diff --git a/server/src/test/java/io/spine/server/given/groups/GroupProjection.java b/server/src/test/java/io/spine/server/given/groups/GroupProjection.java index 69f541859ce..19315cf2494 100644 --- a/server/src/test/java/io/spine/server/given/groups/GroupProjection.java +++ b/server/src/test/java/io/spine/server/given/groups/GroupProjection.java @@ -45,15 +45,12 @@ public static final class Repository extends ProjectionRepository { @Override - public void onRegistered() { - super.onRegistered(); - StateUpdateRouting routing = StateUpdateRouting.newInstance(); + protected void setupStateRouting(StateUpdateRouting routing) { routing.route(Organization.class, (org, eventContext) -> withId(GroupId.newBuilder() .setUuid(org.getHead() .getValue()) .build())); - eventRouting().routeEntityStateUpdates(routing); } } } diff --git a/server/src/test/java/io/spine/server/procman/ProcessManagerRepositoryTest.java b/server/src/test/java/io/spine/server/procman/ProcessManagerRepositoryTest.java index 2f317fca38d..96709169da9 100644 --- a/server/src/test/java/io/spine/server/procman/ProcessManagerRepositoryTest.java +++ b/server/src/test/java/io/spine/server/procman/ProcessManagerRepositoryTest.java @@ -21,6 +21,7 @@ package io.spine.server.procman; import com.google.common.truth.Truth; +import com.google.common.truth.Truth8; import com.google.protobuf.Any; import io.spine.base.CommandMessage; import io.spine.base.EventMessage; @@ -40,6 +41,7 @@ import io.spine.server.event.DuplicateEventException; import io.spine.server.procman.given.delivery.GivenMessage; import io.spine.server.procman.given.repo.EventDiscardingProcManRepository; +import io.spine.server.procman.given.repo.ProjectCompletion; import io.spine.server.procman.given.repo.RememberingSubscriber; import io.spine.server.procman.given.repo.SensoryDeprivedPmRepository; import io.spine.server.procman.given.repo.TestProcessManager; @@ -139,10 +141,11 @@ protected TestProcessManager createEntity(ProjectId id) { .newBuilder() .setId(id) .build(); - TestProcessManager result = Given.processManagerOfClass(TestProcessManager.class) - .withId(id) - .withState(state) - .build(); + TestProcessManager result = + Given.processManagerOfClass(TestProcessManager.class) + .withId(id) + .withState(state) + .build(); return result; } @@ -259,6 +262,14 @@ private void dispatchEvent(Event origin) { repository().dispatch(EventEnvelope.of(event)); } + @Test + @DisplayName("allow customizing command routing") + void setupOfCommandRouting() { + ProjectCompletion.Repository repo = new ProjectCompletion.Repository(); + boundedContext.register(repo); + assertTrue(repo.callbackCalled()); + } + @Nested @DisplayName("dispatch") class Dispatch { @@ -304,7 +315,7 @@ void events() { assertTrue(TestProcessManager.processed(event.enclosedMessage())); dispatchEvent(event); - RuntimeException exception = repository().getLatestException(); + RuntimeException exception = repository().latestException(); assertNotNull(exception); assertThat(exception, instanceOf(DuplicateEventException.class)); } @@ -318,7 +329,7 @@ void commands() { assertTrue(TestProcessManager.processed(command.enclosedMessage())); dispatchCommand(command); - RuntimeException exception = repository().getLatestException(); + RuntimeException exception = repository().latestException(); assertNotNull(exception); assertThat(exception, instanceOf(DuplicateCommandException.class)); } @@ -451,7 +462,8 @@ void throwOnUnknownCommand() { ProcessManagerRepository repo = repository(); Throwable exception = assertThrows(RuntimeException.class, () -> repo.dispatchNowTo(id, request)); - assertThat(getRootCause(exception), instanceOf(IllegalStateException.class)); + Truth.assertThat(getRootCause(exception)) + .isInstanceOf(IllegalStateException.class); } @Nested @@ -501,18 +513,20 @@ void postCommandRejections() { } @Test - @DisplayName("throw ISE on registering to BC if repo is not subscribed to any messages") + @DisplayName("check that its `ProcessManager` class is subscribed to at least one message") void notRegisterIfSubscribedToNothing() { SensoryDeprivedPmRepository repo = new SensoryDeprivedPmRepository(); - BoundedContext boundedContext = BoundedContext.newBuilder() - .setMultitenant(false) - .build(); - repo.setBoundedContext(boundedContext); - assertThrows(IllegalStateException.class, repo::onRegistered); + BoundedContext context = BoundedContext + .newBuilder() + .setMultitenant(false) + .build(); + + assertThrows(IllegalStateException.class, () -> + repo.setContext(context)); } @Test - @DisplayName("provide EventFilter which discards EntityStateChanged events") + @DisplayName("provide `EventFilter` which discards `EntityStateChanged` events") void discardEntityStateChangedEvents() { EventFilter filter = repository().eventFilter(); ProjectId projectId = ProjectId @@ -523,8 +537,8 @@ void discardEntityStateChangedEvents() { .newBuilder() .setProjectId(projectId) .build(); - assertTrue(filter.filter(arbitraryEvent) - .isPresent()); + Truth8.assertThat(filter.filter(arbitraryEvent)) + .isPresent(); Any newState = pack(currentTime()); EntityHistoryId historyId = EntityHistoryId @@ -537,11 +551,12 @@ void discardEntityStateChangedEvents() { .setId(historyId) .setNewState(newState) .build(); - assertFalse(filter.filter(discardedEvent).isPresent()); + Truth8.assertThat(filter.filter(discardedEvent)) + .isEmpty(); } @Test - @DisplayName("post all domain events through an EventFilter") + @DisplayName("post all domain events through an `EventFilter`") void postEventsThroughFilter() { ProjectId projectId = ProjectId .newBuilder() diff --git a/server/src/test/java/io/spine/server/procman/ProcessManagerTest.java b/server/src/test/java/io/spine/server/procman/ProcessManagerTest.java index 9ae310a2ebf..0d93f26dc1a 100644 --- a/server/src/test/java/io/spine/server/procman/ProcessManagerTest.java +++ b/server/src/test/java/io/spine/server/procman/ProcessManagerTest.java @@ -117,9 +117,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.spy; -@SuppressWarnings({ - "InnerClassMayBeStatic", "ClassCanBeStatic" /* JUnit nested classes cannot be static. */, - "DuplicateStringLiteralInspection" /* Common test display names. */}) @DisplayName("ProcessManager should") class ProcessManagerTest { diff --git a/server/src/test/java/io/spine/server/procman/given/pm/DirectQuizProcman.java b/server/src/test/java/io/spine/server/procman/given/pm/DirectQuizProcman.java index 0fcb4a4e101..68b051300b4 100644 --- a/server/src/test/java/io/spine/server/procman/given/pm/DirectQuizProcman.java +++ b/server/src/test/java/io/spine/server/procman/given/pm/DirectQuizProcman.java @@ -72,7 +72,7 @@ PmQuizStarted handle(PmStartQuiz command) { if (questionIsClosed(questionId)) { PmQuestionAlreadySolved event = PmQuestionAlreadySolved - .vBuilder() + .newBuilder() .setQuizId(examId) .setQuestionId(questionId) .build(); @@ -83,7 +83,7 @@ PmQuizStarted handle(PmStartQuiz command) { if (answerIsCorrect) { PmQuestionSolved reaction = PmQuestionSolved - .vBuilder() + .newBuilder() .setQuizId(examId) .setQuestionId(questionId) .build(); @@ -91,7 +91,7 @@ PmQuizStarted handle(PmStartQuiz command) { } else { PmQuestionFailed reaction = PmQuestionFailed - .vBuilder() + .newBuilder() .setQuizId(examId) .setQuestionId(questionId) .build(); diff --git a/server/src/test/java/io/spine/server/procman/given/repo/ProjectCompletion.java b/server/src/test/java/io/spine/server/procman/given/repo/ProjectCompletion.java new file mode 100644 index 00000000000..d7a7ec76519 --- /dev/null +++ b/server/src/test/java/io/spine/server/procman/given/repo/ProjectCompletion.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019, TeamDev. 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.procman.given.repo; + +import io.spine.core.CommandContext; +import io.spine.server.command.Assign; +import io.spine.server.procman.ProcessManager; +import io.spine.server.procman.ProcessManagerRepository; +import io.spine.server.route.CommandRouting; +import io.spine.test.procman.ProcessId; +import io.spine.test.procman.ProcessState; +import io.spine.test.procman.ProcessStateVBuilder; +import io.spine.test.procman.ProjectId; +import io.spine.test.procman.command.PmCompleteProject; +import io.spine.test.procman.command.PmCreateProject; +import io.spine.test.procman.event.PmProjectCreated; + +import static io.spine.testdata.Sample.builderForType; + +/** + * A test environment for testing + * {@linkplain ProcessManagerRepository#setupCommandRouting(CommandRouting) custom command + * routing setup}. + * + * @see Repository#setupCommandRouting(CommandRouting) + */ +public class ProjectCompletion + extends ProcessManager { + + @Assign + PmProjectCreated handle(PmCompleteProject command, CommandContext ignored) { + PmProjectCreated.Builder event = builderForType(PmProjectCreated.class); + return event.setProjectId(command.getProjectId()) + .build(); + } + + public static class Repository + extends ProcessManagerRepository { + + private boolean callbackCalled; + + @Override + protected void setupCommandRouting(CommandRouting routing) { + super.setupCommandRouting(routing); + routing.route(PmCreateProject.class, + (command, context) -> toProcessId(command.getProjectId())); + this.callbackCalled = true; + } + + private static ProcessId toProcessId(ProjectId projectId) { + return ProcessId + .newBuilder() + .setUuid(projectId.getId()) + .build(); + } + + public boolean callbackCalled() { + return callbackCalled; + } + + } +} diff --git a/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManager.java b/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManager.java index c3a84d872ec..3894454f600 100644 --- a/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManager.java +++ b/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManager.java @@ -85,7 +85,7 @@ private void keep(Message commandOrEventMsg) { } private void handleProjectCreated(ProjectId projectId) { - Project newState = state().toVBuilder() + Project newState = state().toBuilder() .setId(projectId) .setStatus(Project.Status.CREATED) .build(); @@ -93,14 +93,14 @@ private void handleProjectCreated(ProjectId projectId) { } private void handleTaskAdded(Task task) { - Project newState = state().toVBuilder() + Project newState = state().toBuilder() .addTask(task) .build(); builder().mergeFrom(newState); } private void handleProjectStarted() { - Project newState = state().toVBuilder() + Project newState = state().toBuilder() .setStatus(Project.Status.STARTED) .build(); builder().mergeFrom(newState); @@ -170,7 +170,7 @@ PmProjectDeleted handle(PmDeleteProject command) { PmNothingDone handle(PmDoNothing command, CommandContext ignored) { keep(command); return PmNothingDone - .vBuilder() + .newBuilder() .setProjectId(command.getProjectId()) .build(); } @@ -192,7 +192,6 @@ Nothing on(StandardRejections.EntityAlreadyArchived rejection) { @React Nothing on(StandardRejections.EntityAlreadyDeleted rejection) { - keep(rejection); return nothing(); } diff --git a/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManagerRepository.java b/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManagerRepository.java index bc0d9d05e04..0a31158db00 100644 --- a/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManagerRepository.java +++ b/server/src/test/java/io/spine/server/procman/given/repo/TestProcessManagerRepository.java @@ -21,20 +21,27 @@ package io.spine.server.procman.given.repo; import io.spine.server.entity.EventFilter; +import io.spine.server.entity.rejection.StandardRejection; import io.spine.server.procman.ProcessManagerRepository; +import io.spine.server.route.EventRouting; import io.spine.server.type.CommandEnvelope; import io.spine.server.type.EventEnvelope; import io.spine.test.procman.Project; import io.spine.test.procman.ProjectId; import org.checkerframework.checker.nullness.qual.Nullable; -public class TestProcessManagerRepository +import static io.spine.server.route.EventRoute.withId; + +public final class TestProcessManagerRepository extends ProcessManagerRepository { private @Nullable RuntimeException latestException; - public TestProcessManagerRepository() { - super(); + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(StandardRejection.class, + (event, context) -> withId((ProjectId) event.entityId())); } @Override @@ -49,7 +56,7 @@ public void onError(CommandEnvelope cmd, RuntimeException exception) { super.onError(cmd, exception); } - public @Nullable RuntimeException getLatestException() { + public @Nullable RuntimeException latestException() { return latestException; } diff --git a/server/src/test/java/io/spine/server/projection/ProjectionRepositoryTest.java b/server/src/test/java/io/spine/server/projection/ProjectionRepositoryTest.java index cd231d3d6f1..a7339267af8 100644 --- a/server/src/test/java/io/spine/server/projection/ProjectionRepositoryTest.java +++ b/server/src/test/java/io/spine/server/projection/ProjectionRepositoryTest.java @@ -546,15 +546,15 @@ void eventStore() { } @Test - @DisplayName("throw ISE on registering to BC if repo is not subscribed to any messages") + @DisplayName("check that its `Projection` class is subscribed to at least one message") void notRegisterIfSubscribedToNothing() { SensoryDeprivedProjectionRepository repo = new SensoryDeprivedProjectionRepository(); - BoundedContext boundedContext = BoundedContext + BoundedContext context = BoundedContext .newBuilder() .setMultitenant(false) .build(); - repo.setBoundedContext(boundedContext); - assertThrows(IllegalStateException.class, repo::onRegistered); + assertThrows(IllegalStateException.class, () -> + repo.setContext(context)); } } diff --git a/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java b/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java index 578d0aaec4e..7023bf79095 100644 --- a/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java +++ b/server/src/test/java/io/spine/server/projection/e2e/ProjectionEndToEndTest.java @@ -109,7 +109,7 @@ void receiveExternal() { OrganizationId producerId = established.getId(); sender.receivesEventsProducedBy(producerId, established); GroupId groupId = GroupId - .vBuilder() + .newBuilder() .setUuid(producerId.getUuid()) .build(); receiver.assertEntityWithState(GroupName.class, groupId) diff --git a/server/src/test/java/io/spine/server/projection/given/EntitySubscriberProjection.java b/server/src/test/java/io/spine/server/projection/given/EntitySubscriberProjection.java index a360e936f7c..273ec917b8a 100644 --- a/server/src/test/java/io/spine/server/projection/given/EntitySubscriberProjection.java +++ b/server/src/test/java/io/spine/server/projection/given/EntitySubscriberProjection.java @@ -44,10 +44,11 @@ public EntitySubscriberProjection(ProjectId id) { @Subscribe void onUpdate(Project aggregateState) { - List taskNames = aggregateState.getTaskList() - .stream() - .map(Task::getTitle) - .collect(toList()); + List taskNames = + aggregateState.getTaskList() + .stream() + .map(Task::getTitle) + .collect(toList()); builder().setProjectId(aggregateState.getId()) .setProjectName(aggregateState.getName()) .clearTaskName() @@ -58,13 +59,8 @@ public static final class Repository extends ProjectionRepository { @Override - public void onRegistered() { - super.onRegistered(); - eventRouting().routeEntityStateUpdates( - StateUpdateRouting - .newInstance() - .route(Project.class, (state, context) -> withId(state.getId())) - ); + protected void setupStateRouting(StateUpdateRouting routing) { + routing.route(Project.class, (state, context) -> withId(state.getId())); } } } diff --git a/server/src/test/java/io/spine/server/projection/given/TestProjection.java b/server/src/test/java/io/spine/server/projection/given/TestProjection.java index 100651fe918..513aa49309a 100644 --- a/server/src/test/java/io/spine/server/projection/given/TestProjection.java +++ b/server/src/test/java/io/spine/server/projection/given/TestProjection.java @@ -87,7 +87,7 @@ void on(PrjProjectCreated event) { // Keep the event message for further inspection in tests. keep(event); - Project newState = state().toVBuilder() + Project newState = state().toBuilder() .setId(event.getProjectId()) .setStatus(Project.Status.CREATED) .setName(event.getName()) @@ -114,7 +114,7 @@ void on(PrjTaskAdded event) { void on(PrjProjectStarted event, @SuppressWarnings("UnusedParameters") EventContext ignored) { keep(event); - Project newState = state().toVBuilder() + Project newState = state().toBuilder() .setStatus(Project.Status.STARTED) .build(); builder().mergeFrom(newState); diff --git a/server/src/test/java/io/spine/server/route/CommandRoutingTest.java b/server/src/test/java/io/spine/server/route/CommandRoutingTest.java index 8db9d3a995b..c4475a9b43a 100644 --- a/server/src/test/java/io/spine/server/route/CommandRoutingTest.java +++ b/server/src/test/java/io/spine/server/route/CommandRoutingTest.java @@ -81,7 +81,7 @@ public Long apply(RegisterUser message, CommandContext context) { @BeforeEach void setUp() { - commandRouting = CommandRouting.newInstance(); + commandRouting = CommandRouting.newInstance(Long.class); } @Test diff --git a/server/src/test/java/io/spine/server/route/EventProducersTest.java b/server/src/test/java/io/spine/server/route/EventProducersTest.java deleted file mode 100644 index eb768274371..00000000000 --- a/server/src/test/java/io/spine/server/route/EventProducersTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2019, TeamDev. 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.route; - -import io.spine.base.EventMessage; -import io.spine.testing.Tests; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static io.spine.testing.DisplayNames.HAVE_PARAMETERLESS_CTOR; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -@DisplayName("EventProducers utility should") -class EventProducersTest { - - @Test - @DisplayName(HAVE_PARAMETERLESS_CTOR) - void haveUtilityConstructor() { - Tests.assertHasPrivateParameterlessCtor(EventProducers.class); - } - - @Test - @DisplayName("create function for taking ID from context") - void getIdFromContext() { - EventRoute fn = EventRoute.byProducerId(); - assertFunction(fn); - } - - @Test - @DisplayName("create function for getting ID from first message field") - void getIdFromFirstMsgField() { - EventRoute fn = EventRoute.byFirstMessageField(); - assertFunction(fn); - } - - private static void assertFunction(EventRoute fn) { - assertNotNull(fn); - - // Check that custom toString() is provided. - assertFalse(fn.toString().contains(EventRoute.class.getSimpleName())); - } -} diff --git a/server/src/test/java/io/spine/server/route/StateUpdateRoutingTest.java b/server/src/test/java/io/spine/server/route/StateUpdateRoutingTest.java index 19c58c698be..92bd730d980 100644 --- a/server/src/test/java/io/spine/server/route/StateUpdateRoutingTest.java +++ b/server/src/test/java/io/spine/server/route/StateUpdateRoutingTest.java @@ -38,11 +38,13 @@ @DisplayName("StateUpdateRouting should") class StateUpdateRoutingTest { + private static final EventContext emptyContext = EventContext.getDefaultInstance(); + @Test @DisplayName("not accept nulls") void notAcceptNulls() { new NullPointerTester() - .setDefault(EventContext.class, EventContext.getDefaultInstance()) + .setDefault(EventContext.class, emptyContext) .testAllPublicInstanceMethods(StateUpdateRouting.newInstance()); } @@ -50,12 +52,10 @@ void notAcceptNulls() { @DisplayName("skip all messages be default") void routeNothingByDefault() { StateUpdateRouting routing = StateUpdateRouting.newInstance(); - Set emptyTargets = routing.apply(Empty.getDefaultInstance(), - EventContext.getDefaultInstance()); + Set emptyTargets = routing.apply(Empty.getDefaultInstance(), emptyContext); assertThat(emptyTargets).isEmpty(); - Set logTargets = routing.apply(LogState.getDefaultInstance(), - EventContext.getDefaultInstance()); + Set logTargets = routing.apply(LogState.getDefaultInstance(), emptyContext); assertThat(logTargets).isEmpty(); } @@ -72,7 +72,7 @@ void routeMessagesByRoutes() { .newBuilder() .putCounters(counterKey, counter) .build(); - Set targets = routing.apply(log, EventContext.getDefaultInstance()); + Set targets = routing.apply(log, emptyContext); assertThat(targets).containsExactly(counter); } @@ -95,7 +95,7 @@ void createEventRoute() { .setWhen(currentTime()) .build(); EventRoute eventRoute = routing.eventRoute(); - Set targets = eventRoute.apply(event, EventContext.getDefaultInstance()); + Set targets = eventRoute.apply(event, emptyContext); assertThat(targets).containsExactly(counter); } } diff --git a/server/src/test/java/io/spine/server/route/given/switchman/Log.java b/server/src/test/java/io/spine/server/route/given/switchman/Log.java index 6ebeb5849d0..9761f3b1577 100644 --- a/server/src/test/java/io/spine/server/route/given/switchman/Log.java +++ b/server/src/test/java/io/spine/server/route/given/switchman/Log.java @@ -26,6 +26,7 @@ import io.spine.server.aggregate.AggregateRepository; import io.spine.server.aggregate.Apply; import io.spine.server.event.React; +import io.spine.server.route.EventRouting; import io.spine.server.route.given.switchman.event.SwitchPositionConfirmed; import io.spine.server.route.given.switchman.event.SwitchWorkRecorded; import io.spine.server.route.given.switchman.event.SwitchmanAbsenceRecorded; @@ -85,9 +86,9 @@ public Repository() { } @Override - public void onRegistered() { - super.onRegistered(); - eventRouting().replaceDefault((message, context) -> SINGLETON_ID_SET); + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.replaceDefault((event, ctx) -> SINGLETON_ID_SET); } } } diff --git a/server/src/test/java/io/spine/server/route/given/switchman/SwitchmanBureau.java b/server/src/test/java/io/spine/server/route/given/switchman/SwitchmanBureau.java index 8e3c3cc5013..e09ec37a0e7 100644 --- a/server/src/test/java/io/spine/server/route/given/switchman/SwitchmanBureau.java +++ b/server/src/test/java/io/spine/server/route/given/switchman/SwitchmanBureau.java @@ -23,6 +23,7 @@ import io.spine.core.CommandContext; import io.spine.server.aggregate.AggregateRepository; import io.spine.server.route.CommandRoute; +import io.spine.server.route.CommandRouting; import io.spine.server.route.given.switchman.command.SetSwitch; import io.spine.server.route.given.switchman.rejection.SwitchmanUnavailable; @@ -30,17 +31,16 @@ * A repository which fires a rejection in response to a command with a particular value of the * target aggregate ID. */ -@SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") public final class SwitchmanBureau extends AggregateRepository { /** The ID of the aggregate for which a {@link SetSwitch command} would be rejected. */ public static final String MISSING_SWITCHMAN_NAME = "Petrovich"; - @SuppressWarnings("ResultOfMethodCallIgnored") - // Can ignore the value since we're calling own builder-like method. - public SwitchmanBureau() { - super(); - commandRouting().route(SetSwitch.class, new CommandRoute() { + @SuppressWarnings("SerializableInnerClassWithNonSerializableOuterClass") + @Override + protected void setupCommandRouting(CommandRouting routing) { + super.setupCommandRouting(routing); + routing.route(SetSwitch.class, new CommandRoute() { private static final long serialVersionUID = 0L; @Override diff --git a/server/src/test/java/io/spine/server/stand/StandTest.java b/server/src/test/java/io/spine/server/stand/StandTest.java index ad0346fe2c0..bdf657cc0bb 100644 --- a/server/src/test/java/io/spine/server/stand/StandTest.java +++ b/server/src/test/java/io/spine/server/stand/StandTest.java @@ -131,7 +131,6 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; diff --git a/server/src/test/java/io/spine/server/stand/given/Given.java b/server/src/test/java/io/spine/server/stand/given/Given.java index e83fd01acc2..f0bd0fe0bd8 100644 --- a/server/src/test/java/io/spine/server/stand/given/Given.java +++ b/server/src/test/java/io/spine/server/stand/given/Given.java @@ -27,6 +27,7 @@ import io.spine.server.projection.Projection; import io.spine.server.projection.ProjectionRepository; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.test.projection.Project; import io.spine.test.projection.ProjectId; import io.spine.test.projection.ProjectVBuilder; @@ -58,9 +59,10 @@ public Set apply(PrjProjectCreated message, EventContext context) { } }; - public StandTestProjectionRepository() { - super(); - eventRouting().route(PrjProjectCreated.class, EVENT_TARGETS_FN); + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(PrjProjectCreated.class, EVENT_TARGETS_FN); } @Override @@ -69,7 +71,7 @@ public EntityLifecycle lifecycleOf(ProjectId id) { } } - public static class StandTestProjection + public static final class StandTestProjection extends Projection { public StandTestProjection(ProjectId id) { diff --git a/server/src/test/java/io/spine/system/server/EntityHistoryTest.java b/server/src/test/java/io/spine/system/server/EntityHistoryTest.java index d912169edcf..50e78d496d9 100644 --- a/server/src/test/java/io/spine/system/server/EntityHistoryTest.java +++ b/server/src/test/java/io/spine/system/server/EntityHistoryTest.java @@ -77,7 +77,7 @@ class EntityHistoryTest { @BeforeEach void setUp() { BoundedContextName contextName = BoundedContextName - .vBuilder() + .newBuilder() .setValue(EntityHistoryTest.class.getSimpleName()) .build(); context = BoundedContext @@ -116,13 +116,13 @@ void entityCreated() { checkCommandDispatchedToAggregateHandler(); checkEntityCreated(AGGREGATE, PersonAggregate.TYPE); - checkEntityStateChanged(Person.vBuilder() + checkEntityStateChanged(Person.newBuilder() .setId(id) .setName(PersonName.getDefaultInstance()) .build()); checkEventDispatchedToSubscriber(); checkEntityCreated(PROJECTION, PersonProjection.TYPE); - checkEntityStateChanged(PersonDetails.vBuilder() + checkEntityStateChanged(PersonDetails.newBuilder() .setId(id) .setName(PersonName.getDefaultInstance()) .build()); @@ -152,7 +152,7 @@ void unArchivedAndUnDeleted() { eventAccumulator.forgetEvents(); ExposePerson command = ExposePerson - .vBuilder() + .newBuilder() .setId(id) .build(); postCommand(command); @@ -190,7 +190,7 @@ void commandToPart() { @DisplayName("command is dispatched to handler in procman") void commandToPm() { CommandMessage startCommand = StartPersonCreation - .vBuilder() + .newBuilder() .setId(id) .build(); postCommand(startCommand); @@ -206,7 +206,7 @@ void commandToPm() { eventAccumulator.forgetEvents(); CommandMessage domainCommand = CompletePersonCreation - .vBuilder() + .newBuilder() .setId(id) .build(); postCommand(domainCommand); @@ -256,7 +256,7 @@ void eventToReactorInAggregate() { eventAccumulator.forgetEvents(); RenamePerson domainCommand = RenamePerson - .vBuilder() + .newBuilder() .setId(id) .setNewFirstName("Paul") .build(); @@ -276,7 +276,7 @@ void eventToReactorInAggregate() { private void createPerson() { CreatePerson command = CreatePerson - .vBuilder() + .newBuilder() .setId(id) .build(); postCommand(command); @@ -285,7 +285,7 @@ private void createPerson() { @CanIgnoreReturnValue private HidePerson hidePerson() { HidePerson command = HidePerson - .vBuilder() + .newBuilder() .setId(id) .build(); postCommand(command); @@ -295,7 +295,7 @@ private HidePerson hidePerson() { @CanIgnoreReturnValue private CreatePersonName createPersonName() { CreatePersonName domainCommand = CreatePersonName - .vBuilder() + .newBuilder() .setId(id) .setFirstName("Ringo") .build(); diff --git a/server/src/test/java/io/spine/system/server/given/entity/PersonAggregate.java b/server/src/test/java/io/spine/system/server/given/entity/PersonAggregate.java index 27953c55d89..17d77be4c89 100644 --- a/server/src/test/java/io/spine/system/server/given/entity/PersonAggregate.java +++ b/server/src/test/java/io/spine/system/server/given/entity/PersonAggregate.java @@ -51,7 +51,7 @@ protected PersonAggregate(PersonId id) { @Assign PersonCreated handle(CreatePerson command) { return PersonCreated - .vBuilder() + .newBuilder() .setId(command.getId()) .setName(command.getName()) .build(); @@ -60,7 +60,7 @@ PersonCreated handle(CreatePerson command) { @Assign PersonHidden handle(HidePerson command) { return PersonHidden - .vBuilder() + .newBuilder() .setId(command.getId()) .build(); } @@ -68,7 +68,7 @@ PersonHidden handle(HidePerson command) { @Assign PersonExposed handle(ExposePerson command) { return PersonExposed - .vBuilder() + .newBuilder() .setId(command.getId()) .build(); } @@ -76,7 +76,7 @@ PersonExposed handle(ExposePerson command) { @Assign PersonRenamed handle(RenamePerson command) { return PersonRenamed - .vBuilder() + .newBuilder() .setId(command.getId()) .setNewFirstName(command.getNewFirstName()) .build(); diff --git a/server/src/test/java/io/spine/system/server/given/entity/PersonNamePart.java b/server/src/test/java/io/spine/system/server/given/entity/PersonNamePart.java index 639d7850aa1..2dbf8a24a69 100644 --- a/server/src/test/java/io/spine/system/server/given/entity/PersonNamePart.java +++ b/server/src/test/java/io/spine/system/server/given/entity/PersonNamePart.java @@ -53,7 +53,7 @@ Nothing reactOn(PersonRenamed event) { @Assign PersonNameCreated handle(CreatePersonName command) { return PersonNameCreated - .vBuilder() + .newBuilder() .setId(command.getId()) .setFirstName(command.getFirstName()) .build(); diff --git a/server/src/test/java/io/spine/system/server/given/entity/PersonProcman.java b/server/src/test/java/io/spine/system/server/given/entity/PersonProcman.java index dadf1e1ad0e..08219d46a3f 100644 --- a/server/src/test/java/io/spine/system/server/given/entity/PersonProcman.java +++ b/server/src/test/java/io/spine/system/server/given/entity/PersonProcman.java @@ -50,7 +50,7 @@ public final class PersonProcman PersonCreationStarted handle(StartPersonCreation command) { builder().setId(command.getId()); return PersonCreationStarted - .vBuilder() + .newBuilder() .setId(command.getId()) .build(); } @@ -60,7 +60,7 @@ PersonCreationCompleted handle(CompletePersonCreation command) { builder().setId(command.getId()) .setCreated(true); return PersonCreationCompleted - .vBuilder() + .newBuilder() .setId(command.getId()) .build(); } diff --git a/server/src/test/java/io/spine/system/server/given/schedule/TestCommandScheduler.java b/server/src/test/java/io/spine/system/server/given/schedule/TestCommandScheduler.java index 4f7ac84b0f1..5fc08bd8efc 100644 --- a/server/src/test/java/io/spine/system/server/given/schedule/TestCommandScheduler.java +++ b/server/src/test/java/io/spine/system/server/given/schedule/TestCommandScheduler.java @@ -43,7 +43,7 @@ protected void doSchedule(Command command) { public void assertScheduled(Command command) { // System properties are modified by the framework. boolean found = scheduledCommands.stream() - .map(cmd -> cmd.toVBuilder() + .map(cmd -> cmd.toBuilder() .clearSystemProperties() .build()) .anyMatch(command::equals); diff --git a/server/src/test/proto/spine/test/command/commands.proto b/server/src/test/proto/spine/test/command/commands.proto index 4898bb2c757..9769013cfca 100644 --- a/server/src/test/proto/spine/test/command/commands.proto +++ b/server/src/test/proto/spine/test/command/commands.proto @@ -94,3 +94,7 @@ message CmdSetTaskDescription { TaskId task_id = 1; string description = 3; } + +// Invalid command message which does not contain any fields. +message CmdEmpty { +} diff --git a/server/src/test/proto/spine/test/procman/cmd_routing_test.proto b/server/src/test/proto/spine/test/procman/cmd_routing_test.proto new file mode 100644 index 00000000000..04acf62af19 --- /dev/null +++ b/server/src/test/proto/spine/test/procman/cmd_routing_test.proto @@ -0,0 +1,36 @@ +/* + * Copyright 2019, TeamDev. 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. + */ +syntax = "proto3"; + +package spine.test.procman; + +import "spine/options.proto"; + +option (type_url_prefix) = "type.spine.io"; +option java_package="io.spine.test.procman"; +option java_multiple_files = true; + +message ProcessId { + string uuid = 1; +} + +message ProcessState { + ProcessId id = 1 [(required) = false]; +} diff --git a/server/src/test/proto/spine/test/procman/commands.proto b/server/src/test/proto/spine/test/procman/commands.proto index 713a1d64001..cf8c626caf5 100644 --- a/server/src/test/proto/spine/test/procman/commands.proto +++ b/server/src/test/proto/spine/test/procman/commands.proto @@ -80,3 +80,7 @@ message PmScheduleRetrospective { message PmPlanIteration { ProjectId project_id = 1; } + +message PmCompleteProject { + ProjectId project_id = 1; +} diff --git a/testutil-client/src/main/java/io/spine/testing/client/TestActorRequestFactory.java b/testutil-client/src/main/java/io/spine/testing/client/TestActorRequestFactory.java index f079f940523..a4bb59d97be 100644 --- a/testutil-client/src/main/java/io/spine/testing/client/TestActorRequestFactory.java +++ b/testutil-client/src/main/java/io/spine/testing/client/TestActorRequestFactory.java @@ -166,13 +166,13 @@ public Command createCommand(CommandMessage message, Timestamp timestamp) { private static Command withTimestamp(Command command, Timestamp timestamp) { CommandContext origin = command.context(); ActorContext withTime = origin.getActorContext() - .toVBuilder() + .toBuilder() .setTimestamp(timestamp) .build(); - CommandContext resultContext = origin.toVBuilder() + CommandContext resultContext = origin.toBuilder() .setActorContext(withTime) .build(); - Command result = command.toVBuilder() + Command result = command.toBuilder() .setContext(resultContext) .build(); return result; @@ -191,7 +191,7 @@ public Command generateCommand() { @SuppressWarnings("MagicNumber") String randomSuffix = String.format("%04d", TestValues.random(10_000)); TestCommandMessage msg = TestCommandMessage - .vBuilder() + .newBuilder() .setId("random-number-" + randomSuffix) .build(); return createCommand(msg); diff --git a/testutil-core/src/main/java/io/spine/testing/core/given/GivenCommandContext.java b/testutil-core/src/main/java/io/spine/testing/core/given/GivenCommandContext.java index 57e71782975..d86d326c0a7 100644 --- a/testutil-core/src/main/java/io/spine/testing/core/given/GivenCommandContext.java +++ b/testutil-core/src/main/java/io/spine/testing/core/given/GivenCommandContext.java @@ -25,7 +25,6 @@ import io.spine.core.ActorContext; import io.spine.core.CommandContext; import io.spine.core.CommandContext.Schedule; -import io.spine.core.CommandContextVBuilder; import io.spine.core.TenantId; import io.spine.core.UserId; @@ -59,12 +58,12 @@ public static CommandContext withRandomActor() { */ public static CommandContext withActorAndTime(UserId actor, Timestamp when) { TenantId tenantId = GivenTenantId.newUuid(); - ActorContext actorContext = ActorContext.vBuilder() + ActorContext actorContext = ActorContext.newBuilder() .setActor(actor) .setTimestamp(when) .setTenantId(tenantId) .build(); - CommandContext result = CommandContext.vBuilder() + CommandContext result = CommandContext.newBuilder() .setActorContext(actorContext) .build(); return result; @@ -78,7 +77,7 @@ public static CommandContext withActorAndTime(UserId actor, Timestamp when) { * @return a new {@code CommandContext} instance */ public static CommandContext withScheduledDelayOf(Duration delay) { - Schedule schedule = Schedule.vBuilder() + Schedule schedule = Schedule.newBuilder() .setDelay(delay) .build(); return withSchedule(schedule); @@ -92,8 +91,9 @@ public static CommandContext withScheduledDelayOf(Duration delay) { * @return a new {@code CommandContext} instance */ private static CommandContext withSchedule(Schedule schedule) { - CommandContextVBuilder builder = withRandomActor().toVBuilder() - .setSchedule(schedule); + CommandContext.Builder builder = + withRandomActor().toBuilder() + .setSchedule(schedule); return builder.build(); } } diff --git a/testutil-server/src/main/java/io/spine/testing/server/EventReactionTest.java b/testutil-server/src/main/java/io/spine/testing/server/EventReactionTest.java index 6b097c58f82..d4ccddf849c 100644 --- a/testutil-server/src/main/java/io/spine/testing/server/EventReactionTest.java +++ b/testutil-server/src/main/java/io/spine/testing/server/EventReactionTest.java @@ -79,10 +79,10 @@ protected EventReactorExpected expectThat(E entity) { protected final Event createEvent(M message) { Event event = eventFactory.createEvent(message); EventContext context = event.context() - .toVBuilder() + .toBuilder() .setExternal(externalMessage()) .build(); - return event.toVBuilder() + return event.toBuilder() .setContext(context) .build(); } diff --git a/testutil-server/src/main/java/io/spine/testing/server/TestEventFactory.java b/testutil-server/src/main/java/io/spine/testing/server/TestEventFactory.java index a0bf63cbd4d..cef2f732c8d 100644 --- a/testutil-server/src/main/java/io/spine/testing/server/TestEventFactory.java +++ b/testutil-server/src/main/java/io/spine/testing/server/TestEventFactory.java @@ -93,10 +93,10 @@ public Event createEvent(EventMessage message, @Nullable Version version, Timest checkNotNull(atTime); Event event = createEvent(message, version); EventContext context = event.context() - .toVBuilder() + .toBuilder() .setTimestamp(atTime) .build(); - Event result = event.toVBuilder() + Event result = event.toBuilder() .setContext(context) .build(); return result; diff --git a/testutil-server/src/main/java/io/spine/testing/server/projection/ProjectionTest.java b/testutil-server/src/main/java/io/spine/testing/server/projection/ProjectionTest.java index 4b09b61ab3d..0eb348e5ec7 100644 --- a/testutil-server/src/main/java/io/spine/testing/server/projection/ProjectionTest.java +++ b/testutil-server/src/main/java/io/spine/testing/server/projection/ProjectionTest.java @@ -60,11 +60,11 @@ protected List dispatchTo(P entity) { Event sourceEvent = createEvent(); EventContext context = sourceEvent.context() - .toVBuilder() + .toBuilder() .setEnrichment(enrichment()) .build(); Event enrichedEvent = - sourceEvent.toVBuilder() + sourceEvent.toBuilder() .setContext(context) .build(); dispatch(entity, enrichedEvent); diff --git a/testutil-server/src/test/java/io/spine/testing/server/CommandSubjectTest.java b/testutil-server/src/test/java/io/spine/testing/server/CommandSubjectTest.java index 04bf4f4969b..f203ee2b952 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/CommandSubjectTest.java +++ b/testutil-server/src/test/java/io/spine/testing/server/CommandSubjectTest.java @@ -54,7 +54,7 @@ CommandSubject assertWithSubjectThat(Iterable messages) { Command createMessage() { return newCommand( TuAddComment - .vBuilder() + .newBuilder() .setId(generateTaskId()) .build() ); @@ -64,7 +64,7 @@ Command createMessage() { Command createAnotherMessage() { return newCommand( TuCreateTask - .vBuilder() + .newBuilder() .setId(generateTaskId()) .build() ); @@ -76,7 +76,7 @@ private static Command newCommand(CommandMessage msg) { private static TuTaskId generateTaskId() { return TuTaskId - .vBuilder() + .newBuilder() .setValue(newUuid()) .build(); } diff --git a/testutil-server/src/test/java/io/spine/testing/server/EventSubjectTest.java b/testutil-server/src/test/java/io/spine/testing/server/EventSubjectTest.java index b6dff063ff9..3914ccc4056 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/EventSubjectTest.java +++ b/testutil-server/src/test/java/io/spine/testing/server/EventSubjectTest.java @@ -51,7 +51,7 @@ EventSubject assertWithSubjectThat(Iterable messages) { Event createMessage() { TuTaskId taskId = generateTaskId(); TuCommentAdded event = TuCommentAdded - .vBuilder() + .newBuilder() .setId(taskId) .build(); return newEvent(event); @@ -61,7 +61,7 @@ Event createMessage() { Event createAnotherMessage() { TuTaskId taskId = generateTaskId(); TuTaskCreated event = TuTaskCreated - .vBuilder() + .newBuilder() .setId(taskId) .build(); return newEvent(event); @@ -73,7 +73,7 @@ private static Event newEvent(EventMessage msg) { private static TuTaskId generateTaskId() { return TuTaskId - .vBuilder() + .newBuilder() .setValue(newUuid()) .build(); } diff --git a/testutil-server/src/test/java/io/spine/testing/server/blackbox/BlackBoxBoundedContextTest.java b/testutil-server/src/test/java/io/spine/testing/server/blackbox/BlackBoxBoundedContextTest.java index bb3bd220d5d..94ebe246b3a 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/blackbox/BlackBoxBoundedContextTest.java +++ b/testutil-server/src/test/java/io/spine/testing/server/blackbox/BlackBoxBoundedContextTest.java @@ -494,7 +494,7 @@ void deletedFlag() { @DisplayName("state subject") void stateSubject() { BbInit expectedState = BbInit - .vBuilder() + .newBuilder() .setId(id) .setInitialized(true) .build(); diff --git a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbInitProcess.java b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbInitProcess.java index f8ac35551b6..777529735eb 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbInitProcess.java +++ b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbInitProcess.java @@ -51,14 +51,14 @@ public final class BbInitProcess extends ProcessManager> on(BbInitProject cmd) { builder().setId(cmd.getProjectId()); BbAssignTeam assignTeam = BbAssignTeam - .vBuilder() + .newBuilder() .setProjectId(cmd.getProjectId()) .addAllMember(cmd.getMemberList()) .build(); @Nullable BbAssignScrumMaster assignScrumMaster = cmd.hasScrumMaster() ? BbAssignScrumMaster - .vBuilder() + .newBuilder() .setProjectId(cmd.getProjectId()) .setScrumMaster(cmd.getScrumMaster()) .build() @@ -74,12 +74,12 @@ Pair on(BbAssignTeam cmd) { setDeleted(true); return Pair.of( BbTeamAssigned - .vBuilder() + .newBuilder() .setProjectId(cmd.getProjectId()) .addAllMember(cmd.getMemberList()) .build(), BbProjectInitialized - .vBuilder() + .newBuilder() .setProjectId(cmd.getProjectId()) .build() ); @@ -88,7 +88,7 @@ Pair on(BbAssignTeam cmd) { @Assign BbScrumMasterAssigned on(BbAssignScrumMaster cmd) { return BbScrumMasterAssigned - .vBuilder() + .newBuilder() .setProjectId(cmd.getProjectId()) .setScrumMaster(cmd.getScrumMaster()) .build(); diff --git a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectAggregate.java b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectAggregate.java index f3a1534703b..67f840891e3 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectAggregate.java +++ b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectAggregate.java @@ -55,7 +55,7 @@ final class BbProjectAggregate extends Aggregate on(BbUserDeleted event) { private BbAssigneeRemoved userUnassigned(UserId user) { return BbAssigneeRemoved - .vBuilder() + .newBuilder() .setId(id()) .setUserId(user) .build(); diff --git a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectRepository.java b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectRepository.java index b35a05a4bf1..4d90073a2d3 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectRepository.java +++ b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbProjectRepository.java @@ -21,6 +21,7 @@ package io.spine.testing.server.blackbox.given; import io.spine.server.aggregate.AggregateRepository; +import io.spine.server.route.EventRouting; import io.spine.testing.server.blackbox.BbProjectId; import io.spine.testing.server.blackbox.event.BbUserDeleted; @@ -29,8 +30,10 @@ public final class BbProjectRepository extends AggregateRepository { - public BbProjectRepository() { - eventRouting().route(BbUserDeleted.class, - (event, context) -> new HashSet<>(event.getProjectList())); + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(BbUserDeleted.class, + (event, context) -> new HashSet<>(event.getProjectList())); } } diff --git a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbReportRepository.java b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbReportRepository.java index dda1b0fe318..27045d1c24e 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbReportRepository.java +++ b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/BbReportRepository.java @@ -23,6 +23,7 @@ import io.spine.server.aggregate.AggregateRepository; import io.spine.server.entity.AbstractEntity; import io.spine.server.route.EventRoute; +import io.spine.server.route.EventRouting; import io.spine.testing.server.blackbox.BbProjectId; import io.spine.testing.server.blackbox.BbReportId; import io.spine.testing.server.blackbox.event.BbTaskAdded; @@ -41,8 +42,10 @@ public final class BbReportRepository extends AggregateRepository aggregates = newArrayList(); - public BbReportRepository() { - eventRouting().route(BbTaskAdded.class, (EventRoute) + @Override + protected void setupEventRouting(EventRouting routing) { + super.setupEventRouting(routing); + routing.route(BbTaskAdded.class, (EventRoute) (event, context) -> getReportsContainingProject(event.getProjectId())); } diff --git a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/Given.java b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/Given.java index 731650ffcc8..6e780fb4cc4 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/Given.java +++ b/testutil-server/src/test/java/io/spine/testing/server/blackbox/given/Given.java @@ -33,7 +33,6 @@ import io.spine.testing.server.blackbox.command.BbCreateProject; import io.spine.testing.server.blackbox.command.BbCreateReport; import io.spine.testing.server.blackbox.command.BbInitProject; -import io.spine.testing.server.blackbox.command.BbInitProjectVBuilder; import io.spine.testing.server.blackbox.command.BbRegisterCommandDispatcher; import io.spine.testing.server.blackbox.command.BbStartProject; import io.spine.testing.server.blackbox.event.BbEventDispatcherRegistered; @@ -57,7 +56,7 @@ public static BbProjectId newProjectId() { public static BbAddTask addTask(BbProjectId projectId) { return BbAddTask - .vBuilder() + .newBuilder() .setProjectId(projectId) .setTask(newTask()) .build(); @@ -65,7 +64,7 @@ public static BbAddTask addTask(BbProjectId projectId) { public static BbTaskAdded taskAdded(BbProjectId projectId) { return BbTaskAdded - .vBuilder() + .newBuilder() .setProjectId(projectId) .setTask(newTask()) .build(); @@ -73,14 +72,14 @@ public static BbTaskAdded taskAdded(BbProjectId projectId) { private static BbTask newTask() { return BbTask - .vBuilder() + .newBuilder() .setTitle(newUuid()) .build(); } public static BbCreateReport createReport(BbProjectId projectId) { return BbCreateReport - .vBuilder() + .newBuilder() .setReportId(newReportId()) .addProjectId(projectId) .build(); @@ -88,7 +87,7 @@ public static BbCreateReport createReport(BbProjectId projectId) { private static BbReportId newReportId() { return BbReportId - .vBuilder() + .newBuilder() .setId(newUuid()) .build(); } @@ -96,7 +95,7 @@ private static BbReportId newReportId() { public static BbRegisterCommandDispatcher registerCommandDispatcher(Class dispatcherName) { return BbRegisterCommandDispatcher - .vBuilder() + .newBuilder() .setDispatcherName(dispatcherName.getName()) .build(); } @@ -105,7 +104,7 @@ private static BbReportId newReportId() { eventDispatcherRegistered(Class dispatcherClass) { String name = dispatcherClass.getName(); BbEventDispatcherRegistered result = BbEventDispatcherRegistered - .vBuilder() + .newBuilder() .setDispatcherName(name) .build(); return result; @@ -116,21 +115,21 @@ public static BbCreateProject createProject() { } private static UserId generateUserId() { - return UserId.vBuilder() + return UserId.newBuilder() .setValue(TestValues.randomString()) .build(); } public static BbCreateProject createProject(BbProjectId id) { return BbCreateProject - .vBuilder() + .newBuilder() .setProjectId(id) .build(); } public static BbInitProject initProject(BbProjectId id, boolean scrum) { - BbInitProjectVBuilder builder = BbInitProject - .vBuilder() + BbInitProject.Builder builder = BbInitProject + .newBuilder() .setProjectId(id); // Generate a random team. IntStream.range(0, TestValues.random(1, 10)) @@ -143,14 +142,14 @@ public static BbInitProject initProject(BbProjectId id, boolean scrum) { public static BbStartProject startProject(BbProjectId id) { return BbStartProject - .vBuilder() + .newBuilder() .setProjectId(id) .build(); } public static BbProject createdProjectState(BbCreateProject createProject) { return BbProject - .vBuilder() + .newBuilder() .setId(createProject.getProjectId()) .setStatus(BbProject.Status.CREATED) .build(); @@ -158,7 +157,7 @@ public static BbProject createdProjectState(BbCreateProject createProject) { public static BbAssignProject addProjectAssignee(BbProjectId projectId, UserId id) { return BbAssignProject - .vBuilder() + .newBuilder() .setId(projectId) .setUserId(id) .build(); @@ -166,7 +165,7 @@ public static BbAssignProject addProjectAssignee(BbProjectId projectId, UserId i public static BbUserDeleted userDeleted(UserId id, BbProjectId... projectIds) { return BbUserDeleted - .vBuilder() + .newBuilder() .setId(id) .addAllProject(newArrayList(projectIds)) .build(); diff --git a/testutil-server/src/test/java/io/spine/testing/server/entity/EntitySubjectTest.java b/testutil-server/src/test/java/io/spine/testing/server/entity/EntitySubjectTest.java index 0841c961294..033c79036b1 100644 --- a/testutil-server/src/test/java/io/spine/testing/server/entity/EntitySubjectTest.java +++ b/testutil-server/src/test/java/io/spine/testing/server/entity/EntitySubjectTest.java @@ -51,7 +51,7 @@ class EntitySubjectTest extends SubjectTest> { void setUp() { BbProjectId id = BbProjectId.generate(); BbProjectView state = BbProjectView - .vBuilder() + .newBuilder() .setId(id) .build(); entity = Given.projectionOfClass(ProjectView.class) diff --git a/version.gradle b/version.gradle index 6d9ca6e231a..a50cdbe425c 100644 --- a/version.gradle +++ b/version.gradle @@ -25,11 +25,11 @@ * as we want to manage the versions in a single source. */ -def final SPINE_VERSION = '1.0.0-pre7' +def final SPINE_VERSION = '1.0.0-SNAPSHOT' ext { // The version of the modules in this project. - versionToPublish = '1.0.0-SNAPSHOT' + versionToPublish = SPINE_VERSION // Depend on `base` for the general definitions and a model compiler. spineBaseVersion = SPINE_VERSION