From 8f0b0948ca240176bb02e8b74263d771927f3896 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 10 Aug 2016 18:06:41 +0300 Subject: [PATCH 001/361] Extract gRPC server from the ClientService. --- .../examples/aggregate/server/Server.java | 23 +- .../java/org/spine3/server/ClientService.java | 108 +-------- .../server/transport/GrpcContainer.java | 220 ++++++++++++++++++ .../spine3/server/transport/package-info.java | 32 +++ .../spine3/server/ClientServiceShould.java | 93 +------- .../server/transport/GrpcContainerShould.java | 139 +++++++++++ 6 files changed, 416 insertions(+), 199 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/transport/GrpcContainer.java create mode 100644 server/src/main/java/org/spine3/server/transport/package-info.java create mode 100644 server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java diff --git a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java index c9fe0e7e838..3f21f283a5f 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java @@ -26,6 +26,7 @@ import org.spine3.server.event.EventSubscriber; import org.spine3.server.storage.StorageFactory; import org.spine3.server.storage.memory.InMemoryStorageFactory; +import org.spine3.server.transport.GrpcContainer; import java.io.IOException; @@ -39,7 +40,7 @@ */ public class Server { - private final ClientService clientService; + private final GrpcContainer grpcContainer; private final BoundedContext boundedContext; public Server(StorageFactory storageFactory) { @@ -53,26 +54,32 @@ public Server(StorageFactory storageFactory) { // Subscribe an event subscriber in the bounded context. final EventSubscriber eventLogger = new EventLogger(); - boundedContext.getEventBus().subscribe(eventLogger); + boundedContext.getEventBus() + .subscribe(eventLogger); // Create a client service with this bounded context. - this.clientService = ClientService.newBuilder() - .addBoundedContext(boundedContext) + final ClientService clientService = ClientService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + + // Create a gRPC server and schedule the client service instance for deployment. + this.grpcContainer = GrpcContainer.newBuilder() .setPort(DEFAULT_CLIENT_SERVICE_PORT) + .addService(clientService) .build(); } public void start() throws IOException { - clientService.start(); - clientService.addShutdownHook(); + grpcContainer.start(); + grpcContainer.addShutdownHook(); } public void awaitTermination() { - clientService.awaitTermination(); + grpcContainer.awaitTermination(); } public void shutdown() throws Exception { - clientService.shutdown(); + grpcContainer.shutdown(); boundedContext.close(); } diff --git a/server/src/main/java/org/spine3/server/ClientService.java b/server/src/main/java/org/spine3/server/ClientService.java index 1797d36da44..7728c93007a 100644 --- a/server/src/main/java/org/spine3/server/ClientService.java +++ b/server/src/main/java/org/spine3/server/ClientService.java @@ -20,11 +20,8 @@ package org.spine3.server; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; -import io.grpc.ServerBuilder; -import io.grpc.ServerServiceDefinition; import io.grpc.stub.StreamObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,115 +29,32 @@ import org.spine3.base.Event; import org.spine3.base.Response; import org.spine3.base.Responses; -import org.spine3.client.ConnectionConstants; import org.spine3.client.grpc.Topic; import org.spine3.server.command.error.CommandException; import org.spine3.server.command.error.UnsupportedCommandException; import org.spine3.server.type.CommandClass; -import javax.annotation.Nullable; -import java.io.IOException; import java.util.Set; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Throwables.propagate; - /** * The {@code ClientService} allows client applications to post commands and * receive updates from the application backend. * * @author Alexander Yevsyukov */ -public class ClientService extends org.spine3.client.grpc.ClientServiceGrpc.ClientServiceImplBase { - - private static final String SERVICE_NOT_STARTED_MSG = "Service was not started or is shutdown already."; - - private final int port; +public class ClientService + extends org.spine3.client.grpc.ClientServiceGrpc.ClientServiceImplBase { private final ImmutableMap boundedContextMap; - @Nullable - private io.grpc.Server grpcServer; - public static Builder newBuilder() { return new Builder(); } protected ClientService(Builder builder) { - this.port = builder.getPort(); this.boundedContextMap = builder.getBoundedContextMap(); } - /** - * Starts the service. - * - * @throws IOException if unable to bind - */ - public void start() throws IOException { - checkState(grpcServer == null, "Service is started already."); - grpcServer = createGrpcServer(port); - grpcServer.start(); - } - - /** Waits for the service to become terminated. */ - public void awaitTermination() { - checkState(grpcServer != null, SERVICE_NOT_STARTED_MSG); - try { - grpcServer.awaitTermination(); - } catch (InterruptedException e) { - throw propagate(e); - } - } - - /** - * Makes the JVM shut down the service when it is shutting down itself. - * - *

Call this method when running the service in a separate JVM. - */ - public void addShutdownHook() { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - // Use stderr here since the logger may have been reset by its JVM shutdown hook. - @SuppressWarnings("UseOfSystemOutOrSystemErr") - @Override - public void run() { - final String serviceClass = ClientService.this.getClass().getName(); - try { - if (!isShutdown()) { - System.err.println("Shutting down " + serviceClass + " since JVM is shutting down..."); - shutdown(); - System.err.println(serviceClass + " shut down."); - } - } catch (RuntimeException e) { - e.printStackTrace(System.err); - } - } - })); - } - - /** - * Returns {@code true} if the service is shutdown or was not started at all, {@code false} otherwise. - * - * @see ClientService#shutdown() - */ - public boolean isShutdown() { - final boolean isShutdown = grpcServer == null; - return isShutdown; - } - - /** Initiates an orderly shutdown in which existing calls continue but new calls are rejected. */ - public void shutdown() { - checkState(grpcServer != null, SERVICE_NOT_STARTED_MSG); - grpcServer.shutdown(); - grpcServer = null; - } - - @VisibleForTesting - /* package */ io.grpc.Server createGrpcServer(int port) { - final ServerServiceDefinition service = bindService(); - final ServerBuilder builder = ServerBuilder.forPort(port) - .addService(service); - return builder.build(); - } @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. @Override @@ -150,7 +64,8 @@ public void post(Command request, StreamObserver responseObserver) { if (boundedContext == null) { handleUnsupported(request, responseObserver); } else { - boundedContext.getCommandBus().post(request, responseObserver); + boundedContext.getCommandBus() + .post(request, responseObserver); } } @@ -181,7 +96,6 @@ public static class Builder { private final Set boundedContexts = Sets.newHashSet(); private ImmutableMap boundedContextMap; - private int port = ConnectionConstants.DEFAULT_CLIENT_SERVICE_PORT; public Builder addBoundedContext(BoundedContext boundedContext) { // Save it to a temporary set so that it is easy to remove it if needed. @@ -194,14 +108,6 @@ public Builder removeBoundedContext(BoundedContext boundedContext) { return this; } - public Builder setPort(int port) { - this.port = port; - return this; - } - - public int getPort() { - return this.port; - } @SuppressWarnings("ReturnOfCollectionOrArrayField") // is immutable public ImmutableMap getBoundedContextMap() { @@ -210,7 +116,6 @@ public ImmutableMap getBoundedContextMap() { /** * Builds the {@link ClientService}. - * {@link ConnectionConstants#DEFAULT_CLIENT_SERVICE_PORT} is used by default. */ public ClientService build() { this.boundedContextMap = createBoundedContextMap(); @@ -227,8 +132,9 @@ private ImmutableMap createBoundedContextMap() { } private static void addBoundedContext(ImmutableMap.Builder mapBuilder, - BoundedContext boundedContext) { - final Set cmdClasses = boundedContext.getCommandBus().getSupportedCommandClasses(); + BoundedContext boundedContext) { + final Set cmdClasses = boundedContext.getCommandBus() + .getSupportedCommandClasses(); for (CommandClass commandClass : cmdClasses) { mapBuilder.put(commandClass, boundedContext); } diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java new file mode 100644 index 00000000000..777e879c9ef --- /dev/null +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -0,0 +1,220 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.transport; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import io.grpc.BindableService; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerServiceDefinition; +import org.spine3.client.ConnectionConstants; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Throwables.propagate; + +/** + * Wrapping container for gRPC server. + * + *

Maintains and deploys several of gRPC services within a single server. + *

Uses {@link ServerServiceDefinition}s of each service. + * + * @author Alex Tymchenko + */ +public class GrpcContainer { + + private static final String SERVER_NOT_STARTED_MSG = "gRPC server was not started or is shut down already."; + + private final int port; + private final ImmutableSet services; + + @Nullable + private io.grpc.Server grpcServer; + + + public static Builder newBuilder() { + return new Builder(); + } + + protected GrpcContainer(Builder builder) { + this.port = builder.getPort(); + this.services = builder.getServices(); + } + + + /** + * Starts the service. + * + * @throws IOException if unable to bind + */ + public void start() throws IOException { + checkState(grpcServer == null, "gRPC server is started already."); + grpcServer = createGrpcServer(); + grpcServer.start(); + } + + + /** + * Returns {@code true} if the server is shut down or was not started at all, {@code false} otherwise. + * + * @see GrpcContainer#shutdown() + */ + public boolean isShutdown() { + final boolean isShutdown = grpcServer == null; + return isShutdown; + } + + /** Initiates an orderly shutdown in which existing calls continue but new calls are rejected. */ + public void shutdown() { + checkState(grpcServer != null, SERVER_NOT_STARTED_MSG); + grpcServer.shutdown(); + grpcServer = null; + } + + + /** Waits for the service to become terminated. */ + public void awaitTermination() { + checkState(grpcServer != null, SERVER_NOT_STARTED_MSG); + try { + grpcServer.awaitTermination(); + } catch (InterruptedException e) { + throw propagate(e); + } + } + + + /** + * Check if the given gRPC service is scheduled for the deployment in this container. + * + *

Note, that the given gRPC service will become available to the clients, once the gRPC container is started. + *

To find out, whether the service is already available for calls, use {@link #isLive(BindableService)} method. + * + * @param service the gRPC service to check + * @return {@code true}, if the given gRPC service for deployment; {@code false} otherwise + */ + public boolean isScheduledForDeployment(BindableService service) { + final String nameOfInterest = service.bindService() + .getServiceDescriptor() + .getName(); + + boolean serviceIsPresent = false; + for (ServerServiceDefinition serverServiceDefinition : services) { + final String scheduledServiceName = serverServiceDefinition.getServiceDescriptor() + .getName(); + serviceIsPresent = serviceIsPresent || scheduledServiceName.equals(nameOfInterest); + } + return serviceIsPresent; + } + + /** + * Check if the given gRPC service has actually been deployed and is available for interaction within this container. + * + *

Returns {@code true} if and only if + *

a. the service has been previously scheduled for the deployment, + *

b. the container has been started. + * + * @param service the gRPC service + * @return {@code true}, if the service is available for interaction within this container; {@code false} otherwise + */ + public boolean isLive(BindableService service) { + return !isShutdown() && isScheduledForDeployment(service); + } + + /** + * Makes the JVM shut down the service when it is shutting down itself. + * + *

Call this method when running the service in a separate JVM. + */ + public void addShutdownHook() { + Runtime.getRuntime() + .addShutdownHook(new Thread(new Runnable() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + @SuppressWarnings("UseOfSystemOutOrSystemErr") + @Override + public void run() { + final String serverClass = GrpcContainer.this.getClass() + .getName(); + try { + if (!isShutdown()) { + System.err.println("Shutting down " + serverClass + " since JVM is shutting down..."); + shutdown(); + System.err.println(serverClass + " shut down."); + } + } catch (RuntimeException e) { + e.printStackTrace(System.err); + } + } + })); + } + + @VisibleForTesting + /* package */ Server createGrpcServer() { + final ServerBuilder builder = ServerBuilder.forPort(port); + for (ServerServiceDefinition service : services) { + builder.addService(service); + } + + return builder.build(); + } + + + public static class Builder { + + private int port = ConnectionConstants.DEFAULT_CLIENT_SERVICE_PORT; + private final Set serviceDefinitions = Sets.newHashSet(); + + public Builder setPort(int port) { + this.port = port; + return this; + } + + public int getPort() { + return this.port; + } + + public Builder addService(BindableService service) { + serviceDefinitions.add(service.bindService()); + return this; + } + + public Builder removeService(ServerServiceDefinition service) { + serviceDefinitions.remove(service); + return this; + } + + public ImmutableSet getServices() { + return ImmutableSet.copyOf(serviceDefinitions); + } + + public GrpcContainer build() { + return new GrpcContainer(this); + } + + } + + +} diff --git a/server/src/main/java/org/spine3/server/transport/package-info.java b/server/src/main/java/org/spine3/server/transport/package-info.java new file mode 100644 index 00000000000..c6e20a5ec24 --- /dev/null +++ b/server/src/main/java/org/spine3/server/transport/package-info.java @@ -0,0 +1,32 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * @author Alex Tymchenko + * + * This package contains classes and interfaces defining data transport options between Spine apps and the outer world. + * + *

Typical transport options include gRPC servers, Firebase adapters etc. + */ +@ParametersAreNonnullByDefault +package org.spine3.server.transport; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/server/src/test/java/org/spine3/server/ClientServiceShould.java b/server/src/test/java/org/spine3/server/ClientServiceShould.java index dda8606ed25..9d648a64320 100644 --- a/server/src/test/java/org/spine3/server/ClientServiceShould.java +++ b/server/src/test/java/org/spine3/server/ClientServiceShould.java @@ -23,7 +23,6 @@ import com.google.common.collect.Sets; import com.google.protobuf.StringValue; -import io.grpc.Server; import io.grpc.stub.StreamObserver; import org.junit.After; import org.junit.Before; @@ -53,19 +52,13 @@ import org.spine3.test.clientservice.event.TaskAdded; import org.spine3.testdata.TestCommandBusFactory; -import java.io.IOException; import java.util.List; import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; @@ -75,7 +68,6 @@ public class ClientServiceShould { private ClientService service; - private Server server; private final Set boundedContexts = Sets.newHashSet(); private BoundedContext projectsContext; @@ -103,15 +95,10 @@ public void setUp() { builder.addBoundedContext(context); } service = spy(builder.build()); - server = mock(Server.class); - doReturn(server).when(service).createGrpcServer(anyInt()); } @After public void tearDown() throws Exception { - if (!service.isShutdown()) { - service.shutdown(); - } for (BoundedContext boundedContext : boundedContexts) { boundedContext.close(); } @@ -138,85 +125,11 @@ public void return_error_if_command_is_unsupported() { service.post(unsupportedCmd, responseObserver); - final Throwable exception = responseObserver.getThrowable().getCause(); + final Throwable exception = responseObserver.getThrowable() + .getCause(); assertEquals(UnsupportedCommandException.class, exception.getClass()); } - @Test - public void start_server() throws IOException { - service.start(); - - verify(server).start(); - } - - @Test - public void throw_exception_if_started_already() throws IOException { - service.start(); - try { - service.start(); - } catch (IllegalStateException expected) { - return; - } - fail("Exception must be thrown."); - } - - @Test - public void await_termination() throws IOException, InterruptedException { - service.start(); - service.awaitTermination(); - - verify(server).awaitTermination(); - } - - @Test(expected = IllegalStateException.class) - public void throw_exception_if_call_await_termination_on_not_started_service() { - service.awaitTermination(); - } - - @Test - public void assure_service_is_shutdown() throws IOException { - service.start(); - service.shutdown(); - - assertTrue(service.isShutdown()); - } - - @Test - public void assure_service_was_not_started() throws IOException { - assertTrue(service.isShutdown()); - } - - @Test - public void assure_service_is_not_shut_down() throws IOException { - service.start(); - - assertFalse(service.isShutdown()); - } - - @Test - public void shutdown_itself() throws IOException, InterruptedException { - service.start(); - service.shutdown(); - - verify(server).shutdown(); - } - - @Test(expected = IllegalStateException.class) - public void throw_exception_if_call_shutdown_on_not_started_service() { - service.shutdown(); - } - - @Test - public void throw_exception_if_shutdown_already() throws IOException { - service.start(); - service.shutdown(); - try { - service.shutdown(); - } catch (IllegalStateException expected) { - return; - } - fail("Expected an exception."); - } /* * Stub repositories and aggregates @@ -247,7 +160,7 @@ public TaskAdded handle(AddTask cmd, CommandContext ctx) { @Assign public List handle(StartProject cmd, CommandContext ctx) { - final ProjectStarted message = Given.EventMessage.projectStarted(cmd.getProjectId()); + final ProjectStarted message = Given.EventMessage.projectStarted(cmd.getProjectId()); return newArrayList(message); } diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java new file mode 100644 index 00000000000..7b811dbc2dd --- /dev/null +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -0,0 +1,139 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.transport; + +import io.grpc.Server; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * @author Alex Tymchenko + */ +public class GrpcContainerShould { + private Server server; + private GrpcContainer grpcContainer; + + + @Before + public void setUp() { + final GrpcContainer.Builder builder = GrpcContainer.newBuilder(); + grpcContainer = spy(builder.build()); + + server = mock(Server.class); + doReturn(server).when(grpcContainer).createGrpcServer(); + } + + @After + public void tearDown() { + if (!grpcContainer.isShutdown()) { + grpcContainer.shutdown(); + } + } + + @Test + public void start_server() throws IOException { + grpcContainer.start(); + + verify(server).start(); + } + + @Test + public void throw_exception_if_started_already() throws IOException { + grpcContainer.start(); + try { + grpcContainer.start(); + } catch (IllegalStateException expected) { + return; + } + fail("Exception must be thrown."); + } + + @Test + public void await_termination() throws IOException, InterruptedException { + grpcContainer.start(); + grpcContainer.awaitTermination(); + + verify(server).awaitTermination(); + } + + @Test(expected = IllegalStateException.class) + public void throw_exception_if_call_await_termination_on_not_started_service() { + grpcContainer.awaitTermination(); + } + + @Test + public void assure_service_is_shutdown() throws IOException { + grpcContainer.start(); + grpcContainer.shutdown(); + + assertTrue(grpcContainer.isShutdown()); + } + + @Test + public void assure_service_was_not_started() throws IOException { + assertTrue(grpcContainer.isShutdown()); + } + + @Test + public void assure_service_is_not_shut_down() throws IOException { + grpcContainer.start(); + + assertFalse(grpcContainer.isShutdown()); + } + + @Test + public void shutdown_itself() throws IOException, InterruptedException { + grpcContainer.start(); + grpcContainer.shutdown(); + + verify(server).shutdown(); + } + + @Test(expected = IllegalStateException.class) + public void throw_exception_if_call_shutdown_on_not_started_service() { + grpcContainer.shutdown(); + } + + @Test + public void throw_exception_if_shutdown_already() throws IOException { + grpcContainer.start(); + grpcContainer.shutdown(); + try { + grpcContainer.shutdown(); + } catch (IllegalStateException expected) { + return; + } + fail("Expected an exception."); + } + +} From f5667d338ccab827adde89f1d24589407ac3a7aa Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 10 Aug 2016 18:08:21 +0300 Subject: [PATCH 002/361] Add jdk1.4 logging bindings for 'examples' module to see sample Client <-> Server interaction details. --- examples/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/build.gradle b/examples/build.gradle index f2d72c1026a..f72a6a21b85 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,7 +1,8 @@ - dependencies { compile project(path: ':client') compile project(path: ':server') + //Use jdk14 bindings for the sample application + compile group: 'org.slf4j', name: 'slf4j-jdk14', version: project.SLF4J_VERSION } sourceSets.main.java.srcDir GEN_MAIN_SPINE_DIR From a0a9b1d005cc344d00725e55a85ea72572dfc223 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:27:17 +0300 Subject: [PATCH 003/361] Update client service with a deployment unit test. --- .../spine3/server/ClientServiceShould.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/server/src/test/java/org/spine3/server/ClientServiceShould.java b/server/src/test/java/org/spine3/server/ClientServiceShould.java index 9d648a64320..8e45e4cf72c 100644 --- a/server/src/test/java/org/spine3/server/ClientServiceShould.java +++ b/server/src/test/java/org/spine3/server/ClientServiceShould.java @@ -38,6 +38,7 @@ import org.spine3.server.command.Assign; import org.spine3.server.command.CommandBus; import org.spine3.server.command.error.UnsupportedCommandException; +import org.spine3.server.transport.GrpcContainer; import org.spine3.test.clientservice.Project; import org.spine3.test.clientservice.ProjectId; import org.spine3.test.clientservice.command.AddTask; @@ -52,11 +53,13 @@ import org.spine3.test.clientservice.event.TaskAdded; import org.spine3.testdata.TestCommandBusFactory; +import java.io.IOException; import java.util.List; import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; @@ -130,6 +133,27 @@ public void return_error_if_command_is_unsupported() { assertEquals(UnsupportedCommandException.class, exception.getClass()); } + @Test + public void deploy_to_grpc_container() throws IOException { + final GrpcContainer grpcContainer = GrpcContainer.newBuilder() + .addService(service) + .build(); + try { + assertTrue(grpcContainer.isScheduledForDeployment(service)); + + grpcContainer.start(); + assertTrue(grpcContainer.isLive(service)); + + grpcContainer.shutdown(); + assertFalse(grpcContainer.isLive(service)); + } finally { + if(!grpcContainer.isShutdown()) { + grpcContainer.shutdown(); + } + } + + } + /* * Stub repositories and aggregates From 2c96d1d2ce30ccf4b04ab5e1e8230b8a18b4ba44 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:28:57 +0300 Subject: [PATCH 004/361] Make package-info description more clear. --- .../main/java/org/spine3/server/transport/package-info.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/transport/package-info.java b/server/src/main/java/org/spine3/server/transport/package-info.java index c6e20a5ec24..7da22a828fb 100644 --- a/server/src/main/java/org/spine3/server/transport/package-info.java +++ b/server/src/main/java/org/spine3/server/transport/package-info.java @@ -19,9 +19,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ + /** - * @author Alex Tymchenko - * * This package contains classes and interfaces defining data transport options between Spine apps and the outer world. * *

Typical transport options include gRPC servers, Firebase adapters etc. From 40a83847f8b44dfbe2c185c172440c9af30757a8 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:30:06 +0300 Subject: [PATCH 005/361] Define Stand, StandFunnel and StandStorage. --- .../java/org/spine3/server/stand/Stand.java | 126 ++++++++++++++ .../org/spine3/server/stand/StandFunnel.java | 140 ++++++++++++++++ .../org/spine3/server/stand/package-info.java | 33 ++++ .../spine3/server/storage/StandStorage.java | 55 +++++++ .../spine3/server/storage/StorageFactory.java | 6 + .../storage/memory/InMemoryStandStorage.java | 155 ++++++++++++++++++ .../memory/InMemoryStorageFactory.java | 9 + 7 files changed, 524 insertions(+) create mode 100644 server/src/main/java/org/spine3/server/stand/Stand.java create mode 100644 server/src/main/java/org/spine3/server/stand/StandFunnel.java create mode 100644 server/src/main/java/org/spine3/server/stand/package-info.java create mode 100644 server/src/main/java/org/spine3/server/storage/StandStorage.java create mode 100644 server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java new file mode 100644 index 00000000000..05184c50c6e --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -0,0 +1,126 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.protobuf.Any; +import org.spine3.server.storage.StandStorage; +import org.spine3.server.storage.memory.InMemoryStandStorage; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkState; + +/** + * A container for storing the lastest {@link org.spine3.server.aggregate.Aggregate} states. + * + *

Provides an optimal way to access the latest state of published aggregates for read-side services. + * The aggregate states are delivered to the instance of {@code Stand} through {@link StandFunnel} + * from {@link org.spine3.server.aggregate.AggregateRepository} instances. + * + *

In order to provide a flexibility in defining data access policies, {@code Stand} contains only the states + * of published aggregates. Please refer to {@link org.spine3.server.aggregate.Aggregate} for publication description. + * + *

Each {@link org.spine3.server.BoundedContext} contains the only instance of {@code Stand}. + * + * @author Alex Tymchenko + */ +public class Stand { + + private final ImmutableSet storages; + + private Stand(Builder builder) { + storages = builder.getEnabledStorages(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Store the new value for the {@link org.spine3.server.aggregate.Aggregate} to each of the configured + * instances of {@link StandStorage}. + * + *

Each state value is stored as one-to-one to its {@link org.spine3.protobuf.TypeUrl} obtained + * via {@link Any#getTypeUrl()}. + * + *

In case {@code Stand} already contains the state for this {@code Aggregate}, the value will be replaced. + * + *

The state of an {@code Aggregate} must not be null. + * + * @param newAggregateState the state of an {@link org.spine3.server.aggregate.Aggregate} put into Stand + */ + public void updateAggregateState(Any newAggregateState) { + // TODO[alex.tymchenko]: should we also check the given Aggregate is allowed to be published? Or is it an AggregateRepository responsibility? + checkState(newAggregateState != null, "newAggregateState must not be null"); + + for (StandStorage storage : storages) { + storage.write(newAggregateState); + } + } + + public static class Builder { + private final Set userProvidedStorages = Sets.newHashSet(); + private ImmutableSet enabledStorages; + + + public Builder addStorage(StandStorage storage) { + userProvidedStorages.add(storage); + return this; + } + + public Builder removeStorage(StandStorage storage) { + userProvidedStorages.remove(storage); + return this; + } + + @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection is immutable + public ImmutableSet getEnabledStorages() { + return enabledStorages; + } + + + private ImmutableSet composeEnabledStorages() { + final ImmutableSet.Builder builder = ImmutableSet.builder(); + if (userProvidedStorages.isEmpty()) { + final InMemoryStandStorage inMemoryStandStorage = InMemoryStandStorage.newBuilder() + .build(); + builder.add(inMemoryStandStorage); + } + builder.addAll(userProvidedStorages); + return builder.build(); + } + + + /** + * Build an instance of {@code Stand} + * + * @return the instance of Stand + */ + public Stand build() { + this.enabledStorages = composeEnabledStorages(); + final Stand result = new Stand(this); + return result; + } + } +} diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java new file mode 100644 index 00000000000..18b06a86141 --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -0,0 +1,140 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.Any; + +import java.util.concurrent.Executor; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * Delivers the latest {@link org.spine3.server.aggregate.Aggregate} states to the {@link Stand}. + * + *

Note: Unlike {@link org.spine3.server.event.EventBus} and {@link org.spine3.server.command.CommandBus}, which assume + * many publishers and many subscribers, the funnel may have zero or more publishers (typically, instances of + * {@link org.spine3.server.aggregate.AggregateRepository}), but the only subscriber, the instance of {@link Stand}. + * + *

In scope of a single {@link org.spine3.server.BoundedContext} there can be the only instance of {@code StandFunnel}. + * + * @author Alex Tymchenko + */ +public class StandFunnel { + + /** + * The instance of {@link Stand} to deliver the {@link org.spine3.server.aggregate.Aggregate} state updates to. + */ + private final Stand stand; + + /** + * An {@link Executor} used for execution of data delivery methods. + */ + private final Executor executor; + + + private StandFunnel(Builder builder) { + this.stand = builder.getStand(); + this.executor = builder.getExecutor(); + } + + public void post(final Any aggregateState) { + executor.execute(new Runnable() { + @Override + public void run() { + stand.updateAggregateState(aggregateState); + } + }); + } + + /** + * Create a new {@code Builder} for {@link StandFunnel}. + * + * @return a new {@code Builder} instance. + */ + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + /** + * The target {@code Stand} to deliver the {@code Aggregate} updates to. + */ + private Stand stand; + + /** + * Optional {@code Executor} for delivering the data to {@code Stand}. + * + *

If not set, a default value will be set by the builder. + */ + private Executor executor; + + public Stand getStand() { + return stand; + } + + /** + * Set the {@link Stand} instance for this {@code StandFunnel}. + * + *

The value must not be null. + * + *

If this method is not used, a default value will be used. + * + * @param stand the instance of {@link Stand}. + * @return {@code this} instance of {@code Builder} + */ + public Builder setStand(Stand stand) { + this.stand = checkNotNull(stand); + return this; + } + + public Executor getExecutor() { + return executor; + } + + /** + * Set the {@code Executor} instance for this {@code StandFunnel}. + * + *

The value must not be {@code null}. + * + * @param executor the instance of {@code Executor}. + * @return {@code this} instance of {@code Builder} + */ + public Builder setExecutor(Executor executor) { + this.executor = checkNotNull(executor); + return this; + } + + public StandFunnel build() { + checkState(stand != null, "Stand must be defined for the funnel"); + + if (executor == null) { + executor = MoreExecutors.directExecutor(); + } + + final StandFunnel result = new StandFunnel(this); + return result; + } + } +} diff --git a/server/src/main/java/org/spine3/server/stand/package-info.java b/server/src/main/java/org/spine3/server/stand/package-info.java new file mode 100644 index 00000000000..f2ccb2aee2e --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/package-info.java @@ -0,0 +1,33 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** + * This package provides classes for working with Stand. + */ + +@SPI +@ParametersAreNonnullByDefault +package org.spine3.server.stand; + +import org.spine3.SPI; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java new file mode 100644 index 00000000000..4aff572fe6f --- /dev/null +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -0,0 +1,55 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.storage; + +import com.google.protobuf.Any; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.stand.Stand; + +/** + * Contract for {@link Stand} storage. + * + *

Stores the latest {@link org.spine3.server.aggregate.Aggregate} states. The state is passed as {@link Any} proto message + * and stored by its {@link TypeUrl}. + * + *

Not more than one {@code Aggregate} state object is stored for a single {@code TypeUrl}. + * + *

Allows to access the states via {@link TypeUrl} its. + * + * @author Alex Tymchenko + * @see Any#getTypeUrl() + * @see Stand + */ +public abstract class StandStorage extends AbstractStorage { + + protected StandStorage(boolean multitenant) { + super(multitenant); + } + + /** + * Update the storage with the {@link org.spine3.server.aggregate.Aggregate} state. + * + * @param aggregateState the state of {@code Aggregate} to store. + */ + public abstract void write(Any aggregateState); + +} diff --git a/server/src/main/java/org/spine3/server/storage/StorageFactory.java b/server/src/main/java/org/spine3/server/storage/StorageFactory.java index 9c0385b6dff..696a19bd4e6 100644 --- a/server/src/main/java/org/spine3/server/storage/StorageFactory.java +++ b/server/src/main/java/org/spine3/server/storage/StorageFactory.java @@ -45,6 +45,10 @@ public interface StorageFactory extends AutoCloseable { /** Creates a new {@link EventStorage} instance. */ EventStorage createEventStorage(); + /** Creates a new {@link StandStorage} instance. */ + StandStorage createStandStorage(); + + /** * Creates a new {@link AggregateStorage} instance. * @@ -68,4 +72,6 @@ public interface StorageFactory extends AutoCloseable { * @param the type of stream projection IDs */ ProjectionStorage createProjectionStorage(Class> projectionClass); + + } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java new file mode 100644 index 00000000000..7f8b66e45c1 --- /dev/null +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -0,0 +1,155 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.storage.memory; + +import com.google.common.collect.MapMaker; +import com.google.common.collect.Sets; +import com.google.protobuf.Any; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.storage.StandStorage; + +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * In-memory implementation of {@link StandStorage}. + * + *

Uses a {@link ConcurrentMap} for internal storage. + * + * @author Alex Tymchenko + */ +public class InMemoryStandStorage extends StandStorage { + + private final ConcurrentMap aggregateStates; + + private InMemoryStandStorage(Builder builder) { + super(builder.isMultitenant()); + aggregateStates = builder.getSeedDataMap(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public Any read(TypeUrl id) { + final Any result = aggregateStates.get(id); + return result; + } + + @Override + public void write(TypeUrl id, Any record) { + final TypeUrl recordType = TypeUrl.of(record.getTypeUrl()); + checkState(id == recordType, "The typeUrl of the record does not correspond to id"); + + doPut(aggregateStates, record); + } + + @Override + public void write(Any aggregateState) { + doPut(aggregateStates, aggregateState); + } + + public static class Builder { + + private final Set seedData = Sets.newHashSet(); + private ConcurrentMap seedDataMap; + private boolean multitenant; + + /** + * Add a seed data item. + * + *

Use this method to pre-fill the {@link StandStorage}. + * + *

The argument value must not be null. + * + * @param stateObject the state object to be added + * @return {@code this} instance of {@code Builder} + */ + public Builder addStateObject(Any stateObject) { + checkNotNull(stateObject, "stateObject must not be null"); + seedData.add(stateObject); + return this; + } + + /** + * Remove a previously added seed data item. + * + *

The argument value must not be null. + * + * @param stateObject the state object to be removed + * @return {@code this} instance of {@code Builder} + */ + public Builder removeStateObject(Any stateObject) { + checkNotNull(stateObject, "Cannot remove null stateObject"); + seedData.remove(stateObject); + return this; + } + + private ConcurrentMap buildSeedDataMap() { + final ConcurrentMap stateMap = new MapMaker().initialCapacity(seedData.size()) + .makeMap(); + for (final Any any : seedData) { + doPut(stateMap, any); + } + return stateMap; + } + + /** + * Do not expose the contents to public, as the returned {@code ConcurrentMap} must be mutable. + */ + private ConcurrentMap getSeedDataMap() { + return seedDataMap; + } + + public boolean isMultitenant() { + return multitenant; + } + + public Builder setMultitenant(boolean multitenant) { + this.multitenant = multitenant; + return this; + } + + /** + * Builds an instance of in-memory stand storage. + * + * @return an instance of in-memory storage + */ + public InMemoryStandStorage build() { + this.seedDataMap = buildSeedDataMap(); + final InMemoryStandStorage result = new InMemoryStandStorage(this); + return result; + } + + } + + private static void doPut(final ConcurrentMap targetMap, final Any any) { + final String typeUrlString = any.getTypeUrl(); + final TypeUrl typeUrl = TypeUrl.of(typeUrlString); + targetMap.put(typeUrl, any); + } + +} diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java index 6f5fc9884ff..003e5a018bb 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java @@ -27,6 +27,7 @@ import org.spine3.server.storage.EntityStorage; import org.spine3.server.storage.EventStorage; import org.spine3.server.storage.ProjectionStorage; +import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.StorageFactory; /** @@ -57,6 +58,14 @@ public EventStorage createEventStorage() { return new InMemoryEventStorage(isMultitenant()); } + @Override + public StandStorage createStandStorage() { + final InMemoryStandStorage result = InMemoryStandStorage.newBuilder() + .setMultitenant(isMultitenant()) + .build(); + return result; + } + /** NOTE: the parameter is unused. */ @Override public AggregateStorage createAggregateStorage(Class> unused) { From 7dbabc762322a87b96bb07dee627ba1c91787971 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:30:46 +0300 Subject: [PATCH 006/361] Add Stand to the BoundedContext builder. --- .../org/spine3/server/BoundedContext.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index 46b78d3fc44..bdbff04bda1 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -43,6 +43,8 @@ import org.spine3.server.integration.IntegrationEvent; import org.spine3.server.integration.IntegrationEventContext; import org.spine3.server.integration.grpc.IntegrationEventSubscriberGrpc; +import org.spine3.server.stand.Stand; +import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.StorageFactory; import org.spine3.validate.Validate; @@ -64,7 +66,7 @@ * @author Mikhail Melnik */ public class BoundedContext extends IntegrationEventSubscriberGrpc.IntegrationEventSubscriberImplBase - implements AutoCloseable { + implements AutoCloseable { /** The default name for a {@code BoundedContext}. */ public static final String DEFAULT_NAME = "Main"; @@ -80,6 +82,7 @@ public class BoundedContext extends IntegrationEventSubscriberGrpc.IntegrationEv private final StorageFactory storageFactory; private final CommandBus commandBus; private final EventBus eventBus; + private final Stand stand; private final List> repositories = Lists.newLinkedList(); @@ -89,6 +92,7 @@ private BoundedContext(Builder builder) { this.storageFactory = builder.storageFactory; this.commandBus = builder.commandBus; this.eventBus = builder.eventBus; + this.stand = builder.stand; } /** @@ -207,10 +211,10 @@ private static Event toEvent(IntegrationEvent integrationEvent) { final IntegrationEventContext sourceContext = integrationEvent.getContext(); final StringValue producerId = newStringValue(sourceContext.getBoundedContextName()); final EventContext context = EventContext.newBuilder() - .setEventId(sourceContext.getEventId()) - .setTimestamp(sourceContext.getTimestamp()) - .setProducerId(AnyPacker.pack(producerId)) - .build(); + .setEventId(sourceContext.getEventId()) + .setTimestamp(sourceContext.getTimestamp()) + .setProducerId(AnyPacker.pack(producerId)) + .build(); final Event result = Events.createEvent(integrationEvent.getMessage(), context); return result; } @@ -244,6 +248,7 @@ public static class Builder { private EventBus eventBus; private boolean multitenant; private EventEnricher eventEnricher; + private Stand stand; /** * Sets the name for a new bounded context. @@ -371,6 +376,16 @@ public EventEnricher getEventEnricher() { return eventEnricher; } + public Builder setStand(Stand stand) { + this.stand = checkNotNull(stand); + return this; + } + + @Nullable + public Stand getStand() { + return stand; + } + public BoundedContext build() { checkNotNull(storageFactory, "storageFactory must be set"); @@ -388,6 +403,10 @@ public BoundedContext build() { eventBus = createEventBus(); } + if (stand == null) { + stand = createStand(storageFactory); + } + commandBus.setMultitenant(this.multitenant); final BoundedContext result = new BoundedContext(this); @@ -432,6 +451,14 @@ private EventBus createEventBus() { .build(); return result; } + + private static Stand createStand(StorageFactory storageFactory) { + final StandStorage standStorage = storageFactory.createStandStorage(); + final Stand result = Stand.newBuilder() + .addStorage(standStorage) + .build(); + return result; + } } private enum LogSingleton { From aecde7f7a772446fefea2e31616be97a6ff0ab4e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 20:23:10 +0300 Subject: [PATCH 007/361] Allow to specify StandFunnel while building an instance of BoundedContext. --- .../org/spine3/server/BoundedContext.java | 38 +++++++++++++++++++ .../server/aggregate/AggregateRepository.java | 7 ++++ 2 files changed, 45 insertions(+) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index bdbff04bda1..494d06a7867 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -44,6 +44,7 @@ import org.spine3.server.integration.IntegrationEventContext; import org.spine3.server.integration.grpc.IntegrationEventSubscriberGrpc; import org.spine3.server.stand.Stand; +import org.spine3.server.stand.StandFunnel; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.StorageFactory; import org.spine3.validate.Validate; @@ -83,6 +84,7 @@ public class BoundedContext extends IntegrationEventSubscriberGrpc.IntegrationEv private final CommandBus commandBus; private final EventBus eventBus; private final Stand stand; + private final StandFunnel standFunnel; private final List> repositories = Lists.newLinkedList(); @@ -93,6 +95,7 @@ private BoundedContext(Builder builder) { this.commandBus = builder.commandBus; this.eventBus = builder.eventBus; this.stand = builder.stand; + this.standFunnel = builder.standFunnel; } /** @@ -231,6 +234,12 @@ public EventBus getEventBus() { return this.eventBus; } + /** Obtains instance of {@link StandFunnel} of this {@code BoundedContext}. */ + @CheckReturnValue + public StandFunnel getStandFunnel() { + return this.standFunnel; + } + /** * A builder for producing {@code BoundedContext} instances. * @@ -249,6 +258,8 @@ public static class Builder { private boolean multitenant; private EventEnricher eventEnricher; private Stand stand; + private StandFunnel standFunnel; + private Executor standFunnelExecutor; /** * Sets the name for a new bounded context. @@ -386,6 +397,16 @@ public Stand getStand() { return stand; } + @Nullable + public Executor getStandFunnelExecutor() { + return standFunnelExecutor; + } + + public Builder setStandFunnelExecutor(Executor standFunnelExecutor) { + this.standFunnelExecutor = standFunnelExecutor; + return this; + } + public BoundedContext build() { checkNotNull(storageFactory, "storageFactory must be set"); @@ -407,6 +428,8 @@ public BoundedContext build() { stand = createStand(storageFactory); } + standFunnel = createStandFunnel(standFunnelExecutor); + commandBus.setMultitenant(this.multitenant); final BoundedContext result = new BoundedContext(this); @@ -415,6 +438,21 @@ public BoundedContext build() { return result; } + private StandFunnel createStandFunnel(@Nullable Executor standFunnelExecutor) { + StandFunnel standFunnel; + if (standFunnelExecutor == null) { + standFunnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + } else { + standFunnel = StandFunnel.newBuilder() + .setExecutor(standFunnelExecutor) + .setStand(stand) + .build(); + } + return standFunnel; + } + private CommandStore createCommandStore() { final CommandStore result = new CommandStore(storageFactory.createCommandStorage()); return result; diff --git a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java index bec8e076324..6e720f382f5 100644 --- a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java +++ b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java @@ -19,6 +19,7 @@ */ package org.spine3.server.aggregate; +import com.google.protobuf.Any; import com.google.protobuf.Message; import org.spine3.base.Command; import org.spine3.base.CommandContext; @@ -26,12 +27,14 @@ import org.spine3.base.Errors; import org.spine3.base.Event; import org.spine3.base.FailureThrowable; +import org.spine3.protobuf.AnyPacker; import org.spine3.server.BoundedContext; import org.spine3.server.command.CommandDispatcher; import org.spine3.server.command.CommandStatusService; import org.spine3.server.entity.GetTargetIdFromCommand; import org.spine3.server.entity.Repository; import org.spine3.server.event.EventBus; +import org.spine3.server.stand.StandFunnel; import org.spine3.server.storage.AggregateEvents; import org.spine3.server.storage.AggregateStorage; import org.spine3.server.storage.Storage; @@ -81,6 +84,7 @@ public abstract class AggregateRepository> private final GetTargetIdFromCommand getIdFunction = GetTargetIdFromCommand.newInstance(); private final CommandStatusService commandStatusService; private final EventBus eventBus; + private final StandFunnel standFunnel; /** * Creates a new repository instance. @@ -91,6 +95,7 @@ public AggregateRepository(BoundedContext boundedContext) { super(boundedContext); this.commandStatusService = boundedContext.getCommandBus().getCommandStatusService(); this.eventBus = boundedContext.getEventBus(); + this.standFunnel = boundedContext.getStandFunnel(); } @Override @@ -228,6 +233,8 @@ public void dispatch(Command request) throws IllegalStateException { //noinspection OverlyBroadCatchBlock try { store(aggregate); + final Any packedAny = AnyPacker.pack(aggregate.getState()); + standFunnel.post(packedAny); } catch (Exception e) { commandStatusService.setToError(commandId, e); } From aede49032bf676cb9610af4cb94196ab761ba4d5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 20:39:21 +0300 Subject: [PATCH 008/361] Investigate the build failure reason: enable --info option for gradlew on Travis. --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bf11a976a3e..95078a8cb45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,8 @@ before_install: - pip install --user codecov after_success: - - codecov \ No newline at end of file + - codecov + +script: + - ./gradlew assemble + - ./gradlew check --info \ No newline at end of file From fa46bfcc82be36fb5b659e8584553d1794ecaf94 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 10 Aug 2016 18:06:41 +0300 Subject: [PATCH 009/361] Extract gRPC server from the ClientService. --- .../examples/aggregate/server/Server.java | 23 +- .../java/org/spine3/server/ClientService.java | 108 +-------- .../server/transport/GrpcContainer.java | 220 ++++++++++++++++++ .../spine3/server/transport/package-info.java | 32 +++ .../spine3/server/ClientServiceShould.java | 93 +------- .../server/transport/GrpcContainerShould.java | 139 +++++++++++ 6 files changed, 416 insertions(+), 199 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/transport/GrpcContainer.java create mode 100644 server/src/main/java/org/spine3/server/transport/package-info.java create mode 100644 server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java diff --git a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java index c9fe0e7e838..3f21f283a5f 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java @@ -26,6 +26,7 @@ import org.spine3.server.event.EventSubscriber; import org.spine3.server.storage.StorageFactory; import org.spine3.server.storage.memory.InMemoryStorageFactory; +import org.spine3.server.transport.GrpcContainer; import java.io.IOException; @@ -39,7 +40,7 @@ */ public class Server { - private final ClientService clientService; + private final GrpcContainer grpcContainer; private final BoundedContext boundedContext; public Server(StorageFactory storageFactory) { @@ -53,26 +54,32 @@ public Server(StorageFactory storageFactory) { // Subscribe an event subscriber in the bounded context. final EventSubscriber eventLogger = new EventLogger(); - boundedContext.getEventBus().subscribe(eventLogger); + boundedContext.getEventBus() + .subscribe(eventLogger); // Create a client service with this bounded context. - this.clientService = ClientService.newBuilder() - .addBoundedContext(boundedContext) + final ClientService clientService = ClientService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + + // Create a gRPC server and schedule the client service instance for deployment. + this.grpcContainer = GrpcContainer.newBuilder() .setPort(DEFAULT_CLIENT_SERVICE_PORT) + .addService(clientService) .build(); } public void start() throws IOException { - clientService.start(); - clientService.addShutdownHook(); + grpcContainer.start(); + grpcContainer.addShutdownHook(); } public void awaitTermination() { - clientService.awaitTermination(); + grpcContainer.awaitTermination(); } public void shutdown() throws Exception { - clientService.shutdown(); + grpcContainer.shutdown(); boundedContext.close(); } diff --git a/server/src/main/java/org/spine3/server/ClientService.java b/server/src/main/java/org/spine3/server/ClientService.java index 1797d36da44..7728c93007a 100644 --- a/server/src/main/java/org/spine3/server/ClientService.java +++ b/server/src/main/java/org/spine3/server/ClientService.java @@ -20,11 +20,8 @@ package org.spine3.server; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; -import io.grpc.ServerBuilder; -import io.grpc.ServerServiceDefinition; import io.grpc.stub.StreamObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,115 +29,32 @@ import org.spine3.base.Event; import org.spine3.base.Response; import org.spine3.base.Responses; -import org.spine3.client.ConnectionConstants; import org.spine3.client.grpc.Topic; import org.spine3.server.command.error.CommandException; import org.spine3.server.command.error.UnsupportedCommandException; import org.spine3.server.type.CommandClass; -import javax.annotation.Nullable; -import java.io.IOException; import java.util.Set; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Throwables.propagate; - /** * The {@code ClientService} allows client applications to post commands and * receive updates from the application backend. * * @author Alexander Yevsyukov */ -public class ClientService extends org.spine3.client.grpc.ClientServiceGrpc.ClientServiceImplBase { - - private static final String SERVICE_NOT_STARTED_MSG = "Service was not started or is shutdown already."; - - private final int port; +public class ClientService + extends org.spine3.client.grpc.ClientServiceGrpc.ClientServiceImplBase { private final ImmutableMap boundedContextMap; - @Nullable - private io.grpc.Server grpcServer; - public static Builder newBuilder() { return new Builder(); } protected ClientService(Builder builder) { - this.port = builder.getPort(); this.boundedContextMap = builder.getBoundedContextMap(); } - /** - * Starts the service. - * - * @throws IOException if unable to bind - */ - public void start() throws IOException { - checkState(grpcServer == null, "Service is started already."); - grpcServer = createGrpcServer(port); - grpcServer.start(); - } - - /** Waits for the service to become terminated. */ - public void awaitTermination() { - checkState(grpcServer != null, SERVICE_NOT_STARTED_MSG); - try { - grpcServer.awaitTermination(); - } catch (InterruptedException e) { - throw propagate(e); - } - } - - /** - * Makes the JVM shut down the service when it is shutting down itself. - * - *

Call this method when running the service in a separate JVM. - */ - public void addShutdownHook() { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - // Use stderr here since the logger may have been reset by its JVM shutdown hook. - @SuppressWarnings("UseOfSystemOutOrSystemErr") - @Override - public void run() { - final String serviceClass = ClientService.this.getClass().getName(); - try { - if (!isShutdown()) { - System.err.println("Shutting down " + serviceClass + " since JVM is shutting down..."); - shutdown(); - System.err.println(serviceClass + " shut down."); - } - } catch (RuntimeException e) { - e.printStackTrace(System.err); - } - } - })); - } - - /** - * Returns {@code true} if the service is shutdown or was not started at all, {@code false} otherwise. - * - * @see ClientService#shutdown() - */ - public boolean isShutdown() { - final boolean isShutdown = grpcServer == null; - return isShutdown; - } - - /** Initiates an orderly shutdown in which existing calls continue but new calls are rejected. */ - public void shutdown() { - checkState(grpcServer != null, SERVICE_NOT_STARTED_MSG); - grpcServer.shutdown(); - grpcServer = null; - } - - @VisibleForTesting - /* package */ io.grpc.Server createGrpcServer(int port) { - final ServerServiceDefinition service = bindService(); - final ServerBuilder builder = ServerBuilder.forPort(port) - .addService(service); - return builder.build(); - } @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. @Override @@ -150,7 +64,8 @@ public void post(Command request, StreamObserver responseObserver) { if (boundedContext == null) { handleUnsupported(request, responseObserver); } else { - boundedContext.getCommandBus().post(request, responseObserver); + boundedContext.getCommandBus() + .post(request, responseObserver); } } @@ -181,7 +96,6 @@ public static class Builder { private final Set boundedContexts = Sets.newHashSet(); private ImmutableMap boundedContextMap; - private int port = ConnectionConstants.DEFAULT_CLIENT_SERVICE_PORT; public Builder addBoundedContext(BoundedContext boundedContext) { // Save it to a temporary set so that it is easy to remove it if needed. @@ -194,14 +108,6 @@ public Builder removeBoundedContext(BoundedContext boundedContext) { return this; } - public Builder setPort(int port) { - this.port = port; - return this; - } - - public int getPort() { - return this.port; - } @SuppressWarnings("ReturnOfCollectionOrArrayField") // is immutable public ImmutableMap getBoundedContextMap() { @@ -210,7 +116,6 @@ public ImmutableMap getBoundedContextMap() { /** * Builds the {@link ClientService}. - * {@link ConnectionConstants#DEFAULT_CLIENT_SERVICE_PORT} is used by default. */ public ClientService build() { this.boundedContextMap = createBoundedContextMap(); @@ -227,8 +132,9 @@ private ImmutableMap createBoundedContextMap() { } private static void addBoundedContext(ImmutableMap.Builder mapBuilder, - BoundedContext boundedContext) { - final Set cmdClasses = boundedContext.getCommandBus().getSupportedCommandClasses(); + BoundedContext boundedContext) { + final Set cmdClasses = boundedContext.getCommandBus() + .getSupportedCommandClasses(); for (CommandClass commandClass : cmdClasses) { mapBuilder.put(commandClass, boundedContext); } diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java new file mode 100644 index 00000000000..777e879c9ef --- /dev/null +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -0,0 +1,220 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.transport; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import io.grpc.BindableService; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerServiceDefinition; +import org.spine3.client.ConnectionConstants; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Throwables.propagate; + +/** + * Wrapping container for gRPC server. + * + *

Maintains and deploys several of gRPC services within a single server. + *

Uses {@link ServerServiceDefinition}s of each service. + * + * @author Alex Tymchenko + */ +public class GrpcContainer { + + private static final String SERVER_NOT_STARTED_MSG = "gRPC server was not started or is shut down already."; + + private final int port; + private final ImmutableSet services; + + @Nullable + private io.grpc.Server grpcServer; + + + public static Builder newBuilder() { + return new Builder(); + } + + protected GrpcContainer(Builder builder) { + this.port = builder.getPort(); + this.services = builder.getServices(); + } + + + /** + * Starts the service. + * + * @throws IOException if unable to bind + */ + public void start() throws IOException { + checkState(grpcServer == null, "gRPC server is started already."); + grpcServer = createGrpcServer(); + grpcServer.start(); + } + + + /** + * Returns {@code true} if the server is shut down or was not started at all, {@code false} otherwise. + * + * @see GrpcContainer#shutdown() + */ + public boolean isShutdown() { + final boolean isShutdown = grpcServer == null; + return isShutdown; + } + + /** Initiates an orderly shutdown in which existing calls continue but new calls are rejected. */ + public void shutdown() { + checkState(grpcServer != null, SERVER_NOT_STARTED_MSG); + grpcServer.shutdown(); + grpcServer = null; + } + + + /** Waits for the service to become terminated. */ + public void awaitTermination() { + checkState(grpcServer != null, SERVER_NOT_STARTED_MSG); + try { + grpcServer.awaitTermination(); + } catch (InterruptedException e) { + throw propagate(e); + } + } + + + /** + * Check if the given gRPC service is scheduled for the deployment in this container. + * + *

Note, that the given gRPC service will become available to the clients, once the gRPC container is started. + *

To find out, whether the service is already available for calls, use {@link #isLive(BindableService)} method. + * + * @param service the gRPC service to check + * @return {@code true}, if the given gRPC service for deployment; {@code false} otherwise + */ + public boolean isScheduledForDeployment(BindableService service) { + final String nameOfInterest = service.bindService() + .getServiceDescriptor() + .getName(); + + boolean serviceIsPresent = false; + for (ServerServiceDefinition serverServiceDefinition : services) { + final String scheduledServiceName = serverServiceDefinition.getServiceDescriptor() + .getName(); + serviceIsPresent = serviceIsPresent || scheduledServiceName.equals(nameOfInterest); + } + return serviceIsPresent; + } + + /** + * Check if the given gRPC service has actually been deployed and is available for interaction within this container. + * + *

Returns {@code true} if and only if + *

a. the service has been previously scheduled for the deployment, + *

b. the container has been started. + * + * @param service the gRPC service + * @return {@code true}, if the service is available for interaction within this container; {@code false} otherwise + */ + public boolean isLive(BindableService service) { + return !isShutdown() && isScheduledForDeployment(service); + } + + /** + * Makes the JVM shut down the service when it is shutting down itself. + * + *

Call this method when running the service in a separate JVM. + */ + public void addShutdownHook() { + Runtime.getRuntime() + .addShutdownHook(new Thread(new Runnable() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + @SuppressWarnings("UseOfSystemOutOrSystemErr") + @Override + public void run() { + final String serverClass = GrpcContainer.this.getClass() + .getName(); + try { + if (!isShutdown()) { + System.err.println("Shutting down " + serverClass + " since JVM is shutting down..."); + shutdown(); + System.err.println(serverClass + " shut down."); + } + } catch (RuntimeException e) { + e.printStackTrace(System.err); + } + } + })); + } + + @VisibleForTesting + /* package */ Server createGrpcServer() { + final ServerBuilder builder = ServerBuilder.forPort(port); + for (ServerServiceDefinition service : services) { + builder.addService(service); + } + + return builder.build(); + } + + + public static class Builder { + + private int port = ConnectionConstants.DEFAULT_CLIENT_SERVICE_PORT; + private final Set serviceDefinitions = Sets.newHashSet(); + + public Builder setPort(int port) { + this.port = port; + return this; + } + + public int getPort() { + return this.port; + } + + public Builder addService(BindableService service) { + serviceDefinitions.add(service.bindService()); + return this; + } + + public Builder removeService(ServerServiceDefinition service) { + serviceDefinitions.remove(service); + return this; + } + + public ImmutableSet getServices() { + return ImmutableSet.copyOf(serviceDefinitions); + } + + public GrpcContainer build() { + return new GrpcContainer(this); + } + + } + + +} diff --git a/server/src/main/java/org/spine3/server/transport/package-info.java b/server/src/main/java/org/spine3/server/transport/package-info.java new file mode 100644 index 00000000000..c6e20a5ec24 --- /dev/null +++ b/server/src/main/java/org/spine3/server/transport/package-info.java @@ -0,0 +1,32 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * @author Alex Tymchenko + * + * This package contains classes and interfaces defining data transport options between Spine apps and the outer world. + * + *

Typical transport options include gRPC servers, Firebase adapters etc. + */ +@ParametersAreNonnullByDefault +package org.spine3.server.transport; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/server/src/test/java/org/spine3/server/ClientServiceShould.java b/server/src/test/java/org/spine3/server/ClientServiceShould.java index dda8606ed25..9d648a64320 100644 --- a/server/src/test/java/org/spine3/server/ClientServiceShould.java +++ b/server/src/test/java/org/spine3/server/ClientServiceShould.java @@ -23,7 +23,6 @@ import com.google.common.collect.Sets; import com.google.protobuf.StringValue; -import io.grpc.Server; import io.grpc.stub.StreamObserver; import org.junit.After; import org.junit.Before; @@ -53,19 +52,13 @@ import org.spine3.test.clientservice.event.TaskAdded; import org.spine3.testdata.TestCommandBusFactory; -import java.io.IOException; import java.util.List; import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; @@ -75,7 +68,6 @@ public class ClientServiceShould { private ClientService service; - private Server server; private final Set boundedContexts = Sets.newHashSet(); private BoundedContext projectsContext; @@ -103,15 +95,10 @@ public void setUp() { builder.addBoundedContext(context); } service = spy(builder.build()); - server = mock(Server.class); - doReturn(server).when(service).createGrpcServer(anyInt()); } @After public void tearDown() throws Exception { - if (!service.isShutdown()) { - service.shutdown(); - } for (BoundedContext boundedContext : boundedContexts) { boundedContext.close(); } @@ -138,85 +125,11 @@ public void return_error_if_command_is_unsupported() { service.post(unsupportedCmd, responseObserver); - final Throwable exception = responseObserver.getThrowable().getCause(); + final Throwable exception = responseObserver.getThrowable() + .getCause(); assertEquals(UnsupportedCommandException.class, exception.getClass()); } - @Test - public void start_server() throws IOException { - service.start(); - - verify(server).start(); - } - - @Test - public void throw_exception_if_started_already() throws IOException { - service.start(); - try { - service.start(); - } catch (IllegalStateException expected) { - return; - } - fail("Exception must be thrown."); - } - - @Test - public void await_termination() throws IOException, InterruptedException { - service.start(); - service.awaitTermination(); - - verify(server).awaitTermination(); - } - - @Test(expected = IllegalStateException.class) - public void throw_exception_if_call_await_termination_on_not_started_service() { - service.awaitTermination(); - } - - @Test - public void assure_service_is_shutdown() throws IOException { - service.start(); - service.shutdown(); - - assertTrue(service.isShutdown()); - } - - @Test - public void assure_service_was_not_started() throws IOException { - assertTrue(service.isShutdown()); - } - - @Test - public void assure_service_is_not_shut_down() throws IOException { - service.start(); - - assertFalse(service.isShutdown()); - } - - @Test - public void shutdown_itself() throws IOException, InterruptedException { - service.start(); - service.shutdown(); - - verify(server).shutdown(); - } - - @Test(expected = IllegalStateException.class) - public void throw_exception_if_call_shutdown_on_not_started_service() { - service.shutdown(); - } - - @Test - public void throw_exception_if_shutdown_already() throws IOException { - service.start(); - service.shutdown(); - try { - service.shutdown(); - } catch (IllegalStateException expected) { - return; - } - fail("Expected an exception."); - } /* * Stub repositories and aggregates @@ -247,7 +160,7 @@ public TaskAdded handle(AddTask cmd, CommandContext ctx) { @Assign public List handle(StartProject cmd, CommandContext ctx) { - final ProjectStarted message = Given.EventMessage.projectStarted(cmd.getProjectId()); + final ProjectStarted message = Given.EventMessage.projectStarted(cmd.getProjectId()); return newArrayList(message); } diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java new file mode 100644 index 00000000000..7b811dbc2dd --- /dev/null +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -0,0 +1,139 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.transport; + +import io.grpc.Server; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * @author Alex Tymchenko + */ +public class GrpcContainerShould { + private Server server; + private GrpcContainer grpcContainer; + + + @Before + public void setUp() { + final GrpcContainer.Builder builder = GrpcContainer.newBuilder(); + grpcContainer = spy(builder.build()); + + server = mock(Server.class); + doReturn(server).when(grpcContainer).createGrpcServer(); + } + + @After + public void tearDown() { + if (!grpcContainer.isShutdown()) { + grpcContainer.shutdown(); + } + } + + @Test + public void start_server() throws IOException { + grpcContainer.start(); + + verify(server).start(); + } + + @Test + public void throw_exception_if_started_already() throws IOException { + grpcContainer.start(); + try { + grpcContainer.start(); + } catch (IllegalStateException expected) { + return; + } + fail("Exception must be thrown."); + } + + @Test + public void await_termination() throws IOException, InterruptedException { + grpcContainer.start(); + grpcContainer.awaitTermination(); + + verify(server).awaitTermination(); + } + + @Test(expected = IllegalStateException.class) + public void throw_exception_if_call_await_termination_on_not_started_service() { + grpcContainer.awaitTermination(); + } + + @Test + public void assure_service_is_shutdown() throws IOException { + grpcContainer.start(); + grpcContainer.shutdown(); + + assertTrue(grpcContainer.isShutdown()); + } + + @Test + public void assure_service_was_not_started() throws IOException { + assertTrue(grpcContainer.isShutdown()); + } + + @Test + public void assure_service_is_not_shut_down() throws IOException { + grpcContainer.start(); + + assertFalse(grpcContainer.isShutdown()); + } + + @Test + public void shutdown_itself() throws IOException, InterruptedException { + grpcContainer.start(); + grpcContainer.shutdown(); + + verify(server).shutdown(); + } + + @Test(expected = IllegalStateException.class) + public void throw_exception_if_call_shutdown_on_not_started_service() { + grpcContainer.shutdown(); + } + + @Test + public void throw_exception_if_shutdown_already() throws IOException { + grpcContainer.start(); + grpcContainer.shutdown(); + try { + grpcContainer.shutdown(); + } catch (IllegalStateException expected) { + return; + } + fail("Expected an exception."); + } + +} From 618b7deb95a50e52bbb35577e6c6969b1d25cce8 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 10 Aug 2016 18:08:21 +0300 Subject: [PATCH 010/361] Add jdk1.4 logging bindings for 'examples' module to see sample Client <-> Server interaction details. --- examples/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/build.gradle b/examples/build.gradle index ff3ed78755c..4c3d9f8a843 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,7 +1,8 @@ - dependencies { compile project(path: ':client') compile project(path: ':server') + //Use jdk14 bindings for the sample application + compile group: 'org.slf4j', name: 'slf4j-jdk14', version: project.SLF4J_VERSION } sourceSets.main.java.srcDir generatedSpineDir From c54924c8a0f67df8b497ab3afd8db2fafe4a2091 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:27:17 +0300 Subject: [PATCH 011/361] Update client service with a deployment unit test. --- .../spine3/server/ClientServiceShould.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/server/src/test/java/org/spine3/server/ClientServiceShould.java b/server/src/test/java/org/spine3/server/ClientServiceShould.java index 9d648a64320..8e45e4cf72c 100644 --- a/server/src/test/java/org/spine3/server/ClientServiceShould.java +++ b/server/src/test/java/org/spine3/server/ClientServiceShould.java @@ -38,6 +38,7 @@ import org.spine3.server.command.Assign; import org.spine3.server.command.CommandBus; import org.spine3.server.command.error.UnsupportedCommandException; +import org.spine3.server.transport.GrpcContainer; import org.spine3.test.clientservice.Project; import org.spine3.test.clientservice.ProjectId; import org.spine3.test.clientservice.command.AddTask; @@ -52,11 +53,13 @@ import org.spine3.test.clientservice.event.TaskAdded; import org.spine3.testdata.TestCommandBusFactory; +import java.io.IOException; import java.util.List; import java.util.Set; import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; @@ -130,6 +133,27 @@ public void return_error_if_command_is_unsupported() { assertEquals(UnsupportedCommandException.class, exception.getClass()); } + @Test + public void deploy_to_grpc_container() throws IOException { + final GrpcContainer grpcContainer = GrpcContainer.newBuilder() + .addService(service) + .build(); + try { + assertTrue(grpcContainer.isScheduledForDeployment(service)); + + grpcContainer.start(); + assertTrue(grpcContainer.isLive(service)); + + grpcContainer.shutdown(); + assertFalse(grpcContainer.isLive(service)); + } finally { + if(!grpcContainer.isShutdown()) { + grpcContainer.shutdown(); + } + } + + } + /* * Stub repositories and aggregates From deea1011182103f94e11d02cddc519f0fd6f3ee3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:28:57 +0300 Subject: [PATCH 012/361] Make package-info description more clear. --- .../main/java/org/spine3/server/transport/package-info.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/transport/package-info.java b/server/src/main/java/org/spine3/server/transport/package-info.java index c6e20a5ec24..7da22a828fb 100644 --- a/server/src/main/java/org/spine3/server/transport/package-info.java +++ b/server/src/main/java/org/spine3/server/transport/package-info.java @@ -19,9 +19,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ + /** - * @author Alex Tymchenko - * * This package contains classes and interfaces defining data transport options between Spine apps and the outer world. * *

Typical transport options include gRPC servers, Firebase adapters etc. From 4a51faebc4f38a3445d7c7d73d49df2b83dc0477 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:30:06 +0300 Subject: [PATCH 013/361] Define Stand, StandFunnel and StandStorage. --- .../java/org/spine3/server/stand/Stand.java | 126 ++++++++++++++ .../org/spine3/server/stand/StandFunnel.java | 140 ++++++++++++++++ .../org/spine3/server/stand/package-info.java | 33 ++++ .../spine3/server/storage/StandStorage.java | 55 +++++++ .../spine3/server/storage/StorageFactory.java | 6 + .../storage/memory/InMemoryStandStorage.java | 155 ++++++++++++++++++ .../memory/InMemoryStorageFactory.java | 9 + 7 files changed, 524 insertions(+) create mode 100644 server/src/main/java/org/spine3/server/stand/Stand.java create mode 100644 server/src/main/java/org/spine3/server/stand/StandFunnel.java create mode 100644 server/src/main/java/org/spine3/server/stand/package-info.java create mode 100644 server/src/main/java/org/spine3/server/storage/StandStorage.java create mode 100644 server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java new file mode 100644 index 00000000000..05184c50c6e --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -0,0 +1,126 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.protobuf.Any; +import org.spine3.server.storage.StandStorage; +import org.spine3.server.storage.memory.InMemoryStandStorage; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkState; + +/** + * A container for storing the lastest {@link org.spine3.server.aggregate.Aggregate} states. + * + *

Provides an optimal way to access the latest state of published aggregates for read-side services. + * The aggregate states are delivered to the instance of {@code Stand} through {@link StandFunnel} + * from {@link org.spine3.server.aggregate.AggregateRepository} instances. + * + *

In order to provide a flexibility in defining data access policies, {@code Stand} contains only the states + * of published aggregates. Please refer to {@link org.spine3.server.aggregate.Aggregate} for publication description. + * + *

Each {@link org.spine3.server.BoundedContext} contains the only instance of {@code Stand}. + * + * @author Alex Tymchenko + */ +public class Stand { + + private final ImmutableSet storages; + + private Stand(Builder builder) { + storages = builder.getEnabledStorages(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Store the new value for the {@link org.spine3.server.aggregate.Aggregate} to each of the configured + * instances of {@link StandStorage}. + * + *

Each state value is stored as one-to-one to its {@link org.spine3.protobuf.TypeUrl} obtained + * via {@link Any#getTypeUrl()}. + * + *

In case {@code Stand} already contains the state for this {@code Aggregate}, the value will be replaced. + * + *

The state of an {@code Aggregate} must not be null. + * + * @param newAggregateState the state of an {@link org.spine3.server.aggregate.Aggregate} put into Stand + */ + public void updateAggregateState(Any newAggregateState) { + // TODO[alex.tymchenko]: should we also check the given Aggregate is allowed to be published? Or is it an AggregateRepository responsibility? + checkState(newAggregateState != null, "newAggregateState must not be null"); + + for (StandStorage storage : storages) { + storage.write(newAggregateState); + } + } + + public static class Builder { + private final Set userProvidedStorages = Sets.newHashSet(); + private ImmutableSet enabledStorages; + + + public Builder addStorage(StandStorage storage) { + userProvidedStorages.add(storage); + return this; + } + + public Builder removeStorage(StandStorage storage) { + userProvidedStorages.remove(storage); + return this; + } + + @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection is immutable + public ImmutableSet getEnabledStorages() { + return enabledStorages; + } + + + private ImmutableSet composeEnabledStorages() { + final ImmutableSet.Builder builder = ImmutableSet.builder(); + if (userProvidedStorages.isEmpty()) { + final InMemoryStandStorage inMemoryStandStorage = InMemoryStandStorage.newBuilder() + .build(); + builder.add(inMemoryStandStorage); + } + builder.addAll(userProvidedStorages); + return builder.build(); + } + + + /** + * Build an instance of {@code Stand} + * + * @return the instance of Stand + */ + public Stand build() { + this.enabledStorages = composeEnabledStorages(); + final Stand result = new Stand(this); + return result; + } + } +} diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java new file mode 100644 index 00000000000..18b06a86141 --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -0,0 +1,140 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.Any; + +import java.util.concurrent.Executor; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * Delivers the latest {@link org.spine3.server.aggregate.Aggregate} states to the {@link Stand}. + * + *

Note: Unlike {@link org.spine3.server.event.EventBus} and {@link org.spine3.server.command.CommandBus}, which assume + * many publishers and many subscribers, the funnel may have zero or more publishers (typically, instances of + * {@link org.spine3.server.aggregate.AggregateRepository}), but the only subscriber, the instance of {@link Stand}. + * + *

In scope of a single {@link org.spine3.server.BoundedContext} there can be the only instance of {@code StandFunnel}. + * + * @author Alex Tymchenko + */ +public class StandFunnel { + + /** + * The instance of {@link Stand} to deliver the {@link org.spine3.server.aggregate.Aggregate} state updates to. + */ + private final Stand stand; + + /** + * An {@link Executor} used for execution of data delivery methods. + */ + private final Executor executor; + + + private StandFunnel(Builder builder) { + this.stand = builder.getStand(); + this.executor = builder.getExecutor(); + } + + public void post(final Any aggregateState) { + executor.execute(new Runnable() { + @Override + public void run() { + stand.updateAggregateState(aggregateState); + } + }); + } + + /** + * Create a new {@code Builder} for {@link StandFunnel}. + * + * @return a new {@code Builder} instance. + */ + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + /** + * The target {@code Stand} to deliver the {@code Aggregate} updates to. + */ + private Stand stand; + + /** + * Optional {@code Executor} for delivering the data to {@code Stand}. + * + *

If not set, a default value will be set by the builder. + */ + private Executor executor; + + public Stand getStand() { + return stand; + } + + /** + * Set the {@link Stand} instance for this {@code StandFunnel}. + * + *

The value must not be null. + * + *

If this method is not used, a default value will be used. + * + * @param stand the instance of {@link Stand}. + * @return {@code this} instance of {@code Builder} + */ + public Builder setStand(Stand stand) { + this.stand = checkNotNull(stand); + return this; + } + + public Executor getExecutor() { + return executor; + } + + /** + * Set the {@code Executor} instance for this {@code StandFunnel}. + * + *

The value must not be {@code null}. + * + * @param executor the instance of {@code Executor}. + * @return {@code this} instance of {@code Builder} + */ + public Builder setExecutor(Executor executor) { + this.executor = checkNotNull(executor); + return this; + } + + public StandFunnel build() { + checkState(stand != null, "Stand must be defined for the funnel"); + + if (executor == null) { + executor = MoreExecutors.directExecutor(); + } + + final StandFunnel result = new StandFunnel(this); + return result; + } + } +} diff --git a/server/src/main/java/org/spine3/server/stand/package-info.java b/server/src/main/java/org/spine3/server/stand/package-info.java new file mode 100644 index 00000000000..f2ccb2aee2e --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/package-info.java @@ -0,0 +1,33 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/** + * This package provides classes for working with Stand. + */ + +@SPI +@ParametersAreNonnullByDefault +package org.spine3.server.stand; + +import org.spine3.SPI; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java new file mode 100644 index 00000000000..4aff572fe6f --- /dev/null +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -0,0 +1,55 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.storage; + +import com.google.protobuf.Any; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.stand.Stand; + +/** + * Contract for {@link Stand} storage. + * + *

Stores the latest {@link org.spine3.server.aggregate.Aggregate} states. The state is passed as {@link Any} proto message + * and stored by its {@link TypeUrl}. + * + *

Not more than one {@code Aggregate} state object is stored for a single {@code TypeUrl}. + * + *

Allows to access the states via {@link TypeUrl} its. + * + * @author Alex Tymchenko + * @see Any#getTypeUrl() + * @see Stand + */ +public abstract class StandStorage extends AbstractStorage { + + protected StandStorage(boolean multitenant) { + super(multitenant); + } + + /** + * Update the storage with the {@link org.spine3.server.aggregate.Aggregate} state. + * + * @param aggregateState the state of {@code Aggregate} to store. + */ + public abstract void write(Any aggregateState); + +} diff --git a/server/src/main/java/org/spine3/server/storage/StorageFactory.java b/server/src/main/java/org/spine3/server/storage/StorageFactory.java index 9c0385b6dff..696a19bd4e6 100644 --- a/server/src/main/java/org/spine3/server/storage/StorageFactory.java +++ b/server/src/main/java/org/spine3/server/storage/StorageFactory.java @@ -45,6 +45,10 @@ public interface StorageFactory extends AutoCloseable { /** Creates a new {@link EventStorage} instance. */ EventStorage createEventStorage(); + /** Creates a new {@link StandStorage} instance. */ + StandStorage createStandStorage(); + + /** * Creates a new {@link AggregateStorage} instance. * @@ -68,4 +72,6 @@ public interface StorageFactory extends AutoCloseable { * @param the type of stream projection IDs */ ProjectionStorage createProjectionStorage(Class> projectionClass); + + } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java new file mode 100644 index 00000000000..7f8b66e45c1 --- /dev/null +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -0,0 +1,155 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.storage.memory; + +import com.google.common.collect.MapMaker; +import com.google.common.collect.Sets; +import com.google.protobuf.Any; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.storage.StandStorage; + +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * In-memory implementation of {@link StandStorage}. + * + *

Uses a {@link ConcurrentMap} for internal storage. + * + * @author Alex Tymchenko + */ +public class InMemoryStandStorage extends StandStorage { + + private final ConcurrentMap aggregateStates; + + private InMemoryStandStorage(Builder builder) { + super(builder.isMultitenant()); + aggregateStates = builder.getSeedDataMap(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public Any read(TypeUrl id) { + final Any result = aggregateStates.get(id); + return result; + } + + @Override + public void write(TypeUrl id, Any record) { + final TypeUrl recordType = TypeUrl.of(record.getTypeUrl()); + checkState(id == recordType, "The typeUrl of the record does not correspond to id"); + + doPut(aggregateStates, record); + } + + @Override + public void write(Any aggregateState) { + doPut(aggregateStates, aggregateState); + } + + public static class Builder { + + private final Set seedData = Sets.newHashSet(); + private ConcurrentMap seedDataMap; + private boolean multitenant; + + /** + * Add a seed data item. + * + *

Use this method to pre-fill the {@link StandStorage}. + * + *

The argument value must not be null. + * + * @param stateObject the state object to be added + * @return {@code this} instance of {@code Builder} + */ + public Builder addStateObject(Any stateObject) { + checkNotNull(stateObject, "stateObject must not be null"); + seedData.add(stateObject); + return this; + } + + /** + * Remove a previously added seed data item. + * + *

The argument value must not be null. + * + * @param stateObject the state object to be removed + * @return {@code this} instance of {@code Builder} + */ + public Builder removeStateObject(Any stateObject) { + checkNotNull(stateObject, "Cannot remove null stateObject"); + seedData.remove(stateObject); + return this; + } + + private ConcurrentMap buildSeedDataMap() { + final ConcurrentMap stateMap = new MapMaker().initialCapacity(seedData.size()) + .makeMap(); + for (final Any any : seedData) { + doPut(stateMap, any); + } + return stateMap; + } + + /** + * Do not expose the contents to public, as the returned {@code ConcurrentMap} must be mutable. + */ + private ConcurrentMap getSeedDataMap() { + return seedDataMap; + } + + public boolean isMultitenant() { + return multitenant; + } + + public Builder setMultitenant(boolean multitenant) { + this.multitenant = multitenant; + return this; + } + + /** + * Builds an instance of in-memory stand storage. + * + * @return an instance of in-memory storage + */ + public InMemoryStandStorage build() { + this.seedDataMap = buildSeedDataMap(); + final InMemoryStandStorage result = new InMemoryStandStorage(this); + return result; + } + + } + + private static void doPut(final ConcurrentMap targetMap, final Any any) { + final String typeUrlString = any.getTypeUrl(); + final TypeUrl typeUrl = TypeUrl.of(typeUrlString); + targetMap.put(typeUrl, any); + } + +} diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java index 6f5fc9884ff..003e5a018bb 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java @@ -27,6 +27,7 @@ import org.spine3.server.storage.EntityStorage; import org.spine3.server.storage.EventStorage; import org.spine3.server.storage.ProjectionStorage; +import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.StorageFactory; /** @@ -57,6 +58,14 @@ public EventStorage createEventStorage() { return new InMemoryEventStorage(isMultitenant()); } + @Override + public StandStorage createStandStorage() { + final InMemoryStandStorage result = InMemoryStandStorage.newBuilder() + .setMultitenant(isMultitenant()) + .build(); + return result; + } + /** NOTE: the parameter is unused. */ @Override public AggregateStorage createAggregateStorage(Class> unused) { From a3dcafdb9e37453c0e6f4ebd3a7d23e7c778f040 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 18:30:46 +0300 Subject: [PATCH 014/361] Add Stand to the BoundedContext builder. --- .../org/spine3/server/BoundedContext.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index 46b78d3fc44..bdbff04bda1 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -43,6 +43,8 @@ import org.spine3.server.integration.IntegrationEvent; import org.spine3.server.integration.IntegrationEventContext; import org.spine3.server.integration.grpc.IntegrationEventSubscriberGrpc; +import org.spine3.server.stand.Stand; +import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.StorageFactory; import org.spine3.validate.Validate; @@ -64,7 +66,7 @@ * @author Mikhail Melnik */ public class BoundedContext extends IntegrationEventSubscriberGrpc.IntegrationEventSubscriberImplBase - implements AutoCloseable { + implements AutoCloseable { /** The default name for a {@code BoundedContext}. */ public static final String DEFAULT_NAME = "Main"; @@ -80,6 +82,7 @@ public class BoundedContext extends IntegrationEventSubscriberGrpc.IntegrationEv private final StorageFactory storageFactory; private final CommandBus commandBus; private final EventBus eventBus; + private final Stand stand; private final List> repositories = Lists.newLinkedList(); @@ -89,6 +92,7 @@ private BoundedContext(Builder builder) { this.storageFactory = builder.storageFactory; this.commandBus = builder.commandBus; this.eventBus = builder.eventBus; + this.stand = builder.stand; } /** @@ -207,10 +211,10 @@ private static Event toEvent(IntegrationEvent integrationEvent) { final IntegrationEventContext sourceContext = integrationEvent.getContext(); final StringValue producerId = newStringValue(sourceContext.getBoundedContextName()); final EventContext context = EventContext.newBuilder() - .setEventId(sourceContext.getEventId()) - .setTimestamp(sourceContext.getTimestamp()) - .setProducerId(AnyPacker.pack(producerId)) - .build(); + .setEventId(sourceContext.getEventId()) + .setTimestamp(sourceContext.getTimestamp()) + .setProducerId(AnyPacker.pack(producerId)) + .build(); final Event result = Events.createEvent(integrationEvent.getMessage(), context); return result; } @@ -244,6 +248,7 @@ public static class Builder { private EventBus eventBus; private boolean multitenant; private EventEnricher eventEnricher; + private Stand stand; /** * Sets the name for a new bounded context. @@ -371,6 +376,16 @@ public EventEnricher getEventEnricher() { return eventEnricher; } + public Builder setStand(Stand stand) { + this.stand = checkNotNull(stand); + return this; + } + + @Nullable + public Stand getStand() { + return stand; + } + public BoundedContext build() { checkNotNull(storageFactory, "storageFactory must be set"); @@ -388,6 +403,10 @@ public BoundedContext build() { eventBus = createEventBus(); } + if (stand == null) { + stand = createStand(storageFactory); + } + commandBus.setMultitenant(this.multitenant); final BoundedContext result = new BoundedContext(this); @@ -432,6 +451,14 @@ private EventBus createEventBus() { .build(); return result; } + + private static Stand createStand(StorageFactory storageFactory) { + final StandStorage standStorage = storageFactory.createStandStorage(); + final Stand result = Stand.newBuilder() + .addStorage(standStorage) + .build(); + return result; + } } private enum LogSingleton { From 3ec2be30197707ba5fce8be6a389909d88888372 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 20:23:10 +0300 Subject: [PATCH 015/361] Allow to specify StandFunnel while building an instance of BoundedContext. --- .../org/spine3/server/BoundedContext.java | 38 +++++++++++++++++++ .../server/aggregate/AggregateRepository.java | 7 ++++ 2 files changed, 45 insertions(+) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index bdbff04bda1..494d06a7867 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -44,6 +44,7 @@ import org.spine3.server.integration.IntegrationEventContext; import org.spine3.server.integration.grpc.IntegrationEventSubscriberGrpc; import org.spine3.server.stand.Stand; +import org.spine3.server.stand.StandFunnel; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.StorageFactory; import org.spine3.validate.Validate; @@ -83,6 +84,7 @@ public class BoundedContext extends IntegrationEventSubscriberGrpc.IntegrationEv private final CommandBus commandBus; private final EventBus eventBus; private final Stand stand; + private final StandFunnel standFunnel; private final List> repositories = Lists.newLinkedList(); @@ -93,6 +95,7 @@ private BoundedContext(Builder builder) { this.commandBus = builder.commandBus; this.eventBus = builder.eventBus; this.stand = builder.stand; + this.standFunnel = builder.standFunnel; } /** @@ -231,6 +234,12 @@ public EventBus getEventBus() { return this.eventBus; } + /** Obtains instance of {@link StandFunnel} of this {@code BoundedContext}. */ + @CheckReturnValue + public StandFunnel getStandFunnel() { + return this.standFunnel; + } + /** * A builder for producing {@code BoundedContext} instances. * @@ -249,6 +258,8 @@ public static class Builder { private boolean multitenant; private EventEnricher eventEnricher; private Stand stand; + private StandFunnel standFunnel; + private Executor standFunnelExecutor; /** * Sets the name for a new bounded context. @@ -386,6 +397,16 @@ public Stand getStand() { return stand; } + @Nullable + public Executor getStandFunnelExecutor() { + return standFunnelExecutor; + } + + public Builder setStandFunnelExecutor(Executor standFunnelExecutor) { + this.standFunnelExecutor = standFunnelExecutor; + return this; + } + public BoundedContext build() { checkNotNull(storageFactory, "storageFactory must be set"); @@ -407,6 +428,8 @@ public BoundedContext build() { stand = createStand(storageFactory); } + standFunnel = createStandFunnel(standFunnelExecutor); + commandBus.setMultitenant(this.multitenant); final BoundedContext result = new BoundedContext(this); @@ -415,6 +438,21 @@ public BoundedContext build() { return result; } + private StandFunnel createStandFunnel(@Nullable Executor standFunnelExecutor) { + StandFunnel standFunnel; + if (standFunnelExecutor == null) { + standFunnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + } else { + standFunnel = StandFunnel.newBuilder() + .setExecutor(standFunnelExecutor) + .setStand(stand) + .build(); + } + return standFunnel; + } + private CommandStore createCommandStore() { final CommandStore result = new CommandStore(storageFactory.createCommandStorage()); return result; diff --git a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java index bec8e076324..6e720f382f5 100644 --- a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java +++ b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java @@ -19,6 +19,7 @@ */ package org.spine3.server.aggregate; +import com.google.protobuf.Any; import com.google.protobuf.Message; import org.spine3.base.Command; import org.spine3.base.CommandContext; @@ -26,12 +27,14 @@ import org.spine3.base.Errors; import org.spine3.base.Event; import org.spine3.base.FailureThrowable; +import org.spine3.protobuf.AnyPacker; import org.spine3.server.BoundedContext; import org.spine3.server.command.CommandDispatcher; import org.spine3.server.command.CommandStatusService; import org.spine3.server.entity.GetTargetIdFromCommand; import org.spine3.server.entity.Repository; import org.spine3.server.event.EventBus; +import org.spine3.server.stand.StandFunnel; import org.spine3.server.storage.AggregateEvents; import org.spine3.server.storage.AggregateStorage; import org.spine3.server.storage.Storage; @@ -81,6 +84,7 @@ public abstract class AggregateRepository> private final GetTargetIdFromCommand getIdFunction = GetTargetIdFromCommand.newInstance(); private final CommandStatusService commandStatusService; private final EventBus eventBus; + private final StandFunnel standFunnel; /** * Creates a new repository instance. @@ -91,6 +95,7 @@ public AggregateRepository(BoundedContext boundedContext) { super(boundedContext); this.commandStatusService = boundedContext.getCommandBus().getCommandStatusService(); this.eventBus = boundedContext.getEventBus(); + this.standFunnel = boundedContext.getStandFunnel(); } @Override @@ -228,6 +233,8 @@ public void dispatch(Command request) throws IllegalStateException { //noinspection OverlyBroadCatchBlock try { store(aggregate); + final Any packedAny = AnyPacker.pack(aggregate.getState()); + standFunnel.post(packedAny); } catch (Exception e) { commandStatusService.setToError(commandId, e); } From cf09824e69465f871351d0f74f3888fff0e2ce6f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 20:39:21 +0300 Subject: [PATCH 016/361] Investigate the build failure reason: enable --info option for gradlew on Travis. --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bf11a976a3e..95078a8cb45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,8 @@ before_install: - pip install --user codecov after_success: - - codecov \ No newline at end of file + - codecov + +script: + - ./gradlew assemble + - ./gradlew check --info \ No newline at end of file From 85305a2a4d97a191f0eb02f07bfe9982c1ef3b9c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 20:42:57 +0300 Subject: [PATCH 017/361] Remove redundant assemble target from the Travis script section - as Travis performs the same step by itself anyway. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 95078a8cb45..721b0d17cdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,4 @@ before_install: after_success: - codecov -script: - - ./gradlew assemble - - ./gradlew check --info \ No newline at end of file +script: ./gradlew check --info \ No newline at end of file From 58df7501c1611461bcfe64ad3788d46d91ea7025 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 20:56:42 +0300 Subject: [PATCH 018/361] Fix referencing a slf4j version property in examples configuration. --- examples/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/build.gradle b/examples/build.gradle index 4c3d9f8a843..4a0ed672fc7 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -2,7 +2,7 @@ dependencies { compile project(path: ':client') compile project(path: ':server') //Use jdk14 bindings for the sample application - compile group: 'org.slf4j', name: 'slf4j-jdk14', version: project.SLF4J_VERSION + compile group: 'org.slf4j', name: 'slf4j-jdk14', version: project.slf4JVersion } sourceSets.main.java.srcDir generatedSpineDir From 3d189aa25d0bbc2028b5a55f36a8459b78e8b1e3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 18 Aug 2016 21:19:15 +0300 Subject: [PATCH 019/361] Remove redundant null checks on Aggregate state update. --- server/src/main/java/org/spine3/server/stand/Stand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 05184c50c6e..2c27eaec316 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -72,7 +72,6 @@ public static Builder newBuilder() { */ public void updateAggregateState(Any newAggregateState) { // TODO[alex.tymchenko]: should we also check the given Aggregate is allowed to be published? Or is it an AggregateRepository responsibility? - checkState(newAggregateState != null, "newAggregateState must not be null"); for (StandStorage storage : storages) { storage.write(newAggregateState); From 143696787626df9c6e6df0ec0844a65f837a5325 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 25 Aug 2016 21:43:00 +0300 Subject: [PATCH 020/361] Deliver the aggregate and projection state updates to the Stand through StandFunnel. Create a simple implementation of Stand watch/unwatch functionality. --- .../server/aggregate/AggregateRepository.java | 5 +- .../projection/ProjectionRepository.java | 12 ++ .../java/org/spine3/server/stand/Stand.java | 122 +++++++++++++++++- .../org/spine3/server/stand/StandFunnel.java | 29 ++++- 4 files changed, 163 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java index 6e720f382f5..b81413a1055 100644 --- a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java +++ b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java @@ -233,8 +233,9 @@ public void dispatch(Command request) throws IllegalStateException { //noinspection OverlyBroadCatchBlock try { store(aggregate); - final Any packedAny = AnyPacker.pack(aggregate.getState()); - standFunnel.post(packedAny); + final Message state = aggregate.getState(); + final Any packedAny = AnyPacker.pack(state); + standFunnel.updateAggregateState(packedAny); } catch (Exception e) { commandStatusService.setToError(commandId, e); } diff --git a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java index 5bfb59651ef..6d0a582d38b 100644 --- a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java +++ b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java @@ -21,6 +21,7 @@ package org.spine3.server.projection; import com.google.common.collect.ImmutableSet; +import com.google.protobuf.Any; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; import io.grpc.stub.StreamObserver; @@ -29,12 +30,14 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.Events; +import org.spine3.protobuf.AnyPacker; import org.spine3.server.BoundedContext; import org.spine3.server.entity.EntityRepository; import org.spine3.server.event.EventDispatcher; import org.spine3.server.event.EventFilter; import org.spine3.server.event.EventStore; import org.spine3.server.event.EventStreamQuery; +import org.spine3.server.stand.StandFunnel; import org.spine3.server.storage.EntityStorage; import org.spine3.server.storage.ProjectionStorage; import org.spine3.server.storage.Storage; @@ -84,8 +87,12 @@ protected enum Status { /** An underlying entity storage used to store projections. */ private EntityStorage entityStorage; + /** An instance of {@link StandFunnel} to be informed about state updates */ + private StandFunnel standFunnel; + protected ProjectionRepository(BoundedContext boundedContext) { super(boundedContext); + this.standFunnel = boundedContext.getStandFunnel(); } protected Status getStatus() { @@ -223,6 +230,8 @@ public void dispatch(Event event) { /** * Dispatches event to a projection without checking the status of the repository. * + *

Also posts an update to the {@code StandFunnel} instance for this repository. + * * @param event the event to dispatch */ /* package */ void internalDispatch(Event event) { @@ -232,6 +241,9 @@ public void dispatch(Event event) { final P projection = load(id); projection.handle(eventMessage, context); store(projection); + final M state = projection.getState(); + final Any packedState = AnyPacker.pack(state); + standFunnel.post(packedState); final ProjectionStorage storage = projectionStorage(); final Timestamp eventTime = context.getTimestamp(); storage.writeLastHandledEventTime(eventTime); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 2c27eaec316..e8d1fee7dfd 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -23,13 +23,18 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.memory.InMemoryStandStorage; +import java.util.Collections; +import java.util.HashSet; import java.util.Set; - -import static com.google.common.base.Preconditions.checkState; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; /** * A container for storing the lastest {@link org.spine3.server.aggregate.Aggregate} states. @@ -48,9 +53,12 @@ public class Stand { private final ImmutableSet storages; + private final ConcurrentMap> callbacks = new ConcurrentHashMap<>(); + private final Executor callbackExecutor; private Stand(Builder builder) { storages = builder.getEnabledStorages(); + callbackExecutor = builder.getCallbackExecutor(); } public static Builder newBuilder() { @@ -76,13 +84,102 @@ public void updateAggregateState(Any newAggregateState) { for (StandStorage storage : storages) { storage.write(newAggregateState); } + feedToCallbacks(newAggregateState, callbacks, callbackExecutor); + } + + /** + * Update the state of an entity inside of the current instance of {@code Stand}. + * + * @param entityState the entity state + */ + public void update(Any entityState) { + feedToCallbacks(entityState, callbacks, callbackExecutor); + } + + /** + * Watch for a change of an entity state with a certain {@link TypeUrl}. + * + *

Once this instance of {@code Stand} receives an update of an entity with the given {@code TypeUrl}, + * all such callbacks are executed. + * + * @param typeUrl an instance of entity {@link TypeUrl} to watch for changes + * @param callback an instance of {@link StandUpdateCallback} executed upon entity update. + */ + public void watch(TypeUrl typeUrl, StandUpdateCallback callback) { + if (!callbacks.containsKey(typeUrl)) { + final Set emptySet = Collections.synchronizedSet(new HashSet()); + callbacks.put(typeUrl, emptySet); + } + + callbacks.get(typeUrl) + .add(callback); + } + + /** + * Stop watching for a change of an entity state with a certain {@link TypeUrl}. + * + *

Typically invoked to cancel the previous {@link #watch(TypeUrl, StandUpdateCallback)} call with the same arguments. + *

If no {@code watch} method was executed for the same {@code TypeUrl} and {@code StandUpdateCallback}, + * then {@code unwatch} has no effect. + * + * @param typeUrl an instance of entity {@link TypeUrl} to stop watch for changes + * @param callback an instance of {@link StandUpdateCallback} to be cancelled upon entity update. + */ + public void unwatch(TypeUrl typeUrl, StandUpdateCallback callback) { + final Set registeredCallbacks = callbacks.get(typeUrl); + + if (registeredCallbacks != null && registeredCallbacks.contains(callback)) { + registeredCallbacks.remove(callback); + } + } + + + private static void feedToCallbacks( + final Any entityState, + final ConcurrentMap> callbacks, + final Executor callbackExecutor + ) { + final String typeUrlString = entityState.getTypeUrl(); + final TypeUrl typeUrl = TypeUrl.of(typeUrlString); + + if (callbacks.containsKey(typeUrl)) { + for (final StandUpdateCallback callback : callbacks.get(typeUrl)) { + + callbackExecutor.execute(new Runnable() { + @Override + public void run() { + callback.onEntityStateUpdate(entityState); + } + }); + } + } } + /** + * A contract for the callbacks to be executed upon entity state change. + * + * @see #watch(TypeUrl, StandUpdateCallback) + * @see #unwatch(TypeUrl, StandUpdateCallback) + */ + @SuppressWarnings("InterfaceNeverImplemented") //it's OK, there may be no callbacks in the codebase + public interface StandUpdateCallback { + + void onEntityStateUpdate(Any newEntityState); + } + + public static class Builder { private final Set userProvidedStorages = Sets.newHashSet(); private ImmutableSet enabledStorages; + private Executor callbackExecutor; + /** + * Add an instance of {@link StandStorage} to be used to persist the latest an Aggregate states. + * + * @param storage an instance of {@code StandStorage} + * @return this instance of {@code Builder} + */ public Builder addStorage(StandStorage storage) { userProvidedStorages.add(storage); return this; @@ -93,6 +190,23 @@ public Builder removeStorage(StandStorage storage) { return this; } + public Executor getCallbackExecutor() { + return callbackExecutor; + } + + /** + * Sets an {@code Executor} to be used for executing callback methods. + * + *

If the {@code Executor} is not set, {@link MoreExecutors#directExecutor()} will be used. + * + * @param callbackExecutor the instance of {@code Executor} + * @return this instance of {@code Builder} + */ + public Builder setCallbackExecutor(Executor callbackExecutor) { + this.callbackExecutor = callbackExecutor; + return this; + } + @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection is immutable public ImmutableSet getEnabledStorages() { return enabledStorages; @@ -118,6 +232,10 @@ private ImmutableSet composeEnabledStorages() { */ public Stand build() { this.enabledStorages = composeEnabledStorages(); + if (callbackExecutor == null) { + callbackExecutor = MoreExecutors.directExecutor(); + } + final Stand result = new Stand(this); return result; } diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 18b06a86141..786c9d67cb8 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -58,7 +58,17 @@ private StandFunnel(Builder builder) { this.executor = builder.getExecutor(); } - public void post(final Any aggregateState) { + // TODO[alex.tymchenko]: Currently this method exists in order to distinguish the Aggregate state posted + // from the other entities. Stand may have a different behaviour handling the Aggregate. + // TODO[alex.tymchenko]: Try to get rid of this method in favour of {@link #update). + /** + * Post the state of an {@link org.spine3.server.aggregate.Aggregate} to an instance of {@link Stand}. + * + *

The data is posted as {@link Any} to allow transferring over the network. + * + * @param aggregateState the state of an {@code Aggregate} + */ + public void updateAggregateState(final Any aggregateState) { executor.execute(new Runnable() { @Override public void run() { @@ -67,6 +77,23 @@ public void run() { }); } + /** + * Post the state of an {@link org.spine3.server.entity.Entity} to an instance of {@link Stand}. + * + *

The data is posted as {@link Any} to allow transferring over the network. + * + * @param entityState the state of an {@code Entity} + */ + public void post(final Any entityState) { + executor.execute(new Runnable() { + @Override + public void run() { + stand.update(entityState); + } + }); + + } + /** * Create a new {@code Builder} for {@link StandFunnel}. * From 9046563645563e4346e6f64a9adc371d55cf16ff Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 25 Aug 2016 22:16:41 +0300 Subject: [PATCH 021/361] Change the Travis config to display only a stacktrace instead of the whole INFO output. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 721b0d17cdd..14c6aa59612 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ before_install: after_success: - codecov -script: ./gradlew check --info \ No newline at end of file +script: ./gradlew check --stacktrace \ No newline at end of file From 66cd784d04f0a14232638ff876f087444a65ba4d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 26 Aug 2016 16:25:35 +0300 Subject: [PATCH 022/361] Define the QueryService and supporting model. --- .../main/proto/spine/client/entities.proto | 88 +++++++++++++++++++ .../src/main/proto/spine/client/query.proto | 36 +++++++- .../proto/spine/client/query_service.proto | 40 +++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 client/src/main/proto/spine/client/entities.proto create mode 100644 client/src/main/proto/spine/client/query_service.proto diff --git a/client/src/main/proto/spine/client/entities.proto b/client/src/main/proto/spine/client/entities.proto new file mode 100644 index 00000000000..617ef40e6d2 --- /dev/null +++ b/client/src/main/proto/spine/client/entities.proto @@ -0,0 +1,88 @@ +// +// Copyright 2016, TeamDev Ltd. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +syntax = "proto3"; + +package spine.client; + +option (type_url_prefix) = "type.spine3.org"; +option java_generate_equals_and_hash = true; +option java_multiple_files = true; +option java_outer_classname = "EntitiesProto"; +option java_package = "org.spine3.client"; + +import "google/protobuf/any.proto"; +import "google/protobuf/field_mask.proto"; + +import "spine/annotations.proto"; +import "spine/ui/language.proto"; +import "spine/base/response.proto"; + +// Represents an ID of an entity. +// +// Acts as an read-side API identifier for Projection state, Aggregate state and other entities. +message EntityId { + + // An ID of an entity. + google.protobuf.Any id = 1; +} + +// Defines the entity type and filters. +// +// Use Target to specify and narrow down the source for Topic and Query by an entity type and various criteria. +message Target { + + // Represents TypeUrl for the entity of interest. + string type = 1; + + // Modifies the entity collection of a specified type. + // Allows either to narrow it down by filtering out the entity objects or include all entity instances + // into the target. + oneof criterion { + + // The instruction to include all objects of a given type. + bool include_all = 2; + + // Filter the objects of a given type by certain criteria. + EntityFilters filters = 3; + } +} + +// Set of filters used to modify the entity collection. +// +// To be used in scope of read-side API for specifying the target of query and subscription operations. +message EntityFilters { + + // Match entities by IDs. + EntityIdFilter id_filter = 1; + + // Reserved for more filter types + reserved 2 to 40; +} + +// Allows to add an ID filter for the read operations. +// +// Used to modify the collection of interest by filtering out the objects with identifiers not included into the filter. +// SQL equivalent is "... where entity.id IN (...)". +message EntityIdFilter { + + // The collection of entity IDs. + repeated EntityId ids = 1; +} + diff --git a/client/src/main/proto/spine/client/query.proto b/client/src/main/proto/spine/client/query.proto index 0280acc61d9..1a48b6ef51f 100644 --- a/client/src/main/proto/spine/client/query.proto +++ b/client/src/main/proto/spine/client/query.proto @@ -27,11 +27,45 @@ option java_multiple_files = true; option java_outer_classname = "QueryProto"; option java_package = "org.spine3.client"; + +import "google/protobuf/any.proto"; +import "google/protobuf/field_mask.proto"; + import "spine/annotations.proto"; import "spine/ui/language.proto"; +import "spine/base/response.proto"; +import "spine/client/entities.proto"; + message QueryContext { ui.Language language = 1; //TODO:2015-12-16:alexander.yevsyukov: Finish -} \ No newline at end of file +} + +// The main abstraction over the read-side API. +// +// Allows clients to form the requests to the read-side through the QueryService. +// Query execution typically results in a QueryResponse object. +message Query { + + // Defines the entity of interest, e.g. entity type URL and a set of fetch criteria. + Target target = 1; + + // Field mask to be applied to the items of the query result. + google.protobuf.FieldMask field_mask = 2; + + // Reserved for utility fields like query creation date, required response timeframe etc. + reserved 3 to 6; +} + +message QueryResponse { + + // Represents the base part of the response. I.e. whether the Query has been acked or not. + base.Response response = 1; + + // Reserved for more query response attributes, e.g. to describe paginated response etc. + reserved 2 to 4; + + repeated google.protobuf.Any messages = 5; +} diff --git a/client/src/main/proto/spine/client/query_service.proto b/client/src/main/proto/spine/client/query_service.proto new file mode 100644 index 00000000000..4689c7d8c38 --- /dev/null +++ b/client/src/main/proto/spine/client/query_service.proto @@ -0,0 +1,40 @@ +// +// Copyright 2016, TeamDev Ltd. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +syntax = "proto3"; + +package spine.client; + +option (type_url_prefix) = "type.spine3.org"; +option java_package = "org.spine3.client.grpc"; +option java_multiple_files = true; +option java_outer_classname = "QueryServiceProto"; +option java_generate_equals_and_hash = true; + +import "google/protobuf/any.proto"; + +import "spine/annotations.proto"; +import "spine/client/query.proto"; + +// A service for querying the read-side from clients. +service QueryService { + + // Reads a certain data from the read-side by setting the criteria via Query. + rpc Read (Query) returns (QueryResponse); +} From 5cd7627c2867c0ce82d3baae4a08ead1deba80bb Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 26 Aug 2016 20:07:30 +0300 Subject: [PATCH 023/361] Expose the entity type from the Repository as TypeUrl instance. --- .../main/java/org/spine3/server/entity/Repository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/main/java/org/spine3/server/entity/Repository.java b/server/src/main/java/org/spine3/server/entity/Repository.java index 913930640cf..1a39291a117 100644 --- a/server/src/main/java/org/spine3/server/entity/Repository.java +++ b/server/src/main/java/org/spine3/server/entity/Repository.java @@ -20,6 +20,8 @@ package org.spine3.server.entity; +import org.spine3.protobuf.KnownTypes; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.reflect.Classes; import org.spine3.server.storage.Storage; @@ -115,6 +117,14 @@ protected Class getEntityClass() { return Classes.getGenericParameterType(getClass(), ENTITY_CLASS_GENERIC_INDEX); } + /** Returns the {@link TypeUrl} for the entities managed by this repository */ + @CheckReturnValue + public TypeUrl getEntityType() { + final Class entityClass = getEntityClass(); + final TypeUrl typeUrl = KnownTypes.getTypeUrl(entityClass.getSimpleName()); + return typeUrl; + } + /** * Create a new entity instance with its default state. * From fe74d8d9e611a7b6a8a232a9d26190abdd6bc8c6 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 26 Aug 2016 20:11:00 +0300 Subject: [PATCH 024/361] Register the repositories in the Stand for the current BoundedContext and simplify the entity state delivery to the Stand through its funnel. --- .../org/spine3/server/BoundedContext.java | 2 + .../server/aggregate/AggregateRepository.java | 2 +- .../java/org/spine3/server/stand/Stand.java | 123 +++++++++++++----- .../org/spine3/server/stand/StandFunnel.java | 19 --- 4 files changed, 90 insertions(+), 56 deletions(-) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index 494d06a7867..ec23f06b789 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -140,6 +140,7 @@ public void close() throws Exception { private void shutDownRepositories() throws Exception { for (Repository repository : repositories) { + stand.deregisterSupplierForType(repository.getEntityType()); repository.close(); } repositories.clear(); @@ -190,6 +191,7 @@ public boolean isMultitenant() { if (repository instanceof EventDispatcher) { eventBus.register((EventDispatcher) repository); } + stand.registerTypeSupplier(repository); } private void checkStorageAssigned(Repository repository) { diff --git a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java index b81413a1055..24a9e8ca9bf 100644 --- a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java +++ b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java @@ -235,7 +235,7 @@ public void dispatch(Command request) throws IllegalStateException { store(aggregate); final Message state = aggregate.getState(); final Any packedAny = AnyPacker.pack(state); - standFunnel.updateAggregateState(packedAny); + standFunnel.post(packedAny); } catch (Exception e) { commandStatusService.setToError(commandId, e); } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index e8d1fee7dfd..4ff97d3f8e9 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -26,6 +26,9 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import org.spine3.protobuf.TypeUrl; +import org.spine3.server.aggregate.AggregateRepository; +import org.spine3.server.entity.Entity; +import org.spine3.server.entity.Repository; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.memory.InMemoryStandStorage; @@ -52,10 +55,37 @@ */ public class Stand { + /** + * Persistent storages for the latest {@link org.spine3.server.aggregate.Aggregate} states. + * + *

Any {@code Aggregate} state delivered to this instance of {@code Stand} is persisted in all of the storages. + */ private final ImmutableSet storages; + + /** + * A set of callbacks to be executed upon the incoming updates. + * + *

Each callback is triggerred if the entity with a matching {@code TypeUrl} is delivered to this {@code Stand}. + *

There may be any number of callbacks for a given {@code TypeUrl}. + */ private final ConcurrentMap> callbacks = new ConcurrentHashMap<>(); + + /** An instance of executor used to invoke callbacks */ private final Executor callbackExecutor; + /** The mapping between {@code TypeUrl} instances and repositories providing the entities of this type */ + private final ConcurrentMap typeToRepositoryMap = new ConcurrentHashMap<>(); + + /** + * Store the known {@link org.spine3.server.aggregate.Aggregate} types in order to distinguish them among all + * instances of {@code TypeUrl}. + * + *

Once this instance of {@code Stand} receives an update as {@link Any}, the {@code Aggregate} states + * are persisted for further usage. While the rest of entity updates are not; they are only propagated to + * the registered callbacks. + */ + private final Set knownAggregateTypes = Sets.newConcurrentHashSet(); + private Stand(Builder builder) { storages = builder.getEnabledStorages(); callbackExecutor = builder.getCallbackExecutor(); @@ -66,34 +96,49 @@ public static Builder newBuilder() { } /** - * Store the new value for the {@link org.spine3.server.aggregate.Aggregate} to each of the configured - * instances of {@link StandStorage}. + * Update the state of an entity inside of the current instance of {@code Stand}. + * + *

In case the entity update represents the new {@link org.spine3.server.aggregate.Aggregate} state, + * store the new value for the {@code Aggregate} to each of the configured instances of {@link StandStorage}. * - *

Each state value is stored as one-to-one to its {@link org.spine3.protobuf.TypeUrl} obtained + *

Each {@code Aggregate } state value is stored as one-to-one to its {@link org.spine3.protobuf.TypeUrl} obtained * via {@link Any#getTypeUrl()}. * *

In case {@code Stand} already contains the state for this {@code Aggregate}, the value will be replaced. * - *

The state of an {@code Aggregate} must not be null. + *

The state updates which are not originated from the {@code Aggregate} are not stored in the {@code Stand}. * - * @param newAggregateState the state of an {@link org.spine3.server.aggregate.Aggregate} put into Stand + *

In any case, the state update is then propagated to the callbacks. The set of matched callbacks + * is determined by filtering all the registered callbacks by the entity {@code TypeUrl}. + * + *

The matching callbacks are executed with the {@link #callbackExecutor}. + * + * @param entityState the entity state */ - public void updateAggregateState(Any newAggregateState) { - // TODO[alex.tymchenko]: should we also check the given Aggregate is allowed to be published? Or is it an AggregateRepository responsibility? + @SuppressWarnings("MethodWithMultipleLoops") /* It's fine, since the second loop is most likely + * executed in async fashion. */ + public void update(final Any entityState) { + final String typeUrlString = entityState.getTypeUrl(); + final TypeUrl typeUrl = TypeUrl.of(typeUrlString); + + final boolean isAggregateUpdate = knownAggregateTypes.contains(typeUrl); - for (StandStorage storage : storages) { - storage.write(newAggregateState); + if (isAggregateUpdate) { + for (StandStorage storage : storages) { + storage.write(entityState); + } } - feedToCallbacks(newAggregateState, callbacks, callbackExecutor); - } - /** - * Update the state of an entity inside of the current instance of {@code Stand}. - * - * @param entityState the entity state - */ - public void update(Any entityState) { - feedToCallbacks(entityState, callbacks, callbackExecutor); + if (callbacks.containsKey(typeUrl)) { + for (final StandUpdateCallback callback : callbacks.get(typeUrl)) { + callbackExecutor.execute(new Runnable() { + @Override + public void run() { + callback.onEntityStateUpdate(entityState); + } + }); + } + } } /** @@ -134,27 +179,33 @@ public void unwatch(TypeUrl typeUrl, StandUpdateCallback callback) { } - private static void feedToCallbacks( - final Any entityState, - final ConcurrentMap> callbacks, - final Executor callbackExecutor - ) { - final String typeUrlString = entityState.getTypeUrl(); - final TypeUrl typeUrl = TypeUrl.of(typeUrlString); - - if (callbacks.containsKey(typeUrl)) { - for (final StandUpdateCallback callback : callbacks.get(typeUrl)) { - - callbackExecutor.execute(new Runnable() { - @Override - public void run() { - callback.onEntityStateUpdate(entityState); - } - }); - } + /** + * Register a supplier for the objects of a certain {@link TypeUrl} to be able + * to read them in response to a {@link org.spine3.client.Query}. + * + *

In case the supplier is an instance of {@link AggregateRepository}, the {@code Repository} is not registered + * as type supplier, since the {@code Aggregate} reads are performed by accessing + * the latest state in the supplied {@code StandStorage}. + * + *

However, the type of the {@code AggregateRepository} instance is recorded for the postponed processing + * of updates. + * + * @see #update(Any) + */ + public > void registerTypeSupplier(Repository repository) { + final TypeUrl entityType = repository.getEntityType(); + if (!(repository instanceof AggregateRepository)) { + typeToRepositoryMap.put(entityType, repository); + } else { + knownAggregateTypes.add(entityType); } } + // TODO[alex.tymchenko]: perhaps, we need to close Stand instead of doing this upon repository shutdown (see usages). + public void deregisterSupplierForType(TypeUrl typeUrl) { + typeToRepositoryMap.remove(typeUrl); + } + /** * A contract for the callbacks to be executed upon entity state change. * diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 786c9d67cb8..fbe708fa6cf 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -58,25 +58,6 @@ private StandFunnel(Builder builder) { this.executor = builder.getExecutor(); } - // TODO[alex.tymchenko]: Currently this method exists in order to distinguish the Aggregate state posted - // from the other entities. Stand may have a different behaviour handling the Aggregate. - // TODO[alex.tymchenko]: Try to get rid of this method in favour of {@link #update). - /** - * Post the state of an {@link org.spine3.server.aggregate.Aggregate} to an instance of {@link Stand}. - * - *

The data is posted as {@link Any} to allow transferring over the network. - * - * @param aggregateState the state of an {@code Aggregate} - */ - public void updateAggregateState(final Any aggregateState) { - executor.execute(new Runnable() { - @Override - public void run() { - stand.updateAggregateState(aggregateState); - } - }); - } - /** * Post the state of an {@link org.spine3.server.entity.Entity} to an instance of {@link Stand}. * From 6266f5af14f74509a773e12447156b3331870ce1 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 30 Aug 2016 15:52:58 +0300 Subject: [PATCH 025/361] Change Server in-memory-storage smoke test to play nicely with multithreading. --- .../java/org/spine3/examples/aggregate/server/Server.java | 2 +- .../org/spine3/examples/aggregate/server/ServerShould.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java index 3f21f283a5f..5343bfd69db 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java @@ -72,6 +72,7 @@ public Server(StorageFactory storageFactory) { public void start() throws IOException { grpcContainer.start(); grpcContainer.addShutdownHook(); + log().info("Server started, listening to commands on the port " + DEFAULT_CLIENT_SERVICE_PORT); } public void awaitTermination() { @@ -87,7 +88,6 @@ public void shutdown() throws Exception { public static void main(String[] args) throws IOException { final Server server = new Server(InMemoryStorageFactory.getInstance()); server.start(); - log().info("Server started, listening to commands on the port " + DEFAULT_CLIENT_SERVICE_PORT); server.awaitTermination(); } diff --git a/examples/src/test/java/org/spine3/examples/aggregate/server/ServerShould.java b/examples/src/test/java/org/spine3/examples/aggregate/server/ServerShould.java index 21cda67cb4a..d6cb30c5898 100644 --- a/examples/src/test/java/org/spine3/examples/aggregate/server/ServerShould.java +++ b/examples/src/test/java/org/spine3/examples/aggregate/server/ServerShould.java @@ -25,6 +25,8 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import static com.google.common.base.Throwables.propagate; @@ -34,6 +36,7 @@ public class ServerShould { @Test public void run_on_in_memory_storage() throws Exception { final Server[] serverRef = new Server[1]; + final CountDownLatch serverStartLatch = new CountDownLatch(1); final Thread serverThread = new Thread(new Runnable() { @Override @@ -43,6 +46,7 @@ public void run() { try { server.start(); server.awaitTermination(); + serverStartLatch.countDown(); } catch (IOException e) { throw propagate(e); } @@ -51,6 +55,7 @@ public void run() { serverThread.start(); + serverStartLatch.await(5, TimeUnit.SECONDS); //noinspection ZeroLengthArrayAllocation ClientApp.main(new String[0]); From 708719b868c3bf21b01fd0f08880f32b0e14b170 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 30 Aug 2016 15:55:42 +0300 Subject: [PATCH 026/361] Introduce find and findBulk methods to the EntityRepository and EntityStorage hierarchy. Reorganize hierarchy, so that ProjectionRepository and ProjectionStorage extend EntityRepository and EntityStorage. --- .../org/spine3/server/BoundedContext.java | 2 +- .../java/org/spine3/server/entity/Entity.java | 2 +- .../server/entity/EntityRepository.java | 70 ++++++++++++++++++- .../org/spine3/server/entity/Repository.java | 42 ++++++++--- .../projection/ProjectionRepository.java | 3 +- .../java/org/spine3/server/stand/Stand.java | 40 ++++++++++- .../spine3/server/storage/EntityStorage.java | 23 +++++- .../server/storage/ProjectionStorage.java | 11 ++- .../storage/memory/InMemoryEntityStorage.java | 22 ++++++ .../memory/InMemoryProjectionStorage.java | 7 ++ 10 files changed, 198 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index ec23f06b789..49c46736c88 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -140,7 +140,7 @@ public void close() throws Exception { private void shutDownRepositories() throws Exception { for (Repository repository : repositories) { - stand.deregisterSupplierForType(repository.getEntityType()); + stand.deregisterSupplierForType(repository.getEntityStateType()); repository.close(); } repositories.clear(); diff --git a/server/src/main/java/org/spine3/server/entity/Entity.java b/server/src/main/java/org/spine3/server/entity/Entity.java index eb5997f87d5..fc696e33b2b 100644 --- a/server/src/main/java/org/spine3/server/entity/Entity.java +++ b/server/src/main/java/org/spine3/server/entity/Entity.java @@ -75,7 +75,7 @@ public abstract class Entity { private static final int ID_CLASS_GENERIC_INDEX = 0; /** The index of the declaration of the generic parameter type {@code S} in this class. */ - private static final int STATE_CLASS_GENERIC_INDEX = 1; + protected static final int STATE_CLASS_GENERIC_INDEX = 1; /** Supported ID types except {@link Message}s. */ private static final ImmutableSet> SUPPORTED_SIMPLE_ID_TYPES = ImmutableSet.>builder() diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index bdda363c0c4..6dd2eae4ffb 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -20,6 +20,8 @@ package org.spine3.server.entity; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; import com.google.protobuf.Any; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; @@ -30,8 +32,10 @@ import org.spine3.server.storage.Storage; import org.spine3.server.storage.StorageFactory; +import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Iterator; import static org.spine3.protobuf.AnyPacker.unpack; import static org.spine3.validate.Validate.isDefault; @@ -91,6 +95,66 @@ public E load(I id) { return entity; } + /** + * Finds the entity with the passed ID. + * + *

As opposed to {@link #load(Object)}, the invocation of this method never leads to creation of a new entity. + * + *

NOTE: The storage must be assigned before calling this method. + * + * @param id the id of the entity to find. + * @return the entity or {@code null} if there's no entity with such id + */ + @CheckReturnValue + @Nullable + public E find(I id) { + // TODO[alex.tymchenko]: check whether using #load(id) straightaway is a good idea; + return this.load(id); + } + + + /** + * Finds all the entities in this repository with IDs, contained within the passed {@code Iterable}. + * + *

Provides a convenience wrapper around multiple invocations of {@link #find(Object)}. Descendants may + * optimize the execution of this method, choosing the most suitable way for the particular storage engine used. + * + *

The order of resulting objects in the {@link Iterable} is not guaranteed to be the same as the order + * of IDs passed as argument. + * + *

The instance of {@code Iterable} is always returned with no {@code null} values. + * + *

In case IDs contain duplicates, the resulting {@code Iterable} may also contain duplicates, depending + * on particular implementation. + * + *

Similar to {@link #find(Object)}, the invocation of this method never leads to creation of new objects. + * + *

NOTE: The storage must be assigned before calling this method. + * + * @param ids entity IDs to search for + * @return all the entities in this repository with the IDs matching the given {@code Iterable} + */ + @CheckReturnValue + public ImmutableCollection findBulk(Iterable ids) { + final EntityStorage storage = entityStorage(); + final Iterable entityStorageRecords = storage.readBulk(ids); + + final Iterator idIterator = ids.iterator(); + final Iterator recordIterator = entityStorageRecords.iterator(); + final ImmutableList.Builder builder = ImmutableList.builder(); + + while (idIterator.hasNext() && recordIterator.hasNext()) { + final I id = idIterator.next(); + final EntityStorageRecord record = recordIterator.next(); + final E entity = toEntity(id, record); + builder.add(entity); + } + + final ImmutableList result = builder.build(); + return result; + + } + private E toEntity(I id, EntityStorageRecord record) { final E entity = create(id); final M state = unpack(record.getState()); @@ -104,9 +168,9 @@ private EntityStorageRecord toEntityRecord(E entity) { final Timestamp whenModified = entity.whenModified(); final int version = entity.getVersion(); final EntityStorageRecord.Builder builder = EntityStorageRecord.newBuilder() - .setState(stateAny) - .setWhenModified(whenModified) - .setVersion(version); + .setState(stateAny) + .setWhenModified(whenModified) + .setVersion(version); return builder.build(); } } diff --git a/server/src/main/java/org/spine3/server/entity/Repository.java b/server/src/main/java/org/spine3/server/entity/Repository.java index 1a39291a117..3047966918e 100644 --- a/server/src/main/java/org/spine3/server/entity/Repository.java +++ b/server/src/main/java/org/spine3/server/entity/Repository.java @@ -26,12 +26,14 @@ import org.spine3.server.reflect.Classes; import org.spine3.server.storage.Storage; import org.spine3.server.storage.StorageFactory; +import org.spine3.type.ClassName; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Throwables.propagate; import static java.lang.reflect.Modifier.isPrivate; @@ -42,7 +44,6 @@ * * @param the type of IDs of entities managed by the repository * @param the entity type - * * @author Alexander Yevsyukov */ public abstract class Repository> implements AutoCloseable { @@ -64,6 +65,20 @@ public abstract class Repository> implements AutoClose /** The data storage for this repository. */ private Storage storage; + /** + * Cached value for the entity state type. + * + *

Used to optimise heavy {@link #getEntityStateType()} calls. + **/ + private volatile TypeUrl entityStateType; + + /** + * Cached value for the entity class. + * + *

Used to optimize heavy {@link #getEntityClass()} calls. + **/ + private volatile Class entityClass; + /** * Creates the repository in the passed {@link BoundedContext}. * @@ -114,15 +129,24 @@ protected Class getIdClass() { /** Returns the class of entities managed by this repository. */ @CheckReturnValue protected Class getEntityClass() { - return Classes.getGenericParameterType(getClass(), ENTITY_CLASS_GENERIC_INDEX); + if (entityClass == null) { + entityClass = Classes.getGenericParameterType(getClass(), ENTITY_CLASS_GENERIC_INDEX); + } + checkNotNull(entityClass); + return entityClass; } - /** Returns the {@link TypeUrl} for the entities managed by this repository */ + /** Returns the {@link TypeUrl} for the state objects wrapped by entities managed by this repository */ @CheckReturnValue - public TypeUrl getEntityType() { - final Class entityClass = getEntityClass(); - final TypeUrl typeUrl = KnownTypes.getTypeUrl(entityClass.getSimpleName()); - return typeUrl; + public TypeUrl getEntityStateType() { + if (entityStateType == null) { + final Class entityClass = getEntityClass(); + final Class stateClass = Classes.getGenericParameterType(entityClass, Entity.STATE_CLASS_GENERIC_INDEX); + final ClassName stateClassName = ClassName.of(stateClass); + entityStateType = KnownTypes.getTypeUrl(stateClassName); + } + checkNotNull(entityStateType); + return entityStateType; } /** @@ -145,7 +169,7 @@ public E create(I id) { /** * Stores the passed object. * - *

The storage must be assigned before calling this method. + *

NOTE: The storage must be assigned before calling this method. * * @param obj an instance to store */ @@ -154,7 +178,7 @@ public E create(I id) { /** * Loads the entity with the passed ID. * - *

The storage must be assigned before calling this method. + *

NOTE: The storage must be assigned before calling this method. * * @param id the id of the entity to load * @return the entity or {@code null} if there's no entity with such id diff --git a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java index 6d0a582d38b..74e7d86e36d 100644 --- a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java +++ b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java @@ -45,6 +45,7 @@ import org.spine3.server.type.EventClass; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Set; /** @@ -88,7 +89,7 @@ protected enum Status { private EntityStorage entityStorage; /** An instance of {@link StandFunnel} to be informed about state updates */ - private StandFunnel standFunnel; + private final StandFunnel standFunnel; protected ProjectionRepository(BoundedContext boundedContext) { super(boundedContext); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 4ff97d3f8e9..497aa41dc2e 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -21,10 +21,13 @@ */ package org.spine3.server.stand; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import org.spine3.client.QueryOrBuilder; +import org.spine3.client.Target; import org.spine3.protobuf.TypeUrl; import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.entity.Entity; @@ -32,6 +35,8 @@ import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.memory.InMemoryStandStorage; +import javax.annotation.CheckReturnValue; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -178,6 +183,39 @@ public void unwatch(TypeUrl typeUrl, StandUpdateCallback callback) { } } + /** + * Read all {@link Entity} types exposed for reading by this instance of {@code Stand}. + * + * @return the set of types as {@link TypeUrl} instances + */ + @CheckReturnValue + public ImmutableSet getAvailableTypes() { + final Set types = typeToRepositoryMap.keySet(); + final ImmutableSet result = ImmutableSet.copyOf(types); + return result; + } + + @CheckReturnValue + public ImmutableCollection read(QueryOrBuilder query) { + final Collection result = new HashSet<>(); + + final Target target = query.getTarget(); + final String type = target.getType(); + + final TypeUrl typeUrl = TypeUrl.of(type); + final Repository repository = typeToRepositoryMap.get(typeUrl); + + if (repository != null) { + if(target.getIncludeAll()) { + + } + } + + final ImmutableCollection immutableResult = new ImmutableSet.Builder().addAll(result) + .build(); + return immutableResult; + } + /** * Register a supplier for the objects of a certain {@link TypeUrl} to be able @@ -193,7 +231,7 @@ public void unwatch(TypeUrl typeUrl, StandUpdateCallback callback) { * @see #update(Any) */ public > void registerTypeSupplier(Repository repository) { - final TypeUrl entityType = repository.getEntityType(); + final TypeUrl entityType = repository.getEntityStateType(); if (!(repository instanceof AggregateRepository)) { typeToRepositoryMap.put(entityType, repository); } else { diff --git a/server/src/main/java/org/spine3/server/storage/EntityStorage.java b/server/src/main/java/org/spine3/server/storage/EntityStorage.java index a473ec9d833..eb2fd3ed350 100644 --- a/server/src/main/java/org/spine3/server/storage/EntityStorage.java +++ b/server/src/main/java/org/spine3/server/storage/EntityStorage.java @@ -63,6 +63,24 @@ public void write(I id, EntityStorageRecord record) { writeInternal(id, record); } + /** + * Reads the records from the storage with the given IDs. + * + *

The size of {@link Iterable} returned is always the same as the size of given IDs. + * + *

In case there is no record for a particular ID, {@code null} will be present in the result. + * + * @param ids record IDs of interest + * @return the {@link Iterable} containing the records matching the given IDs + * @throws IllegalStateException if the storage was closed before + */ + public Iterable readBulk(Iterable ids) { + checkNotClosed(); + checkNotNull(ids); + + return readBulkInternal(ids); + } + // // Internal storage methods //--------------------------- @@ -81,8 +99,11 @@ public void write(I id, EntityStorageRecord record) { * *

Rewrites it if a record with this ID already exists in the storage. * - * @param id an ID of the record + * @param id an ID of the record * @param record a record to store */ protected abstract void writeInternal(I id, EntityStorageRecord record); + + /** @see org.spine3.server.storage.EntityStorage#readBulk(java.lang.Iterable) */ + protected abstract Iterable readBulkInternal(Iterable ids); } diff --git a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java index 87971fbdfd2..3529fd9114b 100644 --- a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java @@ -36,25 +36,22 @@ * @author Alexander Litus */ @SPI -public abstract class ProjectionStorage extends AbstractStorage { +public abstract class ProjectionStorage extends EntityStorage { public ProjectionStorage(boolean multitenant) { super(multitenant); } + @Nullable @Override - public EntityStorageRecord read(I id) { - checkNotClosed(); - + protected EntityStorageRecord readInternal(I id) { final EntityStorage storage = getEntityStorage(); final EntityStorageRecord record = storage.read(id); return record; } @Override - public void write(I id, EntityStorageRecord record) { - checkNotClosed(); - + protected void writeInternal(I id, EntityStorageRecord record) { final EntityStorage storage = getEntityStorage(); storage.write(id, record); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryEntityStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryEntityStorage.java index 4391ffbcd40..3e9f88ccea6 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryEntityStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryEntityStorage.java @@ -25,6 +25,8 @@ import org.spine3.server.users.CurrentTenant; import org.spine3.users.TenantId; +import java.util.Collection; +import java.util.LinkedList; import java.util.Map; import static com.google.common.base.Preconditions.checkState; @@ -46,6 +48,26 @@ protected InMemoryEntityStorage(boolean multitenant) { super(multitenant); } + @SuppressWarnings("MethodWithMultipleLoops") /* It's OK for in-memory implementation + * as it is used primarily in tests. */ + @Override + protected Iterable readBulkInternal(final Iterable givenIds) { + final Map storage = getStorage(); + + final Collection result = new LinkedList<>(); + for (I recordId : storage.keySet()) { + for (I givenId : givenIds) { + if(recordId.equals(givenId)) { + final EntityStorageRecord matchingRecord = storage.get(recordId); + result.add(matchingRecord); + continue; + } + result.add(null); + } + } + return result; + } + protected static InMemoryEntityStorage newInstance(boolean multitenant) { return new InMemoryEntityStorage<>(multitenant); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java index 4edf653c214..51304c59050 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java @@ -22,6 +22,7 @@ import com.google.protobuf.Timestamp; import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.ProjectionStorage; import static com.google.common.base.Preconditions.checkNotNull; @@ -69,4 +70,10 @@ public void close() throws Exception { entityStorage.close(); super.close(); } + + @Override + protected Iterable readBulkInternal(Iterable ids) { + final Iterable result = entityStorage.readBulk(ids); + return result; + } } From 31b39d40b8e4b8e5d0c66ac89d63732a91c5b400 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 31 Aug 2016 16:01:24 +0300 Subject: [PATCH 027/361] Rename EntityStorage to RecordStorage. --- .../spine3/server/entity/EntityRepository.java | 14 +++++++------- .../projection/ProjectionRepository.java | 11 +++++------ .../server/storage/ProjectionStorage.java | 8 ++++---- .../{EntityStorage.java => RecordStorage.java} | 8 ++++---- .../spine3/server/storage/StorageFactory.java | 4 ++-- .../memory/InMemoryProjectionStorage.java | 18 +++++++++--------- ...Storage.java => InMemoryRecordStorage.java} | 12 ++++++------ .../storage/memory/InMemoryStorageFactory.java | 8 ++++---- ...tity_storage.proto => record_storage.proto} | 4 ++-- .../spine3/server/entity/RepositoryShould.java | 6 +++--- .../projection/ProjectionRepositoryShould.java | 6 +++--- .../server/storage/EntityStorageShould.java | 14 +++++++------- .../memory/InMemoryEntityStorageShould.java | 12 ++++++------ .../InMemoryProjectionStorageShould.java | 4 ++-- 14 files changed, 64 insertions(+), 65 deletions(-) rename server/src/main/java/org/spine3/server/storage/{EntityStorage.java => RecordStorage.java} (93%) rename server/src/main/java/org/spine3/server/storage/memory/{InMemoryEntityStorage.java => InMemoryRecordStorage.java} (90%) rename server/src/main/proto/spine/server/storage/{entity_storage.proto => record_storage.proto} (94%) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 6dd2eae4ffb..10f281eb6a9 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -27,7 +27,7 @@ import com.google.protobuf.Timestamp; import org.spine3.protobuf.AnyPacker; import org.spine3.server.BoundedContext; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.Storage; import org.spine3.server.storage.StorageFactory; @@ -57,7 +57,7 @@ public EntityRepository(BoundedContext boundedContext) { /** {@inheritDoc} */ @Override protected Storage createStorage(StorageFactory factory) { - final Storage result = factory.createEntityStorage(getEntityClass()); + final Storage result = factory.createRecordStorage(getEntityClass()); return result; } @@ -68,16 +68,16 @@ protected Storage createStorage(StorageFactory factory) { * @throws IllegalStateException if the storage is null */ @Nonnull - protected EntityStorage entityStorage() { + protected RecordStorage recordStorage() { @SuppressWarnings("unchecked") // It is safe to cast as we control the creation in createStorage(). - final EntityStorage storage = (EntityStorage) getStorage(); + final RecordStorage storage = (RecordStorage) getStorage(); return checkStorage(storage); } /** {@inheritDoc} */ @Override public void store(E entity) { - final EntityStorage storage = entityStorage(); + final RecordStorage storage = recordStorage(); final EntityStorageRecord record = toEntityRecord(entity); storage.write(entity.getId(), record); } @@ -86,7 +86,7 @@ public void store(E entity) { @Nullable @Override public E load(I id) { - final EntityStorage storage = entityStorage(); + final RecordStorage storage = recordStorage(); final EntityStorageRecord record = storage.read(id); if (isDefault(record)) { return null; @@ -136,7 +136,7 @@ public E find(I id) { */ @CheckReturnValue public ImmutableCollection findBulk(Iterable ids) { - final EntityStorage storage = entityStorage(); + final RecordStorage storage = recordStorage(); final Iterable entityStorageRecords = storage.readBulk(ids); final Iterator idIterator = ids.iterator(); diff --git a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java index 74e7d86e36d..4abe10c63f4 100644 --- a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java +++ b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java @@ -38,14 +38,13 @@ import org.spine3.server.event.EventStore; import org.spine3.server.event.EventStreamQuery; import org.spine3.server.stand.StandFunnel; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.ProjectionStorage; import org.spine3.server.storage.Storage; import org.spine3.server.storage.StorageFactory; import org.spine3.server.type.EventClass; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Set; /** @@ -86,7 +85,7 @@ protected enum Status { private Status status = Status.CREATED; /** An underlying entity storage used to store projections. */ - private EntityStorage entityStorage; + private RecordStorage recordStorage; /** An instance of {@link StandFunnel} to be informed about state updates */ private final StandFunnel standFunnel; @@ -114,7 +113,7 @@ protected boolean isOnline() { protected Storage createStorage(StorageFactory factory) { final Class

projectionClass = getEntityClass(); final ProjectionStorage projectionStorage = factory.createProjectionStorage(projectionClass); - this.entityStorage = projectionStorage.getEntityStorage(); + this.recordStorage = projectionStorage.getRecordStorage(); return projectionStorage; } @@ -141,8 +140,8 @@ public void close() throws Exception { @Override @Nonnull @SuppressWarnings("RefusedBequest") - protected EntityStorage entityStorage() { - return checkStorage(entityStorage); + protected RecordStorage recordStorage() { + return checkStorage(recordStorage); } /** diff --git a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java index 3529fd9114b..70403d1d87c 100644 --- a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java @@ -36,7 +36,7 @@ * @author Alexander Litus */ @SPI -public abstract class ProjectionStorage extends EntityStorage { +public abstract class ProjectionStorage extends RecordStorage { public ProjectionStorage(boolean multitenant) { super(multitenant); @@ -45,14 +45,14 @@ public ProjectionStorage(boolean multitenant) { @Nullable @Override protected EntityStorageRecord readInternal(I id) { - final EntityStorage storage = getEntityStorage(); + final RecordStorage storage = getRecordStorage(); final EntityStorageRecord record = storage.read(id); return record; } @Override protected void writeInternal(I id, EntityStorageRecord record) { - final EntityStorage storage = getEntityStorage(); + final RecordStorage storage = getRecordStorage(); storage.write(id, record); } @@ -72,5 +72,5 @@ protected void writeInternal(I id, EntityStorageRecord record) { public abstract Timestamp readLastHandledEventTime(); /** Returns an entity storage implementation. */ - public abstract EntityStorage getEntityStorage(); + public abstract RecordStorage getRecordStorage(); } diff --git a/server/src/main/java/org/spine3/server/storage/EntityStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java similarity index 93% rename from server/src/main/java/org/spine3/server/storage/EntityStorage.java rename to server/src/main/java/org/spine3/server/storage/RecordStorage.java index eb2fd3ed350..bdc9ead68bb 100644 --- a/server/src/main/java/org/spine3/server/storage/EntityStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -29,16 +29,16 @@ import static com.google.common.base.Preconditions.checkNotNull; /** - * An entity storage keeps messages with identity. + * A storage keeping messages with identity. * * @param the type of entity IDs * @author Alexander Yevsyukov * @see Entity */ @SPI -public abstract class EntityStorage extends AbstractStorage { +public abstract class RecordStorage extends AbstractStorage { - protected EntityStorage(boolean multitenant) { + protected RecordStorage(boolean multitenant) { super(multitenant); } @@ -104,6 +104,6 @@ public Iterable readBulk(Iterable ids) { */ protected abstract void writeInternal(I id, EntityStorageRecord record); - /** @see org.spine3.server.storage.EntityStorage#readBulk(java.lang.Iterable) */ + /** @see RecordStorage#readBulk(java.lang.Iterable) */ protected abstract Iterable readBulkInternal(Iterable ids); } diff --git a/server/src/main/java/org/spine3/server/storage/StorageFactory.java b/server/src/main/java/org/spine3/server/storage/StorageFactory.java index 696a19bd4e6..21297682052 100644 --- a/server/src/main/java/org/spine3/server/storage/StorageFactory.java +++ b/server/src/main/java/org/spine3/server/storage/StorageFactory.java @@ -58,12 +58,12 @@ public interface StorageFactory extends AutoCloseable { AggregateStorage createAggregateStorage(Class> aggregateClass); /** - * Creates a new {@link EntityStorage} instance. + * Creates a new {@link RecordStorage} instance. * * @param the type of entity IDs * @param entityClass the class of entities to store */ - EntityStorage createEntityStorage(Class> entityClass); + RecordStorage createRecordStorage(Class> entityClass); /** * Creates a new {@link ProjectionStorage} instance. diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java index 51304c59050..4231823e349 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java @@ -21,7 +21,7 @@ package org.spine3.server.storage.memory; import com.google.protobuf.Timestamp; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.ProjectionStorage; @@ -35,18 +35,18 @@ */ /* package */ class InMemoryProjectionStorage extends ProjectionStorage { - private final InMemoryEntityStorage entityStorage; + private final InMemoryRecordStorage recordStorage; /** The time of the last handled event. */ private Timestamp timestampOfLastEvent; - public static InMemoryProjectionStorage newInstance(InMemoryEntityStorage entityStorage, boolean multitenant) { + public static InMemoryProjectionStorage newInstance(InMemoryRecordStorage entityStorage, boolean multitenant) { return new InMemoryProjectionStorage<>(entityStorage, multitenant); } - private InMemoryProjectionStorage(InMemoryEntityStorage entityStorage, boolean multitenant) { + private InMemoryProjectionStorage(InMemoryRecordStorage recordStorage, boolean multitenant) { super(multitenant); - this.entityStorage = entityStorage; + this.recordStorage = recordStorage; } @Override @@ -61,19 +61,19 @@ public Timestamp readLastHandledEventTime() { } @Override - public EntityStorage getEntityStorage() { - return entityStorage; + public RecordStorage getRecordStorage() { + return recordStorage; } @Override public void close() throws Exception { - entityStorage.close(); + recordStorage.close(); super.close(); } @Override protected Iterable readBulkInternal(Iterable ids) { - final Iterable result = entityStorage.readBulk(ids); + final Iterable result = recordStorage.readBulk(ids); return result; } } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryEntityStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java similarity index 90% rename from server/src/main/java/org/spine3/server/storage/memory/InMemoryEntityStorage.java rename to server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 3e9f88ccea6..325ef5be08a 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryEntityStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -20,7 +20,7 @@ package org.spine3.server.storage.memory; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.users.CurrentTenant; import org.spine3.users.TenantId; @@ -33,18 +33,18 @@ import static com.google.common.collect.Maps.newHashMap; /** - * Memory-based implementation of {@link EntityStorage}. + * Memory-based implementation of {@link RecordStorage}. * * @author Alexander Litus */ -/* package */ class InMemoryEntityStorage extends EntityStorage { +/* package */ class InMemoryRecordStorage extends RecordStorage { /** A stub instance of {@code TenantId} to be used by the storage in single-tenant context. */ private static final TenantId singleTenant = TenantId.newBuilder().setValue("SINGLE_TENANT").build(); private final Map> tenantToStorageMap = newHashMap(); - protected InMemoryEntityStorage(boolean multitenant) { + protected InMemoryRecordStorage(boolean multitenant) { super(multitenant); } @@ -68,8 +68,8 @@ protected Iterable readBulkInternal(final Iterable given return result; } - protected static InMemoryEntityStorage newInstance(boolean multitenant) { - return new InMemoryEntityStorage<>(multitenant); + protected static InMemoryRecordStorage newInstance(boolean multitenant) { + return new InMemoryRecordStorage<>(multitenant); } private Map getStorage() { diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java index 003e5a018bb..98e14aa2321 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStorageFactory.java @@ -24,7 +24,7 @@ import org.spine3.server.entity.Entity; import org.spine3.server.storage.AggregateStorage; import org.spine3.server.storage.CommandStorage; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.EventStorage; import org.spine3.server.storage.ProjectionStorage; import org.spine3.server.storage.StandStorage; @@ -78,14 +78,14 @@ public AggregateStorage createAggregateStorage(Class EntityStorage createEntityStorage(Class> unused) { - return InMemoryEntityStorage.newInstance(isMultitenant()); + public RecordStorage createRecordStorage(Class> unused) { + return InMemoryRecordStorage.newInstance(isMultitenant()); } @Override public ProjectionStorage createProjectionStorage(Class> unused) { final boolean multitenant = isMultitenant(); - final InMemoryEntityStorage entityStorage = InMemoryEntityStorage.newInstance(multitenant); + final InMemoryRecordStorage entityStorage = InMemoryRecordStorage.newInstance(multitenant); return InMemoryProjectionStorage.newInstance(entityStorage, multitenant); } diff --git a/server/src/main/proto/spine/server/storage/entity_storage.proto b/server/src/main/proto/spine/server/storage/record_storage.proto similarity index 94% rename from server/src/main/proto/spine/server/storage/entity_storage.proto rename to server/src/main/proto/spine/server/storage/record_storage.proto index 58602225be3..c463d5534e1 100644 --- a/server/src/main/proto/spine/server/storage/entity_storage.proto +++ b/server/src/main/proto/spine/server/storage/record_storage.proto @@ -25,7 +25,7 @@ option (type_url_prefix) = "type.spine3.org"; option (SPI_all) = true; option java_generate_equals_and_hash = true; option java_multiple_files = true; -option java_outer_classname = "EntityStorageProto"; +option java_outer_classname = "RecordStorageProto"; option java_package = "org.spine3.server.storage"; import "google/protobuf/timestamp.proto"; @@ -33,7 +33,7 @@ import "google/protobuf/any.proto"; import "spine/annotations.proto"; -// Used to store entities in the entity storage. +// Used to store entities in the record storage. message EntityStorageRecord { // The timestamp of the last entity modification. google.protobuf.Timestamp when_modified = 1; diff --git a/server/src/test/java/org/spine3/server/entity/RepositoryShould.java b/server/src/test/java/org/spine3/server/entity/RepositoryShould.java index 8e8d591d4ed..3bd3661c0d8 100644 --- a/server/src/test/java/org/spine3/server/entity/RepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/RepositoryShould.java @@ -23,7 +23,7 @@ import org.junit.Before; import org.junit.Test; import org.spine3.server.BoundedContext; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.Storage; import org.spine3.server.storage.StorageFactory; import org.spine3.server.storage.memory.InMemoryStorageFactory; @@ -195,7 +195,7 @@ protected ProjectEntity load(ProjectId id) { @Override protected Storage createStorage(StorageFactory factory) { - return factory.createEntityStorage(getEntityClass()); + return factory.createRecordStorage(getEntityClass()); } } @@ -227,7 +227,7 @@ public void allow_initializing_storage_only_once() { public void close_storage_on_close() throws Exception { repository.initStorage(storageFactory); - final EntityStorage storage = (EntityStorage) repository.getStorage(); + final RecordStorage storage = (RecordStorage) repository.getStorage(); repository.close(); //noinspection ConstantConditions diff --git a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java index e98f67dc3b6..5cdf02044dc 100644 --- a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java @@ -32,7 +32,7 @@ import org.spine3.server.BoundedContext; import org.spine3.server.event.EventStore; import org.spine3.server.event.Subscribe; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.server.type.EventClass; import org.spine3.test.projection.Project; @@ -145,8 +145,8 @@ public void return_id_from_event_message() { @Test public void return_entity_storage() { - final EntityStorage entityStorage = repository.entityStorage(); - assertNotNull(entityStorage); + final RecordStorage recordStorage = repository.recordStorage(); + assertNotNull(recordStorage); } @Test diff --git a/server/src/test/java/org/spine3/server/storage/EntityStorageShould.java b/server/src/test/java/org/spine3/server/storage/EntityStorageShould.java index 4201c8f71fc..6d3225d4e4a 100644 --- a/server/src/test/java/org/spine3/server/storage/EntityStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/EntityStorageShould.java @@ -39,7 +39,7 @@ public abstract class EntityStorageShould extends AbstractStorageShould { @Override - protected abstract EntityStorage getStorage(); + protected abstract RecordStorage getStorage(); /** * Used to get a storage in tests with different ID types. @@ -49,7 +49,7 @@ public abstract class EntityStorageShould extends AbstractStorageShould the type of Entity IDs * @return an empty storage instance */ - protected abstract EntityStorage getStorage(Class> entityClass); + protected abstract RecordStorage getStorage(Class> entityClass); @Override protected EntityStorageRecord newStorageRecord() { @@ -58,33 +58,33 @@ protected EntityStorageRecord newStorageRecord() { @Test public void write_and_read_record_by_Message_id() { - final EntityStorage storage = getStorage(TestEntityWithIdMessage.class); + final RecordStorage storage = getStorage(TestEntityWithIdMessage.class); final ProjectId id = Given.AggregateId.newProjectId(newUuid()); writeAndReadRecordTest(id, storage); } @Test public void write_and_read_record_by_String_id() { - final EntityStorage storage = getStorage(TestEntityWithIdString.class); + final RecordStorage storage = getStorage(TestEntityWithIdString.class); final String id = newUuid(); writeAndReadRecordTest(id, storage); } @Test public void write_and_read_record_by_Long_id() { - final EntityStorage storage = getStorage(TestEntityWithIdLong.class); + final RecordStorage storage = getStorage(TestEntityWithIdLong.class); final long id = 10L; writeAndReadRecordTest(id, storage); } @Test public void write_and_read_record_by_Integer_id() { - final EntityStorage storage = getStorage(TestEntityWithIdInteger.class); + final RecordStorage storage = getStorage(TestEntityWithIdInteger.class); final int id = 10; writeAndReadRecordTest(id, storage); } - protected void writeAndReadRecordTest(Id id, EntityStorage storage) { + protected void writeAndReadRecordTest(Id id, RecordStorage storage) { final EntityStorageRecord expected = newStorageRecord(); storage.write(id, expected); diff --git a/server/src/test/java/org/spine3/server/storage/memory/InMemoryEntityStorageShould.java b/server/src/test/java/org/spine3/server/storage/memory/InMemoryEntityStorageShould.java index b2548297724..94a542ed1b7 100644 --- a/server/src/test/java/org/spine3/server/storage/memory/InMemoryEntityStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/memory/InMemoryEntityStorageShould.java @@ -21,26 +21,26 @@ package org.spine3.server.storage.memory; import org.spine3.server.entity.Entity; -import org.spine3.server.storage.EntityStorage; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.EntityStorageShould; import static org.spine3.base.Identifiers.newUuid; /** - * Tests of an in-memory {@link EntityStorage} implementation. + * Tests of an in-memory {@link RecordStorage} implementation. * * @author Alexander Litus */ public class InMemoryEntityStorageShould extends EntityStorageShould { @Override - protected EntityStorage getStorage() { - return InMemoryEntityStorage.newInstance(false); + protected RecordStorage getStorage() { + return InMemoryRecordStorage.newInstance(false); } @Override - protected EntityStorage getStorage(Class> entityClass) { - return InMemoryEntityStorage.newInstance(false); + protected RecordStorage getStorage(Class> entityClass) { + return InMemoryRecordStorage.newInstance(false); } @Override diff --git a/server/src/test/java/org/spine3/server/storage/memory/InMemoryProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/memory/InMemoryProjectionStorageShould.java index 7d740fd652b..82e06cdc657 100644 --- a/server/src/test/java/org/spine3/server/storage/memory/InMemoryProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/memory/InMemoryProjectionStorageShould.java @@ -32,8 +32,8 @@ public class InMemoryProjectionStorageShould extends ProjectionStorageShould getStorage() { - final InMemoryEntityStorage entityStorage = InMemoryEntityStorage.newInstance(false); - final InMemoryProjectionStorage storage = InMemoryProjectionStorage.newInstance(entityStorage, false); + final InMemoryRecordStorage recordStorage = InMemoryRecordStorage.newInstance(false); + final InMemoryProjectionStorage storage = InMemoryProjectionStorage.newInstance(recordStorage, false); return storage; } From 384fe537228f049b51c7f6e3a1b1dc1b9b353a69 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 31 Aug 2016 19:12:55 +0300 Subject: [PATCH 028/361] Create an initial implementation of QueryService (simple queries) and prepare unit test infrastructure. --- .../org/spine3/server/BoundedContext.java | 6 + .../java/org/spine3/server/QueryService.java | 141 +++++++++++++ .../server/entity/EntityRepository.java | 78 +++++++- .../java/org/spine3/server/stand/Stand.java | 80 ++++++-- .../server/storage/ProjectionStorage.java | 1 + .../spine3/server/storage/RecordStorage.java | 26 ++- .../memory/InMemoryProjectionStorage.java | 8 + .../storage/memory/InMemoryRecordStorage.java | 13 +- .../spine3/server/ClientServiceShould.java | 101 +--------- .../test/java/org/spine3/server/Given.java | 108 ++++++++++ .../org/spine3/server/QueryServiceShould.java | 186 ++++++++++++++++++ .../testdata/TestBoundedContextFactory.java | 8 + .../org/spine3/testdata/TestStandFactory.java | 41 ++++ 13 files changed, 678 insertions(+), 119 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/QueryService.java create mode 100644 server/src/test/java/org/spine3/server/QueryServiceShould.java create mode 100644 server/src/test/java/org/spine3/testdata/TestStandFactory.java diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index 49c46736c88..037ce3db121 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -242,6 +242,12 @@ public StandFunnel getStandFunnel() { return this.standFunnel; } + /** Obtains instance of {@link Stand} of this {@code BoundedContext}. */ + @CheckReturnValue + public Stand getStand() { + return stand; + } + /** * A builder for producing {@code BoundedContext} instances. * diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java new file mode 100644 index 00000000000..a0e788a0f36 --- /dev/null +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -0,0 +1,141 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import io.grpc.stub.StreamObserver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spine3.client.Query; +import org.spine3.client.QueryResponse; +import org.spine3.client.grpc.QueryServiceGrpc; +import org.spine3.protobuf.KnownTypes; +import org.spine3.protobuf.TypeUrl; +import org.spine3.type.ClassName; + +import java.util.Set; + +/** + * The {@code QueryService} provides a synchronous way to fetch read-side state from the server. + * + *

For asynchronous read-side updates please see {@code SubscriptionService}. + * + * @author Alex Tymchenko + */ +public class QueryService + extends QueryServiceGrpc.QueryServiceImplBase { + + private final ImmutableMap typeToContextMap; + + private QueryService(Builder builder) { + this.typeToContextMap = builder.getBoundedContextMap(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. + @Override + public void read(Query request, StreamObserver responseObserver) { + final String typeAsString = request.getTarget() + .getType(); + + // TODO[alex.tymchenko]: too complex + final ClassName typeClassName = ClassName.of(typeAsString); + final TypeUrl type = KnownTypes.getTypeUrl(typeClassName); + final BoundedContext boundedContext = typeToContextMap.get(type); + + try { + boundedContext.getStand() + .execute(request, responseObserver); + + } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { + log().error("Error processing query", e); + responseObserver.onError(e); + } + } + + public static class Builder { + private final Set boundedContexts = Sets.newHashSet(); + private ImmutableMap typeToContextMap; + + + public Builder addBoundedContext(BoundedContext boundedContext) { + // Save it to a temporary set so that it is easy to remove it if needed. + boundedContexts.add(boundedContext); + return this; + } + + public Builder removeBoundedContext(BoundedContext boundedContext) { + boundedContexts.remove(boundedContext); + return this; + } + + + @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection returned is immutable + public ImmutableMap getBoundedContextMap() { + return typeToContextMap; + } + + /** + * Builds the {@link QueryService}. + */ + public QueryService build() { + this.typeToContextMap = createBoundedContextMap(); + final QueryService result = new QueryService(this); + return result; + } + + private ImmutableMap createBoundedContextMap() { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (BoundedContext boundedContext : boundedContexts) { + addBoundedContext(builder, boundedContext); + } + return builder.build(); + } + + private static void addBoundedContext(ImmutableMap.Builder mapBuilder, + BoundedContext boundedContext) { + + final ImmutableSet availableTypes = boundedContext.getStand() + .getAvailableTypes(); + + for (TypeUrl availableType : availableTypes) { + mapBuilder.put(availableType, boundedContext); + } + } + } + + private static Logger log() { + return LogSingleton.INSTANCE.value; + } + + private enum LogSingleton { + INSTANCE; + @SuppressWarnings("NonSerializableFieldInSerializableClass") + private final Logger value = LoggerFactory.getLogger(QueryService.class); + } + +} diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 10f281eb6a9..36b39ca4229 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -20,24 +20,35 @@ package org.spine3.server.entity; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.protobuf.Any; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; -import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.Storage; import org.spine3.server.storage.StorageFactory; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Collection; import java.util.Iterator; +import java.util.List; +import java.util.Map; import static org.spine3.protobuf.AnyPacker.unpack; +import static org.spine3.protobuf.Messages.toMessageClass; import static org.spine3.validate.Validate.isDefault; /** @@ -152,7 +163,72 @@ public ImmutableCollection findBulk(Iterable ids) { final ImmutableList result = builder.build(); return result; + } + + @CheckReturnValue + public ImmutableCollection findAll() { + final RecordStorage storage = recordStorage(); + final Map recordMap = storage.readAll(); + + final ImmutableCollection entities = + FluentIterable.from(recordMap.entrySet()) + .transform(new Function, E>() { + + @Nullable + @Override + public E apply(@Nullable Map.Entry input) { + Preconditions.checkNotNull(input); + return toEntity(input.getKey(), input.getValue()); + } + }) + .toList(); + + return entities; + } + + /** + * Find all the entities passing the given filters. + * + *

At this point only {@link org.spine3.client.EntityIdFilter} is supported. All other filters are ignored. + * + *

NOTE: The storage must be assigned before calling this method. + * + * @param filters entity filters + * @return all the entities in this repository passed the filters. + */ + @CheckReturnValue + public ImmutableCollection findAll(EntityFilters filters) { + final List idsList = filters.getIdFilter() + .getIdsList(); + final Class expectedIdClass = getIdClass(); + + final Collection domainIds = Collections2.transform(idsList, new Function() { + @Nullable + @Override + public I apply(@Nullable EntityId input) { + Preconditions.checkNotNull(input); + final Any idAsAny = input.getId(); + + final TypeUrl typeUrl = TypeUrl.ofEnclosed(idAsAny); + final Class messageClass = toMessageClass(typeUrl); + + if (!expectedIdClass.equals(messageClass)) { + throw new IllegalArgumentException("Unexpected ID of type " + messageClass + " encountered. " + + "Expected: " + expectedIdClass); + } + final Message idAsMessage = AnyPacker.unpack(idAsAny); + + // As the message class is the same as expected, the conversion is safe. + @SuppressWarnings("unchecked") + final I id = (I) idAsMessage; + return id; + + } + }); + + final ImmutableCollection result = findBulk(domainIds); + return result; } private E toEntity(I id, EntityStorageRecord record) { diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 497aa41dc2e..7872d7ca378 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -26,17 +26,26 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import com.google.protobuf.Message; +import io.grpc.stub.StreamObserver; +import org.spine3.base.Responses; +import org.spine3.client.EntityFilters; +import org.spine3.client.Query; import org.spine3.client.QueryOrBuilder; +import org.spine3.client.QueryResponse; import org.spine3.client.Target; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.entity.Entity; +import org.spine3.server.entity.EntityRepository; import org.spine3.server.entity.Repository; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.memory.InMemoryStandStorage; +import org.spine3.type.ClassName; import javax.annotation.CheckReturnValue; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -79,7 +88,7 @@ public class Stand { private final Executor callbackExecutor; /** The mapping between {@code TypeUrl} instances and repositories providing the entities of this type */ - private final ConcurrentMap typeToRepositoryMap = new ConcurrentHashMap<>(); + private final ConcurrentMap typeToRepositoryMap = new ConcurrentHashMap<>(); /** * Store the known {@link org.spine3.server.aggregate.Aggregate} types in order to distinguish them among all @@ -195,25 +204,60 @@ public ImmutableSet getAvailableTypes() { return result; } - @CheckReturnValue - public ImmutableCollection read(QueryOrBuilder query) { - final Collection result = new HashSet<>(); + /** + * Read a particular set of items from the read-side of the application and feed the result into an instance + * + *

{@link Query} defines the query target and the expected detail level for response. + * + *

The query results are fed to an instance of {@link StreamObserver}. + * + * @param query an instance of query + * @param responseObserver an observer to feed the query results to. + */ + public void execute(Query query, StreamObserver responseObserver) { + final ImmutableCollection readResult = internalExecute(query); + final QueryResponse response = QueryResponse.newBuilder() + .addAllMessages(readResult) + .setResponse(Responses.ok()) + .build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + private ImmutableCollection internalExecute(QueryOrBuilder query) { + + final ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); final Target target = query.getTarget(); + final String type = target.getType(); - - final TypeUrl typeUrl = TypeUrl.of(type); - final Repository repository = typeToRepositoryMap.get(typeUrl); + final ClassName typeClassName = ClassName.of(type); + final TypeUrl typeUrl = KnownTypes.getTypeUrl(typeClassName); + final EntityRepository repository = typeToRepositoryMap.get(typeUrl); if (repository != null) { - if(target.getIncludeAll()) { - + if (target.getIncludeAll()) { + final ImmutableCollection all = repository.findAll(); + feedToBuilder(resultBuilder, all); + } else { + final EntityFilters filters = target.getFilters(); + final ImmutableCollection bulkResults = repository.findAll(filters); + feedToBuilder(resultBuilder, bulkResults); } } - final ImmutableCollection immutableResult = new ImmutableSet.Builder().addAll(result) - .build(); - return immutableResult; + final ImmutableSet result = resultBuilder.build(); + + return result; + } + + private static void feedToBuilder(ImmutableSet.Builder resultBuilder, ImmutableCollection all) { + for (Object rawEntity : all) { + final Entity entity = (Entity) rawEntity; + final Message state = entity.getState(); + final Any packedState = AnyPacker.pack(state); + resultBuilder.add(packedState); + } } @@ -230,13 +274,17 @@ public ImmutableCollection read(QueryOrBuilder query) { * * @see #update(Any) */ + @SuppressWarnings("ChainOfInstanceofChecks") public > void registerTypeSupplier(Repository repository) { final TypeUrl entityType = repository.getEntityStateType(); - if (!(repository instanceof AggregateRepository)) { - typeToRepositoryMap.put(entityType, repository); - } else { + + if (repository instanceof EntityRepository) { + typeToRepositoryMap.put(entityType, (EntityRepository) repository); + } + if (repository instanceof AggregateRepository) { knownAggregateTypes.add(entityType); } + } // TODO[alex.tymchenko]: perhaps, we need to close Stand instead of doing this upon repository shutdown (see usages). diff --git a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java index 70403d1d87c..5eb928e80b8 100644 --- a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java @@ -50,6 +50,7 @@ protected EntityStorageRecord readInternal(I id) { return record; } + @Override protected void writeInternal(I id, EntityStorageRecord record) { final RecordStorage storage = getRecordStorage(); diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index bdc9ead68bb..9ceca68bbc6 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -24,6 +24,7 @@ import org.spine3.server.entity.Entity; import javax.annotation.Nullable; +import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -81,6 +82,22 @@ public Iterable readBulk(Iterable ids) { return readBulkInternal(ids); } + /** + * Reads all the records from the storage. + * + *

Each record is returned as a {@link Map} of record ID to the instance of {@link EntityStorageRecord}. + * Such an approach enables turning the records into entities for callees. + * + * @return the {@code Map} containing the ID -> record entries. + * @throws IllegalStateException if the storage was closed before + */ + public Map readAll() { + checkNotClosed(); + + return readAllInternal(); + } + + // // Internal storage methods //--------------------------- @@ -94,6 +111,13 @@ public Iterable readBulk(Iterable ids) { @Nullable protected abstract EntityStorageRecord readInternal(I id); + /** @see RecordStorage#readBulk(java.lang.Iterable) */ + protected abstract Iterable readBulkInternal(Iterable ids); + + + /** @see RecordStorage#readAll() */ + protected abstract Map readAllInternal(); + /** * Writes a record into the storage. * @@ -104,6 +128,4 @@ public Iterable readBulk(Iterable ids) { */ protected abstract void writeInternal(I id, EntityStorageRecord record); - /** @see RecordStorage#readBulk(java.lang.Iterable) */ - protected abstract Iterable readBulkInternal(Iterable ids); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java index 4231823e349..598c4d9fd1d 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java @@ -25,6 +25,8 @@ import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.ProjectionStorage; +import java.util.Map; + import static com.google.common.base.Preconditions.checkNotNull; /** @@ -76,4 +78,10 @@ protected Iterable readBulkInternal(Iterable ids) { final Iterable result = recordStorage.readBulk(ids); return result; } + + @Override + protected Map readAllInternal() { + final Map result = recordStorage.readAll(); + return result; + } } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 325ef5be08a..9566b075f0e 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -20,8 +20,10 @@ package org.spine3.server.storage.memory; -import org.spine3.server.storage.RecordStorage; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.storage.RecordStorage; import org.spine3.server.users.CurrentTenant; import org.spine3.users.TenantId; @@ -68,6 +70,15 @@ protected Iterable readBulkInternal(final Iterable given return result; } + @Override + protected Map readAllInternal() { + final Map storage = getStorage(); + + final ImmutableMap result = ImmutableMap.copyOf(storage); + return result; + } + + protected static InMemoryRecordStorage newInstance(boolean multitenant) { return new InMemoryRecordStorage<>(multitenant); } diff --git a/server/src/test/java/org/spine3/server/ClientServiceShould.java b/server/src/test/java/org/spine3/server/ClientServiceShould.java index 8e45e4cf72c..6dbda08931c 100644 --- a/server/src/test/java/org/spine3/server/ClientServiceShould.java +++ b/server/src/test/java/org/spine3/server/ClientServiceShould.java @@ -28,36 +28,17 @@ import org.junit.Before; import org.junit.Test; import org.spine3.base.Command; -import org.spine3.base.CommandContext; import org.spine3.base.Commands; import org.spine3.base.Response; import org.spine3.base.Responses; -import org.spine3.server.aggregate.Aggregate; -import org.spine3.server.aggregate.AggregateRepository; -import org.spine3.server.aggregate.Apply; -import org.spine3.server.command.Assign; import org.spine3.server.command.CommandBus; import org.spine3.server.command.error.UnsupportedCommandException; import org.spine3.server.transport.GrpcContainer; -import org.spine3.test.clientservice.Project; -import org.spine3.test.clientservice.ProjectId; -import org.spine3.test.clientservice.command.AddTask; -import org.spine3.test.clientservice.command.CreateProject; -import org.spine3.test.clientservice.command.StartProject; -import org.spine3.test.clientservice.customer.Customer; -import org.spine3.test.clientservice.customer.CustomerId; -import org.spine3.test.clientservice.customer.command.CreateCustomer; -import org.spine3.test.clientservice.customer.event.CustomerCreated; -import org.spine3.test.clientservice.event.ProjectCreated; -import org.spine3.test.clientservice.event.ProjectStarted; -import org.spine3.test.clientservice.event.TaskAdded; import org.spine3.testdata.TestCommandBusFactory; import java.io.IOException; -import java.util.List; import java.util.Set; -import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -82,13 +63,13 @@ public class ClientServiceShould { public void setUp() { // Create Projects Bounded Context with one repository. projectsContext = newBoundedContext(spy(TestCommandBusFactory.create())); - final ProjectAggregateRepository projectRepo = new ProjectAggregateRepository(projectsContext); + final Given.ProjectAggregateRepository projectRepo = new Given.ProjectAggregateRepository(projectsContext); projectsContext.register(projectRepo); boundedContexts.add(projectsContext); // Create Customers Bounded Context with one repository. customersContext = newBoundedContext(spy(TestCommandBusFactory.create())); - final CustomerAggregateRepository customerRepo = new CustomerAggregateRepository(customersContext); + final Given.CustomerAggregateRepository customerRepo = new Given.CustomerAggregateRepository(customersContext); customersContext.register(customerRepo); boundedContexts.add(customersContext); @@ -159,84 +140,6 @@ public void deploy_to_grpc_container() throws IOException { * Stub repositories and aggregates ***************************************************/ - private static class ProjectAggregateRepository extends AggregateRepository { - private ProjectAggregateRepository(BoundedContext boundedContext) { - super(boundedContext); - } - } - - private static class ProjectAggregate extends Aggregate { - // an aggregate constructor must be public because it is used via reflection - @SuppressWarnings("PublicConstructorInNonPublicClass") - public ProjectAggregate(ProjectId id) { - super(id); - } - - @Assign - public ProjectCreated handle(CreateProject cmd, CommandContext ctx) { - return Given.EventMessage.projectCreated(cmd.getProjectId()); - } - - @Assign - public TaskAdded handle(AddTask cmd, CommandContext ctx) { - return Given.EventMessage.taskAdded(cmd.getProjectId()); - } - - @Assign - public List handle(StartProject cmd, CommandContext ctx) { - final ProjectStarted message = Given.EventMessage.projectStarted(cmd.getProjectId()); - return newArrayList(message); - } - - @Apply - private void event(ProjectCreated event) { - getBuilder() - .setId(event.getProjectId()) - .setStatus(Project.Status.CREATED) - .build(); - } - - @Apply - private void event(TaskAdded event) { - } - - @Apply - private void event(ProjectStarted event) { - getBuilder() - .setId(event.getProjectId()) - .setStatus(Project.Status.STARTED) - .build(); - } - } - - private static class CustomerAggregateRepository extends AggregateRepository { - private CustomerAggregateRepository(BoundedContext boundedContext) { - super(boundedContext); - } - } - - private static class CustomerAggregate extends Aggregate { - - @SuppressWarnings("PublicConstructorInNonPublicClass") // by convention (as it's used by Reflection). - public CustomerAggregate(CustomerId id) { - super(id); - } - - @Assign - public CustomerCreated handle(CreateCustomer cmd, CommandContext ctx) { - final CustomerCreated event = CustomerCreated.newBuilder() - .setCustomerId(cmd.getCustomerId()) - .setCustomer(cmd.getCustomer()) - .build(); - return event; - } - - @Apply - private void event(CustomerCreated event) { - incrementState(event.getCustomer()); - } - } - private static class TestResponseObserver implements StreamObserver { private Response responseHandled; diff --git a/server/src/test/java/org/spine3/server/Given.java b/server/src/test/java/org/spine3/server/Given.java index 2ff5c67e73c..672f19e4c3d 100644 --- a/server/src/test/java/org/spine3/server/Given.java +++ b/server/src/test/java/org/spine3/server/Given.java @@ -25,12 +25,21 @@ import org.spine3.base.CommandContext; import org.spine3.base.Commands; import org.spine3.base.Identifiers; +import org.spine3.client.Target; import org.spine3.people.PersonName; +import org.spine3.server.aggregate.Aggregate; +import org.spine3.server.aggregate.AggregateRepository; +import org.spine3.server.aggregate.Apply; +import org.spine3.server.command.Assign; +import org.spine3.test.clientservice.Project; import org.spine3.test.clientservice.ProjectId; +import org.spine3.test.clientservice.command.AddTask; import org.spine3.test.clientservice.command.CreateProject; +import org.spine3.test.clientservice.command.StartProject; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.clientservice.customer.command.CreateCustomer; +import org.spine3.test.clientservice.customer.event.CustomerCreated; import org.spine3.test.clientservice.event.ProjectCreated; import org.spine3.test.clientservice.event.ProjectStarted; import org.spine3.test.clientservice.event.TaskAdded; @@ -38,6 +47,9 @@ import org.spine3.time.LocalDates; import org.spine3.users.UserId; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; import static org.spine3.base.Identifiers.newUuid; import static org.spine3.client.UserUtil.newUserId; import static org.spine3.protobuf.Timestamps.getCurrentTime; @@ -148,4 +160,100 @@ private Command() {} return result; } } + + /* package */ static class Query { + + private Query() {}; + + /* package */ static org.spine3.client.Query readAllProjects() { + + final Target queryTarget = Target.newBuilder() + .setType(org.spine3.test.projection.Project.class.getName()) + .setIncludeAll(true) + .build(); + + final org.spine3.client.Query query = org.spine3.client.Query.newBuilder() + .setTarget(queryTarget) + .build(); + return query; + } + } + + /* package */ static class ProjectAggregateRepository extends AggregateRepository { + /* package */ ProjectAggregateRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + private static class ProjectAggregate extends Aggregate { + // an aggregate constructor must be public because it is used via reflection + @SuppressWarnings("PublicConstructorInNonPublicClass") + public ProjectAggregate(ProjectId id) { + super(id); + } + + @Assign + public ProjectCreated handle(CreateProject cmd, CommandContext ctx) { + return EventMessage.projectCreated(cmd.getProjectId()); + } + + @Assign + public TaskAdded handle(AddTask cmd, CommandContext ctx) { + return EventMessage.taskAdded(cmd.getProjectId()); + } + + @Assign + public List handle(StartProject cmd, CommandContext ctx) { + final ProjectStarted message = EventMessage.projectStarted(cmd.getProjectId()); + return newArrayList(message); + } + + @Apply + private void event(ProjectCreated event) { + getBuilder() + .setId(event.getProjectId()) + .setStatus(Project.Status.CREATED) + .build(); + } + + @Apply + private void event(TaskAdded event) { + } + + @Apply + private void event(ProjectStarted event) { + getBuilder() + .setId(event.getProjectId()) + .setStatus(Project.Status.STARTED) + .build(); + } + } + + /* package */ static class CustomerAggregateRepository extends AggregateRepository { + /* package */ CustomerAggregateRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + private static class CustomerAggregate extends Aggregate { + + @SuppressWarnings("PublicConstructorInNonPublicClass") // by convention (as it's used by Reflection). + public CustomerAggregate(CustomerId id) { + super(id); + } + + @Assign + public CustomerCreated handle(CreateCustomer cmd, CommandContext ctx) { + final CustomerCreated event = CustomerCreated.newBuilder() + .setCustomerId(cmd.getCustomerId()) + .setCustomer(cmd.getCustomer()) + .build(); + return event; + } + + @Apply + private void event(CustomerCreated event) { + incrementState(event.getCustomer()); + } + } } diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java new file mode 100644 index 00000000000..89176d4b7c7 --- /dev/null +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -0,0 +1,186 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server; + +import com.google.common.collect.Sets; +import com.google.protobuf.Any; +import com.google.protobuf.Empty; +import io.grpc.stub.StreamObserver; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.spine3.base.EventContext; +import org.spine3.base.Responses; +import org.spine3.client.Query; +import org.spine3.client.QueryResponse; +import org.spine3.server.event.Subscribe; +import org.spine3.server.projection.Projection; +import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.stand.Stand; +import org.spine3.test.bc.event.ProjectCreated; +import org.spine3.test.clientservice.ProjectId; +import org.spine3.test.projection.Project; +import org.spine3.testdata.TestCommandBusFactory; +import org.spine3.testdata.TestStandFactory; + +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; + + +/** + * @author Alex Tymchenko + */ +public class QueryServiceShould { + + private QueryService service; + + private final Set boundedContexts = Sets.newHashSet(); + + private BoundedContext projectsContext; + private BoundedContext customersContext; + private final TestQueryResponseObserver responseObserver = new TestQueryResponseObserver(); + + @Before + public void setUp() { + // Create Projects Bounded Context with one repository and one projection. + projectsContext = newBoundedContext(spy(TestStandFactory.create())); + + final Given.ProjectAggregateRepository projectRepo = new Given.ProjectAggregateRepository(projectsContext); + projectsContext.register(projectRepo); + final ProjectDetailsRepository projectDetailsRepository = new ProjectDetailsRepository(projectsContext); + projectsContext.register(projectDetailsRepository); + + boundedContexts.add(projectsContext); + + + // Create Customers Bounded Context with one repository. + customersContext = newBoundedContext(spy(TestCommandBusFactory.create())); + final Given.CustomerAggregateRepository customerRepo = new Given.CustomerAggregateRepository(customersContext); + customersContext.register(customerRepo); + boundedContexts.add(customersContext); + + final QueryService.Builder builder = QueryService.newBuilder(); + + for (BoundedContext context : boundedContexts) { + builder.addBoundedContext(context); + } + + + service = spy(builder.build()); + } + + @After + public void tearDown() throws Exception { + for (BoundedContext boundedContext : boundedContexts) { + boundedContext.close(); + } + } + + @Test + public void execute_queries() { + final Query query = Given.Query.readAllProjects(); + final Stand stand = projectsContext.getStand(); + service.read(query, responseObserver); + + final QueryResponse responseHandled = responseObserver.getResponseHandled(); + assertNotNull(responseHandled); + assertEquals(Responses.ok(), responseHandled.getResponse()); + assertTrue(responseObserver.isCompleted()); + assertNull(responseObserver.getThrowable()); + verify(stand).execute(query, responseObserver); + + } + + + /* + * Stub repositories and projections + ***************************************************/ + + private static class ProjectDetailsRepository extends ProjectionRepository { + + protected ProjectDetailsRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + private static class ProjectDetails extends Projection { + + /** + * Creates a new instance. + * + * @param id the ID for the new instance + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public ProjectDetails(ProjectId id) { + super(id); + } + + @SuppressWarnings("UnusedParameters") // OK for test method. + @Subscribe + public void on(ProjectCreated event, EventContext context) { + // Do nothing. + } + + } + + private static class TestQueryResponseObserver implements StreamObserver { + + private QueryResponse responseHandled; + private Throwable throwable; + private boolean isCompleted = false; + + @Override + public void onNext(QueryResponse response) { + this.responseHandled = response; + } + + @Override + public void onError(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public void onCompleted() { + this.isCompleted = true; + } + + /* package */ QueryResponse getResponseHandled() { + return responseHandled; + } + + /* package */ Throwable getThrowable() { + return throwable; + } + + /* package */ boolean isCompleted() { + return isCompleted; + } + } +} diff --git a/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java b/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java index abc18805668..33b21acd2e8 100644 --- a/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java @@ -24,6 +24,7 @@ import org.spine3.server.command.CommandBus; import org.spine3.server.event.EventBus; import org.spine3.server.event.enrich.EventEnricher; +import org.spine3.server.stand.Stand; import org.spine3.server.storage.StorageFactory; import org.spine3.server.storage.memory.InMemoryStorageFactory; @@ -62,6 +63,13 @@ public static BoundedContext newBoundedContext(EventBus eventBus) { return builder.build(); } + public static BoundedContext newBoundedContext(Stand stand) { + final BoundedContext.Builder builder = BoundedContext.newBuilder() + .setStorageFactory(FACTORY) + .setStand(stand); + return builder.build(); + } + public static BoundedContext newBoundedContext(CommandBus commandBus) { final BoundedContext.Builder builder = BoundedContext.newBuilder() .setStorageFactory(FACTORY) diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java new file mode 100644 index 00000000000..d54df4fae6d --- /dev/null +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -0,0 +1,41 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.testdata; + +import org.spine3.server.stand.Stand; +import org.spine3.server.storage.memory.InMemoryStandStorage; + +/** + * Creates stands for tests. + * + * @author Alex Tymchenko + */ +public class TestStandFactory { + + private TestStandFactory() {} + + public static Stand create() { + final Stand stand = Stand.newBuilder() + .build(); + return stand; + } +} From 297a8fc2793d12306e5a2a86364f5a9e55b806cf Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 31 Aug 2016 21:02:38 +0300 Subject: [PATCH 029/361] Allow to distinguish the aggregate states not only by type, but by the IDs as well. --- .../server/aggregate/AggregateRepository.java | 2 +- .../projection/ProjectionRepository.java | 2 +- .../spine3/server/stand/AggregateStateId.java | 87 +++++++++++++++++++ .../java/org/spine3/server/stand/Stand.java | 8 +- .../org/spine3/server/stand/StandFunnel.java | 7 +- .../spine3/server/storage/StandStorage.java | 19 +--- .../storage/memory/InMemoryStandStorage.java | 57 ++++++------ 7 files changed, 130 insertions(+), 52 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/stand/AggregateStateId.java diff --git a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java index 24a9e8ca9bf..7b231f27460 100644 --- a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java +++ b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java @@ -235,7 +235,7 @@ public void dispatch(Command request) throws IllegalStateException { store(aggregate); final Message state = aggregate.getState(); final Any packedAny = AnyPacker.pack(state); - standFunnel.post(packedAny); + standFunnel.post(aggregateId, packedAny); } catch (Exception e) { commandStatusService.setToError(commandId, e); } diff --git a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java index 4abe10c63f4..cba77e49928 100644 --- a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java +++ b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java @@ -243,7 +243,7 @@ public void dispatch(Event event) { store(projection); final M state = projection.getState(); final Any packedState = AnyPacker.pack(state); - standFunnel.post(packedState); + standFunnel.post(id, packedState); final ProjectionStorage storage = projectionStorage(); final Timestamp eventTime = context.getTimestamp(); storage.writeLastHandledEventTime(eventTime); diff --git a/server/src/main/java/org/spine3/server/stand/AggregateStateId.java b/server/src/main/java/org/spine3/server/stand/AggregateStateId.java new file mode 100644 index 00000000000..e3671c74ad0 --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/AggregateStateId.java @@ -0,0 +1,87 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import org.spine3.protobuf.TypeUrl; + +import javax.annotation.CheckReturnValue; + +/** + * An identifier for the state of a certain {@link org.spine3.server.aggregate.Aggregate}. + * + *

{@code Aggregate} state is defined by {@link org.spine3.protobuf.TypeUrl}. + * + *

The {@code AggregateStateId} is used to store and access the latest {@code Aggregate} states in a {@link Stand}. + * + * @param the type for IDs of the source aggregate + * @author Alex Tymchenko + */ +public final class AggregateStateId { + private final I aggregateId; + private final TypeUrl stateType; + + private AggregateStateId(I aggregateId, TypeUrl stateType) { + this.aggregateId = aggregateId; + this.stateType = stateType; + } + + @CheckReturnValue + public static AggregateStateId of(I aggregateId, TypeUrl stateType) { + return new AggregateStateId(aggregateId, stateType); + } + + public I getAggregateId() { + return aggregateId; + } + + public TypeUrl getStateType() { + return stateType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AggregateStateId)) { + return false; + } + AggregateStateId that = (AggregateStateId) o; + return Objects.equal(aggregateId, that.aggregateId) && + Objects.equal(stateType, that.stateType); + } + + @Override + public int hashCode() { + return Objects.hashCode(aggregateId, stateType); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("aggregateId", aggregateId) + .add("stateType", stateType) + .toString(); + } +} diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 7872d7ca378..7e5ee89f377 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -131,15 +131,17 @@ public static Builder newBuilder() { */ @SuppressWarnings("MethodWithMultipleLoops") /* It's fine, since the second loop is most likely * executed in async fashion. */ - public void update(final Any entityState) { + public void update(final Object id, final Any entityState) { final String typeUrlString = entityState.getTypeUrl(); final TypeUrl typeUrl = TypeUrl.of(typeUrlString); final boolean isAggregateUpdate = knownAggregateTypes.contains(typeUrl); if (isAggregateUpdate) { + final AggregateStateId aggregateStateId = AggregateStateId.of(id, typeUrl); + for (StandStorage storage : storages) { - storage.write(entityState); + storage.write(aggregateStateId, entityState); } } @@ -229,7 +231,7 @@ private ImmutableCollection internalExecute(QueryOrBuilder query) { final ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); final Target target = query.getTarget(); - + final String type = target.getType(); final ClassName typeClassName = ClassName.of(type); final TypeUrl typeUrl = KnownTypes.getTypeUrl(typeClassName); diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index fbe708fa6cf..9e3cc659ce0 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -61,15 +61,16 @@ private StandFunnel(Builder builder) { /** * Post the state of an {@link org.spine3.server.entity.Entity} to an instance of {@link Stand}. * - *

The data is posted as {@link Any} to allow transferring over the network. + *

The state data is posted as {@link Any} to allow transferring over the network. * + * @param id the id of an entity * @param entityState the state of an {@code Entity} */ - public void post(final Any entityState) { + public void post(final Object id, final Any entityState) { executor.execute(new Runnable() { @Override public void run() { - stand.update(entityState); + stand.update(id, entityState); } }); diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index 4aff572fe6f..ee325cced5e 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -22,34 +22,21 @@ package org.spine3.server.storage; import com.google.protobuf.Any; -import org.spine3.protobuf.TypeUrl; +import org.spine3.server.stand.AggregateStateId; import org.spine3.server.stand.Stand; /** * Contract for {@link Stand} storage. * - *

Stores the latest {@link org.spine3.server.aggregate.Aggregate} states. The state is passed as {@link Any} proto message - * and stored by its {@link TypeUrl}. - * - *

Not more than one {@code Aggregate} state object is stored for a single {@code TypeUrl}. - * - *

Allows to access the states via {@link TypeUrl} its. + * // TODO[alex.tymchenko]: describe * * @author Alex Tymchenko * @see Any#getTypeUrl() * @see Stand */ -public abstract class StandStorage extends AbstractStorage { +public abstract class StandStorage extends AbstractStorage { protected StandStorage(boolean multitenant) { super(multitenant); } - - /** - * Update the storage with the {@link org.spine3.server.aggregate.Aggregate} state. - * - * @param aggregateState the state of {@code Aggregate} to store. - */ - public abstract void write(Any aggregateState); - } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 7f8b66e45c1..3acf7266c97 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -22,12 +22,13 @@ package org.spine3.server.storage.memory; import com.google.common.collect.MapMaker; -import com.google.common.collect.Sets; +import com.google.common.collect.Maps; import com.google.protobuf.Any; import org.spine3.protobuf.TypeUrl; +import org.spine3.server.stand.AggregateStateId; import org.spine3.server.storage.StandStorage; -import java.util.Set; +import java.util.Map; import java.util.concurrent.ConcurrentMap; import static com.google.common.base.Preconditions.checkNotNull; @@ -42,7 +43,7 @@ */ public class InMemoryStandStorage extends StandStorage { - private final ConcurrentMap aggregateStates; + private final ConcurrentMap aggregateStates; private InMemoryStandStorage(Builder builder) { super(builder.isMultitenant()); @@ -54,28 +55,23 @@ public static Builder newBuilder() { } @Override - public Any read(TypeUrl id) { + public Any read(AggregateStateId id) { final Any result = aggregateStates.get(id); return result; } @Override - public void write(TypeUrl id, Any record) { + public void write(AggregateStateId id, Any record) { final TypeUrl recordType = TypeUrl.of(record.getTypeUrl()); - checkState(id == recordType, "The typeUrl of the record does not correspond to id"); + checkState(id.getStateType() == recordType, "The typeUrl of the record does not correspond to id"); - doPut(aggregateStates, record); - } - - @Override - public void write(Any aggregateState) { - doPut(aggregateStates, aggregateState); + doPut(aggregateStates, id, record); } public static class Builder { - private final Set seedData = Sets.newHashSet(); - private ConcurrentMap seedDataMap; + private final Map seedData = Maps.newHashMap(); + private ConcurrentMap seedDataMap; private boolean multitenant; /** @@ -85,12 +81,15 @@ public static class Builder { * *

The argument value must not be null. * + * @param id the id of state object * @param stateObject the state object to be added * @return {@code this} instance of {@code Builder} */ - public Builder addStateObject(Any stateObject) { + public Builder addStateObject(Object id, Any stateObject) { checkNotNull(stateObject, "stateObject must not be null"); - seedData.add(stateObject); + checkNotNull(id, "id must not be null"); + + seedData.put(id, stateObject); return this; } @@ -99,20 +98,21 @@ public Builder addStateObject(Any stateObject) { * *

The argument value must not be null. * - * @param stateObject the state object to be removed + * @param id the ID of state object to be removed * @return {@code this} instance of {@code Builder} */ - public Builder removeStateObject(Any stateObject) { - checkNotNull(stateObject, "Cannot remove null stateObject"); - seedData.remove(stateObject); + public Builder removeStateObject(Object id) { + checkNotNull(id, "Cannot remove the object with null"); + seedData.remove(id); return this; } - private ConcurrentMap buildSeedDataMap() { - final ConcurrentMap stateMap = new MapMaker().initialCapacity(seedData.size()) - .makeMap(); - for (final Any any : seedData) { - doPut(stateMap, any); + private ConcurrentMap buildSeedDataMap() { + final ConcurrentMap stateMap = new MapMaker().initialCapacity(seedData.size()) + .makeMap(); + for (Object id : seedData.keySet()) { + final Any state = seedData.get(id); + doPut(stateMap, id, state); } return stateMap; } @@ -120,7 +120,7 @@ private ConcurrentMap buildSeedDataMap() { /** * Do not expose the contents to public, as the returned {@code ConcurrentMap} must be mutable. */ - private ConcurrentMap getSeedDataMap() { + private ConcurrentMap getSeedDataMap() { return seedDataMap; } @@ -146,10 +146,11 @@ public InMemoryStandStorage build() { } - private static void doPut(final ConcurrentMap targetMap, final Any any) { + private static void doPut(final ConcurrentMap targetMap, final Object id, final Any any) { final String typeUrlString = any.getTypeUrl(); final TypeUrl typeUrl = TypeUrl.of(typeUrlString); - targetMap.put(typeUrl, any); + final AggregateStateId statId = AggregateStateId.of(id, typeUrl); + targetMap.put(statId, any); } } From ff7113c5ed395f1cd047f8ee374fc8c04d89956f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 1 Sep 2016 19:19:05 +0300 Subject: [PATCH 030/361] Simplify StandStorage and re-use RecordStorage and its implementation; implement Aggregate state fetch on top of that. --- .../org/spine3/server/BoundedContext.java | 2 +- .../server/entity/EntityRepository.java | 2 +- .../java/org/spine3/server/stand/Stand.java | 168 ++++++++++++------ .../storage/BulkStorageOperationsMixin.java | 67 +++++++ .../spine3/server/storage/RecordStorage.java | 31 +--- .../spine3/server/storage/StandStorage.java | 13 +- .../storage/memory/InMemoryRecordStorage.java | 11 +- .../storage/memory/InMemoryStandStorage.java | 112 +++++------- 8 files changed, 256 insertions(+), 150 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index 037ce3db121..9421985bb36 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -501,7 +501,7 @@ private EventBus createEventBus() { private static Stand createStand(StorageFactory storageFactory) { final StandStorage standStorage = storageFactory.createStandStorage(); final Stand result = Stand.newBuilder() - .addStorage(standStorage) + .setStorage(standStorage) .build(); return result; } diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 36b39ca4229..2a816a9450f 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -198,7 +198,7 @@ public E apply(@Nullable Map.Entry input) { * @return all the entities in this repository passed the filters. */ @CheckReturnValue - public ImmutableCollection findAll(EntityFilters filters) { + public ImmutableCollection findAll(EntityFilters filters) { final List idsList = filters.getIdFilter() .getIdsList(); final Class expectedIdClass = getIdClass(); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 7e5ee89f377..beb5be21d9f 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -21,7 +21,12 @@ */ package org.spine3.server.stand; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; @@ -30,22 +35,27 @@ import io.grpc.stub.StreamObserver; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; -import org.spine3.client.QueryOrBuilder; import org.spine3.client.QueryResponse; import org.spine3.client.Target; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.KnownTypes; +import org.spine3.protobuf.Timestamps; import org.spine3.protobuf.TypeUrl; import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.entity.Entity; import org.spine3.server.entity.EntityRepository; import org.spine3.server.entity.Repository; +import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.memory.InMemoryStandStorage; import org.spine3.type.ClassName; import javax.annotation.CheckReturnValue; +import javax.annotation.Nullable; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -53,6 +63,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; +import static com.google.common.base.Preconditions.checkNotNull; + /** * A container for storing the lastest {@link org.spine3.server.aggregate.Aggregate} states. * @@ -70,11 +82,11 @@ public class Stand { /** - * Persistent storages for the latest {@link org.spine3.server.aggregate.Aggregate} states. + * Persistent storage for the latest {@link org.spine3.server.aggregate.Aggregate} states. * - *

Any {@code Aggregate} state delivered to this instance of {@code Stand} is persisted in all of the storages. + *

Any {@code Aggregate} state delivered to this instance of {@code Stand} is persisted to this storage. */ - private final ImmutableSet storages; + private final StandStorage storage; /** * A set of callbacks to be executed upon the incoming updates. @@ -88,7 +100,7 @@ public class Stand { private final Executor callbackExecutor; /** The mapping between {@code TypeUrl} instances and repositories providing the entities of this type */ - private final ConcurrentMap typeToRepositoryMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> typeToRepositoryMap = new ConcurrentHashMap<>(); /** * Store the known {@link org.spine3.server.aggregate.Aggregate} types in order to distinguish them among all @@ -101,7 +113,7 @@ public class Stand { private final Set knownAggregateTypes = Sets.newConcurrentHashSet(); private Stand(Builder builder) { - storages = builder.getEnabledStorages(); + storage = builder.getStorage(); callbackExecutor = builder.getCallbackExecutor(); } @@ -140,9 +152,11 @@ public void update(final Object id, final Any entityState) { if (isAggregateUpdate) { final AggregateStateId aggregateStateId = AggregateStateId.of(id, typeUrl); - for (StandStorage storage : storages) { - storage.write(aggregateStateId, entityState); - } + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(entityState) + .setWhenModified(Timestamps.getCurrentTime()) + .build(); + storage.write(aggregateStateId, record); } if (callbacks.containsKey(typeUrl)) { @@ -226,7 +240,7 @@ public void execute(Query query, StreamObserver responseObserver) responseObserver.onCompleted(); } - private ImmutableCollection internalExecute(QueryOrBuilder query) { + private ImmutableCollection internalExecute(Query query) { final ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); @@ -235,28 +249,91 @@ private ImmutableCollection internalExecute(QueryOrBuilder query) { final String type = target.getType(); final ClassName typeClassName = ClassName.of(type); final TypeUrl typeUrl = KnownTypes.getTypeUrl(typeClassName); - final EntityRepository repository = typeToRepositoryMap.get(typeUrl); + final EntityRepository repository = typeToRepositoryMap.get(typeUrl); if (repository != null) { - if (target.getIncludeAll()) { - final ImmutableCollection all = repository.findAll(); - feedToBuilder(resultBuilder, all); + + // the target references an entity state + ImmutableCollection entities = fetchFromEntityRepository(target, repository); + + feedEntitiesToBuilder(resultBuilder, entities); + } else if (knownAggregateTypes.contains(typeUrl)) { + + // the target relates to an {@code Aggregate} state + ImmutableCollection stateRecords = fetchFromStandStorage(target, typeUrl); + + feedStateRecordsToBuilder(resultBuilder, stateRecords); + } + + final ImmutableSet result = resultBuilder.build(); + + return result; + } + + private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { + final ImmutableCollection result; + + if (target.getIncludeAll()) { + result = storage.readAllByType(typeUrl); + + } else { + final EntityFilters filters = target.getFilters(); + if (filters != null && filters.getIdFilter() != null) { + final EntityIdFilter idFilter = filters.getIdFilter(); + final Collection stateIds = Collections2.transform(idFilter.getIdsList(), aggregateStateIdTransformer(typeUrl)); + + final Iterable bulkReadResults = storage.readBulk(stateIds); + result = FluentIterable.from(bulkReadResults) + .filter(new Predicate() { + @Override + public boolean apply(@Nullable EntityStorageRecord input) { + return input != null; + } + }) + .toList(); } else { - final EntityFilters filters = target.getFilters(); - final ImmutableCollection bulkResults = repository.findAll(filters); - feedToBuilder(resultBuilder, bulkResults); + result = ImmutableList.of(); } + } + return result; + } - final ImmutableSet result = resultBuilder.build(); + private static Function aggregateStateIdTransformer(final TypeUrl typeUrl) { + return new Function() { + @Nullable + @Override + public AggregateStateId apply(@Nullable EntityId input) { + checkNotNull(input); + + final AggregateStateId stateId = AggregateStateId.of(input.getId(), typeUrl); + return stateId; + } + }; + } + private static ImmutableCollection fetchFromEntityRepository(Target target, EntityRepository repository) { + final ImmutableCollection result; + if (target.getIncludeAll()) { + result = repository.findAll(); + } else { + final EntityFilters filters = target.getFilters(); + result = repository.findAll(filters); + } return result; } - private static void feedToBuilder(ImmutableSet.Builder resultBuilder, ImmutableCollection all) { - for (Object rawEntity : all) { - final Entity entity = (Entity) rawEntity; - final Message state = entity.getState(); + private static void feedEntitiesToBuilder(ImmutableSet.Builder resultBuilder, ImmutableCollection all) { + for (Entity record : all) { + final Message state = record.getState(); + final Any packedState = AnyPacker.pack(state); + resultBuilder.add(packedState); + } + } + + private static void feedStateRecordsToBuilder(ImmutableSet.Builder resultBuilder, ImmutableCollection all) { + for (EntityStorageRecord record : all) { + final Message state = record.getState(); final Any packedState = AnyPacker.pack(state); resultBuilder.add(packedState); } @@ -274,14 +351,14 @@ private static void feedToBuilder(ImmutableSet.Builder resultBuilder, Immut *

However, the type of the {@code AggregateRepository} instance is recorded for the postponed processing * of updates. * - * @see #update(Any) + * @see #update(Object, Any) */ @SuppressWarnings("ChainOfInstanceofChecks") public > void registerTypeSupplier(Repository repository) { final TypeUrl entityType = repository.getEntityStateType(); if (repository instanceof EntityRepository) { - typeToRepositoryMap.put(entityType, (EntityRepository) repository); + typeToRepositoryMap.put(entityType, (EntityRepository) repository); } if (repository instanceof AggregateRepository) { knownAggregateTypes.add(entityType); @@ -308,33 +385,30 @@ public interface StandUpdateCallback { public static class Builder { - private final Set userProvidedStorages = Sets.newHashSet(); - private ImmutableSet enabledStorages; + private StandStorage storage; private Executor callbackExecutor; /** - * Add an instance of {@link StandStorage} to be used to persist the latest an Aggregate states. + * Set an instance of {@link StandStorage} to be used to persist the latest an Aggregate states. + * + *

If no {@code storage} is assigned, the {@link InMemoryStandStorage} is be set by default. * * @param storage an instance of {@code StandStorage} * @return this instance of {@code Builder} */ - public Builder addStorage(StandStorage storage) { - userProvidedStorages.add(storage); + public Builder setStorage(StandStorage storage) { + this.storage = storage; return this; } - public Builder removeStorage(StandStorage storage) { - userProvidedStorages.remove(storage); - return this; - } public Executor getCallbackExecutor() { return callbackExecutor; } /** - * Sets an {@code Executor} to be used for executing callback methods. + * Set an {@code Executor} to be used for executing callback methods. * *

If the {@code Executor} is not set, {@link MoreExecutors#directExecutor()} will be used. * @@ -346,31 +420,23 @@ public Builder setCallbackExecutor(Executor callbackExecutor) { return this; } - @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection is immutable - public ImmutableSet getEnabledStorages() { - return enabledStorages; - } - - - private ImmutableSet composeEnabledStorages() { - final ImmutableSet.Builder builder = ImmutableSet.builder(); - if (userProvidedStorages.isEmpty()) { - final InMemoryStandStorage inMemoryStandStorage = InMemoryStandStorage.newBuilder() - .build(); - builder.add(inMemoryStandStorage); - } - builder.addAll(userProvidedStorages); - return builder.build(); + public StandStorage getStorage() { + return storage; } /** - * Build an instance of {@code Stand} + * Build an instance of {@code Stand}. * * @return the instance of Stand */ public Stand build() { - this.enabledStorages = composeEnabledStorages(); + + if (storage == null) { + storage = InMemoryStandStorage.newBuilder() + .build(); + } + if (callbackExecutor == null) { callbackExecutor = MoreExecutors.directExecutor(); } diff --git a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java new file mode 100644 index 00000000000..b8c5e6690b5 --- /dev/null +++ b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java @@ -0,0 +1,67 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.storage; + +import com.google.protobuf.Message; + +import javax.annotation.CheckReturnValue; +import java.util.Map; + +/** + * Mixin contract for storages providing bulk operations. + * + *

Defines the common API for storages, which are able to effectively implement bulk reads and writes. + * + * @param a type for entity identifiers + * @param stored record type + * @author Alex Tymchenko + */ +/* package */ interface BulkStorageOperationsMixin { + + /** + * Reads the records from the storage with the given IDs. + * + *

The size of {@link Iterable} returned is always the same as the size of given IDs. + * + *

In case there is no record for a particular ID, {@code null} will be present in the result. + * + * @param ids record IDs of interest + * @return the {@link Iterable} containing the records matching the given IDs + * @throws IllegalStateException if the storage was closed before + */ + @CheckReturnValue + Iterable + readBulk(Iterable ids); + + + /** + * Reads all the records from the storage. + * + *

Each record is returned as a {@link Map} of record ID to the instance of a record. + * Such an approach enables turning the records into entities for callees. + * + * @return the {@code Map} containing the ID -> record entries. + * @throws IllegalStateException if the storage was closed before + */ + @CheckReturnValue + Map readAll(); +} diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index 9ceca68bbc6..f195163973e 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -23,6 +23,7 @@ import org.spine3.SPI; import org.spine3.server.entity.Entity; +import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import java.util.Map; @@ -37,7 +38,8 @@ * @see Entity */ @SPI -public abstract class RecordStorage extends AbstractStorage { +public abstract class RecordStorage extends AbstractStorage + implements BulkStorageOperationsMixin { protected RecordStorage(boolean multitenant) { super(multitenant); @@ -64,17 +66,8 @@ public void write(I id, EntityStorageRecord record) { writeInternal(id, record); } - /** - * Reads the records from the storage with the given IDs. - * - *

The size of {@link Iterable} returned is always the same as the size of given IDs. - * - *

In case there is no record for a particular ID, {@code null} will be present in the result. - * - * @param ids record IDs of interest - * @return the {@link Iterable} containing the records matching the given IDs - * @throws IllegalStateException if the storage was closed before - */ + + @Override public Iterable readBulk(Iterable ids) { checkNotClosed(); checkNotNull(ids); @@ -82,15 +75,7 @@ public Iterable readBulk(Iterable ids) { return readBulkInternal(ids); } - /** - * Reads all the records from the storage. - * - *

Each record is returned as a {@link Map} of record ID to the instance of {@link EntityStorageRecord}. - * Such an approach enables turning the records into entities for callees. - * - * @return the {@code Map} containing the ID -> record entries. - * @throws IllegalStateException if the storage was closed before - */ + @Override public Map readAll() { checkNotClosed(); @@ -111,11 +96,11 @@ public Map readAll() { @Nullable protected abstract EntityStorageRecord readInternal(I id); - /** @see RecordStorage#readBulk(java.lang.Iterable) */ + /** @see BulkStorageOperationsMixin#readBulk(java.lang.Iterable) */ protected abstract Iterable readBulkInternal(Iterable ids); - /** @see RecordStorage#readAll() */ + /** @see BulkStorageOperationsMixin#readAll() */ protected abstract Map readAllInternal(); /** diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index ee325cced5e..aef39ba950f 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -21,7 +21,9 @@ */ package org.spine3.server.storage; +import com.google.common.collect.ImmutableCollection; import com.google.protobuf.Any; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.stand.AggregateStateId; import org.spine3.server.stand.Stand; @@ -34,9 +36,18 @@ * @see Any#getTypeUrl() * @see Stand */ -public abstract class StandStorage extends AbstractStorage { +public abstract class StandStorage extends RecordStorage { protected StandStorage(boolean multitenant) { super(multitenant); } + + + /** + * Reads all the state records by the given type. + * + * @param type a {@link TypeUrl} instance + * @return the state records which {@link Any#getTypeUrl()} equals the argument value + */ + public abstract ImmutableCollection readAllByType(TypeUrl type); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 9566b075f0e..7780bf322c5 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -20,7 +20,6 @@ package org.spine3.server.storage.memory; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.RecordStorage; @@ -37,12 +36,14 @@ /** * Memory-based implementation of {@link RecordStorage}. * - * @author Alexander Litus + * @author Alexander Litus, Alex Tymchenko */ /* package */ class InMemoryRecordStorage extends RecordStorage { /** A stub instance of {@code TenantId} to be used by the storage in single-tenant context. */ - private static final TenantId singleTenant = TenantId.newBuilder().setValue("SINGLE_TENANT").build(); + private static final TenantId singleTenant = TenantId.newBuilder() + .setValue("SINGLE_TENANT") + .build(); private final Map> tenantToStorageMap = newHashMap(); @@ -56,10 +57,12 @@ protected InMemoryRecordStorage(boolean multitenant) { protected Iterable readBulkInternal(final Iterable givenIds) { final Map storage = getStorage(); + // It is not possible to return an immutable collection, since {@code null} may be present in it. final Collection result = new LinkedList<>(); + for (I recordId : storage.keySet()) { for (I givenId : givenIds) { - if(recordId.equals(givenId)) { + if (recordId.equals(givenId)) { final EntityStorageRecord matchingRecord = storage.get(recordId); result.add(matchingRecord); continue; diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 3acf7266c97..75d8a0e5716 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -21,13 +21,16 @@ */ package org.spine3.server.storage.memory; -import com.google.common.collect.MapMaker; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; -import com.google.protobuf.Any; import org.spine3.protobuf.TypeUrl; import org.spine3.server.stand.AggregateStateId; +import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; +import javax.annotation.Nullable; import java.util.Map; import java.util.concurrent.ConcurrentMap; @@ -43,11 +46,11 @@ */ public class InMemoryStandStorage extends StandStorage { - private final ConcurrentMap aggregateStates; + private final InMemoryRecordStorage recordStorage; private InMemoryStandStorage(Builder builder) { super(builder.isMultitenant()); - aggregateStates = builder.getSeedDataMap(); + recordStorage = new InMemoryRecordStorage<>(builder.isMultitenant()); } public static Builder newBuilder() { @@ -55,74 +58,54 @@ public static Builder newBuilder() { } @Override - public Any read(AggregateStateId id) { - final Any result = aggregateStates.get(id); + public ImmutableCollection readAllByType(final TypeUrl type) { + final Map allRecords = readAll(); + final Map resultMap = Maps.filterKeys(allRecords, new Predicate() { + @Override + public boolean apply(@Nullable AggregateStateId stateId) { + checkNotNull(stateId); + final boolean typeMatches = stateId.getStateType() + .equals(type); + return typeMatches; + } + }); + + final ImmutableList result = ImmutableList.copyOf(resultMap.values()); return result; } + @Nullable @Override - public void write(AggregateStateId id, Any record) { - final TypeUrl recordType = TypeUrl.of(record.getTypeUrl()); - checkState(id.getStateType() == recordType, "The typeUrl of the record does not correspond to id"); - - doPut(aggregateStates, id, record); + protected EntityStorageRecord readInternal(AggregateStateId id) { + final EntityStorageRecord result = recordStorage.read(id); + return result; } - public static class Builder { + @Override + protected Iterable readBulkInternal(Iterable ids) { + final Iterable result = recordStorage.readBulk(ids); + return result; + } - private final Map seedData = Maps.newHashMap(); - private ConcurrentMap seedDataMap; - private boolean multitenant; + @Override + protected Map readAllInternal() { + final Map result = recordStorage.readAll(); + return result; + } - /** - * Add a seed data item. - * - *

Use this method to pre-fill the {@link StandStorage}. - * - *

The argument value must not be null. - * - * @param id the id of state object - * @param stateObject the state object to be added - * @return {@code this} instance of {@code Builder} - */ - public Builder addStateObject(Object id, Any stateObject) { - checkNotNull(stateObject, "stateObject must not be null"); - checkNotNull(id, "id must not be null"); + @Override + protected void writeInternal(AggregateStateId id, EntityStorageRecord record) { + final TypeUrl recordType = TypeUrl.of(record.getState() + .getTypeUrl()); + checkState(id.getStateType() == recordType, "The typeUrl of the record does not correspond to id"); - seedData.put(id, stateObject); - return this; - } + recordStorage.write(id, record); + } - /** - * Remove a previously added seed data item. - * - *

The argument value must not be null. - * - * @param id the ID of state object to be removed - * @return {@code this} instance of {@code Builder} - */ - public Builder removeStateObject(Object id) { - checkNotNull(id, "Cannot remove the object with null"); - seedData.remove(id); - return this; - } - private ConcurrentMap buildSeedDataMap() { - final ConcurrentMap stateMap = new MapMaker().initialCapacity(seedData.size()) - .makeMap(); - for (Object id : seedData.keySet()) { - final Any state = seedData.get(id); - doPut(stateMap, id, state); - } - return stateMap; - } + public static class Builder { - /** - * Do not expose the contents to public, as the returned {@code ConcurrentMap} must be mutable. - */ - private ConcurrentMap getSeedDataMap() { - return seedDataMap; - } + private boolean multitenant; public boolean isMultitenant() { return multitenant; @@ -139,18 +122,9 @@ public Builder setMultitenant(boolean multitenant) { * @return an instance of in-memory storage */ public InMemoryStandStorage build() { - this.seedDataMap = buildSeedDataMap(); final InMemoryStandStorage result = new InMemoryStandStorage(this); return result; } } - - private static void doPut(final ConcurrentMap targetMap, final Object id, final Any any) { - final String typeUrlString = any.getTypeUrl(); - final TypeUrl typeUrl = TypeUrl.of(typeUrlString); - final AggregateStateId statId = AggregateStateId.of(id, typeUrl); - targetMap.put(statId, any); - } - } From b1821f13b3d5982241bc40ce69e082df94f6e093 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 2 Sep 2016 16:31:20 +0300 Subject: [PATCH 031/361] Cover QueryService with basic unit tests. --- .../java/org/spine3/server/QueryService.java | 18 ++++-- .../org/spine3/server/QueryServiceShould.java | 60 ++++++++++++++++--- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index a0e788a0f36..1d4252263d8 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -58,9 +58,11 @@ public static Builder newBuilder() { @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. @Override - public void read(Query request, StreamObserver responseObserver) { - final String typeAsString = request.getTarget() - .getType(); + public void read(Query query, StreamObserver responseObserver) { + + log().debug("Incoming query: {}", query); + final String typeAsString = query.getTarget() + .getType(); // TODO[alex.tymchenko]: too complex final ClassName typeClassName = ClassName.of(typeAsString); @@ -69,11 +71,12 @@ public void read(Query request, StreamObserver responseObserver) try { boundedContext.getStand() - .execute(request, responseObserver); + .execute(query, responseObserver); } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { log().error("Error processing query", e); responseObserver.onError(e); + responseObserver.onCompleted(); } } @@ -101,8 +104,13 @@ public ImmutableMap getBoundedContextMap() { /** * Builds the {@link QueryService}. + * + * @throws IllegalStateException if no bounded contexts were added. */ - public QueryService build() { + public QueryService build() throws IllegalStateException { + if(boundedContexts.isEmpty()) { + throw new IllegalStateException("Query service must have at least one bounded context."); + } this.typeToContextMap = createBoundedContextMap(); final QueryService result = new QueryService(this); return result; diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index 89176d4b7c7..8dd9844c6a4 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -22,8 +22,6 @@ package org.spine3.server; import com.google.common.collect.Sets; -import com.google.protobuf.Any; -import com.google.protobuf.Empty; import io.grpc.stub.StreamObserver; import org.junit.After; import org.junit.Before; @@ -39,7 +37,6 @@ import org.spine3.test.bc.event.ProjectCreated; import org.spine3.test.clientservice.ProjectId; import org.spine3.test.projection.Project; -import org.spine3.testdata.TestCommandBusFactory; import org.spine3.testdata.TestStandFactory; import java.util.Set; @@ -48,9 +45,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; @@ -66,6 +64,7 @@ public class QueryServiceShould { private BoundedContext projectsContext; private BoundedContext customersContext; private final TestQueryResponseObserver responseObserver = new TestQueryResponseObserver(); + private ProjectDetailsRepository projectDetailsRepository; @Before public void setUp() { @@ -74,18 +73,19 @@ public void setUp() { final Given.ProjectAggregateRepository projectRepo = new Given.ProjectAggregateRepository(projectsContext); projectsContext.register(projectRepo); - final ProjectDetailsRepository projectDetailsRepository = new ProjectDetailsRepository(projectsContext); + projectDetailsRepository = spy(new ProjectDetailsRepository(projectsContext)); projectsContext.register(projectDetailsRepository); boundedContexts.add(projectsContext); // Create Customers Bounded Context with one repository. - customersContext = newBoundedContext(spy(TestCommandBusFactory.create())); + customersContext = newBoundedContext(spy(TestStandFactory.create())); final Given.CustomerAggregateRepository customerRepo = new Given.CustomerAggregateRepository(customersContext); customersContext.register(customerRepo); boundedContexts.add(customersContext); + final QueryService.Builder builder = QueryService.newBuilder(); for (BoundedContext context : boundedContexts) { @@ -105,17 +105,63 @@ public void tearDown() throws Exception { @Test public void execute_queries() { + final Query query = Given.Query.readAllProjects(); + service.read(query, responseObserver); + checkOkResponse(responseObserver); + } + + @Test + public void dispatch_queries_to_proper_bounded_context() { final Query query = Given.Query.readAllProjects(); final Stand stand = projectsContext.getStand(); service.read(query, responseObserver); + checkOkResponse(responseObserver); + verify(stand).execute(query, responseObserver); + + verify(customersContext.getStand(), never()).execute(query, responseObserver); + } + + @Test(expected = IllegalStateException.class) + public void fail_to_create_with_removed_bounded_context_from_builder() { + final BoundedContext boundedContext = newBoundedContext(TestStandFactory.create()); + + final QueryService.Builder builder = QueryService.newBuilder(); + builder.addBoundedContext(boundedContext) + .removeBoundedContext(boundedContext) + .build(); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = IllegalStateException.class) + public void fail_to_create_with_no_bounded_context() { + QueryService.newBuilder() + .build(); + } + + + @Test + public void return_error_if_query_failed_to_execute() { + when(projectDetailsRepository.findAll()).thenThrow(RuntimeException.class); + final Query query = Given.Query.readAllProjects(); + service.read(query, responseObserver); + checkFailureResponse(responseObserver); + } + + + private static void checkOkResponse(TestQueryResponseObserver responseObserver) { final QueryResponse responseHandled = responseObserver.getResponseHandled(); assertNotNull(responseHandled); assertEquals(Responses.ok(), responseHandled.getResponse()); assertTrue(responseObserver.isCompleted()); assertNull(responseObserver.getThrowable()); - verify(stand).execute(query, responseObserver); + } + private static void checkFailureResponse(TestQueryResponseObserver responseObserver) { + final QueryResponse responseHandled = responseObserver.getResponseHandled(); + assertNull(responseHandled); + assertTrue(responseObserver.isCompleted()); + assertNotNull(responseObserver.getThrowable()); } From 44ba1229fe317340bd2bb899c555761a8c0b7253 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 12 Sep 2016 16:11:21 +0300 Subject: [PATCH 032/361] Describe some scenarios for Stand and StandFunnel tests. --- .../server/stand/StandFunnelShould.java | 50 +++++++++++++++++++ .../org/spine3/server/stand/StandShould.java | 44 ++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 server/src/test/java/org/spine3/server/stand/StandFunnelShould.java create mode 100644 server/src/test/java/org/spine3/server/stand/StandShould.java diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java new file mode 100644 index 00000000000..7ec428c6b35 --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -0,0 +1,50 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +/** + * @author Alex Tymchenko + */ +public class StandFunnelShould { + + // **** Positive scenarios (unit) **** + + /** + * - Initialize properly with various Builder options; + * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. + */ + + + // **** Negative scenarios (unit) **** + + /** + * - Fail to initialise with improper stand. + */ + + // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** + + /** + * - Deliver updates from projection repo on update; + * - deliver updates from aggregate repo on update; + * - deliver the updates from several projection and aggregate repositories. + */ +} diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java new file mode 100644 index 00000000000..68a4056f26d --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -0,0 +1,44 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +/** + * @author Alex Tymchenko + */ +public class StandShould { + +// **** Positive scenarios **** + + /** + * - initialize properly with various Builder options; + * - register aggregate repositories by changing the known aggregate types. + * - register entity repositories properly + * - avoid duplicates while registering repositories + */ + + + // **** Negative scenarios **** + + /** + * - fail to initialize with improper build arguments. + */ +} From 5d61aca74d12d91c78bf8e19f405e9ef6fb908b9 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 12 Sep 2016 18:18:55 +0300 Subject: [PATCH 033/361] Add some StandFunnel tests as a start. --- .../server/stand/StandFunnelShould.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 7ec428c6b35..9e42f37e68e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -21,6 +21,17 @@ */ package org.spine3.server.stand; +import com.google.protobuf.Any; +import org.junit.Assert; +import org.junit.Test; +import org.spine3.testdata.TestStandFactory; + +import java.util.concurrent.Executor; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + /** * @author Alex Tymchenko */ @@ -33,6 +44,39 @@ public class StandFunnelShould { * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. */ + @Test + public void initialize_properly_with_stand_only() { + final Stand stand = TestStandFactory.create(); + final StandFunnel.Builder builder = StandFunnel.newBuilder() + .setStand(stand); + final StandFunnel standFunnel = builder.build(); + Assert.assertNotNull(standFunnel); + } + + + @Test + public void use_executor_from_builder() { + final Stand stand = spy(TestStandFactory.create()); + final Executor executor = spy(new Executor() { + @Override + public void execute(Runnable command) { + + } + }); + final StandFunnel.Builder builder = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(executor); + + final StandFunnel standFunnel = builder.build(); + Assert.assertNotNull(standFunnel); + + final Any someState = Any.getDefaultInstance(); + final Object someId = new Object(); + standFunnel.post(someId, someState); + + verify(executor).execute(any(Runnable.class)); + } + // **** Negative scenarios (unit) **** @@ -40,6 +84,13 @@ public class StandFunnelShould { * - Fail to initialise with improper stand. */ + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = IllegalStateException.class) + public void fail_to_initialize_from_empty_builder() { + final StandFunnel.Builder builder = StandFunnel.newBuilder(); + builder.build(); + } + // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** /** @@ -47,4 +98,6 @@ public class StandFunnelShould { * - deliver updates from aggregate repo on update; * - deliver the updates from several projection and aggregate repositories. */ + + } From 519b8551c5f7725460614b0da3f081263bd59e5d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 12 Sep 2016 21:03:41 +0300 Subject: [PATCH 034/361] Add some Stand-based unit tests. --- .../org/spine3/server/stand/StandShould.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 68a4056f26d..57a64c84c24 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -21,6 +21,22 @@ */ package org.spine3.server.stand; +import com.google.common.collect.ImmutableSet; +import com.google.protobuf.Descriptors; +import org.junit.Assert; +import org.junit.Test; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.BoundedContext; +import org.spine3.server.projection.Projection; +import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.storage.StandStorage; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; + /** * @author Alex Tymchenko */ @@ -35,10 +51,79 @@ public class StandShould { * - avoid duplicates while registering repositories */ + @Test + public void initialize_with_empty_builder() { + final Stand.Builder builder = Stand.newBuilder(); + final Stand stand = builder.build(); + + Assert.assertNotNull(stand); + Assert.assertTrue("Available types must be empty after initialization.", stand.getAvailableTypes() + .isEmpty()); + } + + @Test + // TODO[alex.tymchenko]: either add more meaningful checks or remove it. + public void initialize_with_storage_provided_through_builder() { + final StandStorage standStorageMock = spy(mock(StandStorage.class)); + final Stand stand = Stand.newBuilder() + .setStorage(standStorageMock) + .build(); + Assert.assertNotNull(stand); + } + + @Test + public void register_projection_repositories() { + final Stand stand = Stand.newBuilder() + .build(); + final BoundedContext boundedContext = newBoundedContext(stand); + + Assert.assertTrue(stand.getAvailableTypes() + .isEmpty()); + + final StandTestProjectionRepository standTestProjectionRepo = new StandTestProjectionRepository(boundedContext); + stand.registerTypeSupplier(standTestProjectionRepo); + checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); + + final StandTestProjectionRepository anotherTestProjectionRepo = new StandTestProjectionRepository(boundedContext); + stand.registerTypeSupplier(anotherTestProjectionRepo); + checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); + } + + private static void checkHasExactlyOne(ImmutableSet availableTypes, Descriptors.Descriptor expectedType) { + Assert.assertEquals(1, availableTypes.size()); + + final TypeUrl actualTypeUrl = availableTypes.iterator() + .next(); + final TypeUrl expectedTypeUrl = TypeUrl.of(expectedType); + Assert.assertEquals("Type was registered incorrectly", expectedTypeUrl, actualTypeUrl); + } + // **** Negative scenarios **** /** * - fail to initialize with improper build arguments. */ + + + // ***** Inner classes used for tests. ***** + + private static class StandTestProjection extends Projection { + /** + * Creates a new instance. + * + * @param id the ID for the new instance + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestProjection(ProjectId id) { + super(id); + } + } + + + private static class StandTestProjectionRepository extends ProjectionRepository { + protected StandTestProjectionRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } } From 800628407f884d25117fc403b58c8aa9d9d40833 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 13 Sep 2016 16:22:32 +0300 Subject: [PATCH 035/361] Allow to fetch the known aggregate types from a Stand instance. Use this method in repository registration tests. --- .../java/org/spine3/server/stand/Stand.java | 12 +++++++ .../org/spine3/server/stand/StandShould.java | 32 +++++++++++++------ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index beb5be21d9f..1eb8e1fb5b4 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -220,6 +220,18 @@ public ImmutableSet getAvailableTypes() { return result; } + /** + * Read all {@link org.spine3.server.aggregate.Aggregate} entity types exposed for reading + * by this instance of {@code Stand}. + * + * @return the set of types as {@link TypeUrl} instances + */ + @CheckReturnValue + public ImmutableSet getKnownAggregateTypes() { + final ImmutableSet result = ImmutableSet.copyOf(knownAggregateTypes); + return result; + } + /** * Read a particular set of items from the read-side of the application and feed the result into an instance * diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 57a64c84c24..84bdf079bea 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Descriptors; -import org.junit.Assert; import org.junit.Test; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; @@ -33,6 +32,11 @@ import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; @@ -56,9 +60,12 @@ public void initialize_with_empty_builder() { final Stand.Builder builder = Stand.newBuilder(); final Stand stand = builder.build(); - Assert.assertNotNull(stand); - Assert.assertTrue("Available types must be empty after initialization.", stand.getAvailableTypes() - .isEmpty()); + assertNotNull(stand); + assertTrue("Available types must be empty after the initialization.", stand.getAvailableTypes() + .isEmpty()); + assertTrue("Known aggregate types must be emtpy after the initialization", stand.getKnownAggregateTypes() + .isEmpty()); + } @Test @@ -68,7 +75,7 @@ public void initialize_with_storage_provided_through_builder() { final Stand stand = Stand.newBuilder() .setStorage(standStorageMock) .build(); - Assert.assertNotNull(stand); + assertNotNull(stand); } @Test @@ -77,25 +84,30 @@ public void register_projection_repositories() { .build(); final BoundedContext boundedContext = newBoundedContext(stand); - Assert.assertTrue(stand.getAvailableTypes() - .isEmpty()); + assertTrue(stand.getAvailableTypes() + .isEmpty()); + assertTrue(stand.getKnownAggregateTypes() + .isEmpty()); final StandTestProjectionRepository standTestProjectionRepo = new StandTestProjectionRepository(boundedContext); stand.registerTypeSupplier(standTestProjectionRepo); checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); + final ImmutableSet knownAggregateTypes = stand.getKnownAggregateTypes(); + // As we registered a projection repo, known aggregate types should be still empty. + assertTrue("For some reason an aggregate type was registered", knownAggregateTypes.isEmpty()); final StandTestProjectionRepository anotherTestProjectionRepo = new StandTestProjectionRepository(boundedContext); stand.registerTypeSupplier(anotherTestProjectionRepo); checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); } - private static void checkHasExactlyOne(ImmutableSet availableTypes, Descriptors.Descriptor expectedType) { - Assert.assertEquals(1, availableTypes.size()); + private static void checkHasExactlyOne(Set availableTypes, Descriptors.Descriptor expectedType) { + assertEquals(1, availableTypes.size()); final TypeUrl actualTypeUrl = availableTypes.iterator() .next(); final TypeUrl expectedTypeUrl = TypeUrl.of(expectedType); - Assert.assertEquals("Type was registered incorrectly", expectedTypeUrl, actualTypeUrl); + assertEquals("Type was registered incorrectly", expectedTypeUrl, actualTypeUrl); } From 2a89b8df0e5736005ba61828a3cdab188c78f720 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 13 Sep 2016 16:24:16 +0300 Subject: [PATCH 036/361] Fix a typo in the assertion message. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 84bdf079bea..46d2d7b12a6 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -63,7 +63,7 @@ public void initialize_with_empty_builder() { assertNotNull(stand); assertTrue("Available types must be empty after the initialization.", stand.getAvailableTypes() .isEmpty()); - assertTrue("Known aggregate types must be emtpy after the initialization", stand.getKnownAggregateTypes() + assertTrue("Known aggregate types must be empty after the initialization", stand.getKnownAggregateTypes() .isEmpty()); } From e209b89e14c4ab79691eafa379e7c55b29875fc3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 13 Sep 2016 19:58:34 +0300 Subject: [PATCH 037/361] Add aggregate repository registration test: + adjust Given to reuse CustomerAggregateRepository; + fix Stand#getAvailableTypes() to include aggregate types as well; + implement aggregate repository registration test. --- .../java/org/spine3/server/stand/Stand.java | 9 ++++- .../test/java/org/spine3/server/Given.java | 6 +-- .../org/spine3/server/stand/StandShould.java | 37 +++++++++++++++++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 1eb8e1fb5b4..346e4bc3cb8 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -211,12 +211,17 @@ public void unwatch(TypeUrl typeUrl, StandUpdateCallback callback) { /** * Read all {@link Entity} types exposed for reading by this instance of {@code Stand}. * + *

The result includes all values from {@link #getKnownAggregateTypes()} as well. + * * @return the set of types as {@link TypeUrl} instances */ @CheckReturnValue public ImmutableSet getAvailableTypes() { - final Set types = typeToRepositoryMap.keySet(); - final ImmutableSet result = ImmutableSet.copyOf(types); + final ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); + final Set projectionTypes = typeToRepositoryMap.keySet(); + resultBuilder.addAll(projectionTypes) + .addAll(knownAggregateTypes); + final ImmutableSet result = resultBuilder.build(); return result; } diff --git a/server/src/test/java/org/spine3/server/Given.java b/server/src/test/java/org/spine3/server/Given.java index 672f19e4c3d..cbbb521d1ed 100644 --- a/server/src/test/java/org/spine3/server/Given.java +++ b/server/src/test/java/org/spine3/server/Given.java @@ -55,7 +55,7 @@ import static org.spine3.protobuf.Timestamps.getCurrentTime; import static org.spine3.testdata.TestCommandContextFactory.createCommandContext; -/* package */ class Given { +public class Given { /* package */ static class AggregateId { @@ -229,8 +229,8 @@ private void event(ProjectStarted event) { } } - /* package */ static class CustomerAggregateRepository extends AggregateRepository { - /* package */ CustomerAggregateRepository(BoundedContext boundedContext) { + public static class CustomerAggregateRepository extends AggregateRepository { + public CustomerAggregateRepository(BoundedContext boundedContext) { super(boundedContext); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 46d2d7b12a6..31a27fcc746 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -26,9 +26,11 @@ import org.junit.Test; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.Given; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.StandStorage; +import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; @@ -84,14 +86,12 @@ public void register_projection_repositories() { .build(); final BoundedContext boundedContext = newBoundedContext(stand); - assertTrue(stand.getAvailableTypes() - .isEmpty()); - assertTrue(stand.getKnownAggregateTypes() - .isEmpty()); + checkTypesEmpty(stand); final StandTestProjectionRepository standTestProjectionRepo = new StandTestProjectionRepository(boundedContext); stand.registerTypeSupplier(standTestProjectionRepo); checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); + final ImmutableSet knownAggregateTypes = stand.getKnownAggregateTypes(); // As we registered a projection repo, known aggregate types should be still empty. assertTrue("For some reason an aggregate type was registered", knownAggregateTypes.isEmpty()); @@ -101,6 +101,35 @@ public void register_projection_repositories() { checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); } + @Test + public void register_aggregate_repositories() { + final Stand stand = Stand.newBuilder() + .build(); + final BoundedContext boundedContext = newBoundedContext(stand); + + checkTypesEmpty(stand); + + final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + stand.registerTypeSupplier(customerAggregateRepo); + + final Descriptors.Descriptor customerEntityDescriptor = Customer.getDescriptor(); + checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); + checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); + + @SuppressWarnings("LocalVariableNamingConvention") + final Given.CustomerAggregateRepository anotherCustomerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + stand.registerTypeSupplier(anotherCustomerAggregateRepo); + checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); + checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); + } + + private static void checkTypesEmpty(Stand stand) { + assertTrue(stand.getAvailableTypes() + .isEmpty()); + assertTrue(stand.getKnownAggregateTypes() + .isEmpty()); + } + private static void checkHasExactlyOne(Set availableTypes, Descriptors.Descriptor expectedType) { assertEquals(1, availableTypes.size()); From 23463e5ac0803708f074f7b0be45fc51727a1a21 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 13 Sep 2016 21:02:45 +0300 Subject: [PATCH 038/361] Add more Stand tests: * verify callback executor works as expected; * test StandStorage customization works properly. --- .../test/java/org/spine3/server/Given.java | 2 +- .../org/spine3/server/stand/StandShould.java | 105 ++++++++++++++++-- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/server/src/test/java/org/spine3/server/Given.java b/server/src/test/java/org/spine3/server/Given.java index cbbb521d1ed..93be90b22aa 100644 --- a/server/src/test/java/org/spine3/server/Given.java +++ b/server/src/test/java/org/spine3/server/Given.java @@ -235,7 +235,7 @@ public CustomerAggregateRepository(BoundedContext boundedContext) { } } - private static class CustomerAggregate extends Aggregate { + public static class CustomerAggregate extends Aggregate { @SuppressWarnings("PublicConstructorInNonPublicClass") // by convention (as it's used by Reflection). public CustomerAggregate(CustomerId id) { diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 31a27fcc746..98bc0609385 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -22,25 +22,38 @@ package org.spine3.server.stand; import com.google.common.collect.ImmutableSet; +import com.google.protobuf.Any; import com.google.protobuf.Descriptors; import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InOrder; +import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.Given; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; +import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.calls; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.never; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; /** @@ -70,16 +83,6 @@ public void initialize_with_empty_builder() { } - @Test - // TODO[alex.tymchenko]: either add more meaningful checks or remove it. - public void initialize_with_storage_provided_through_builder() { - final StandStorage standStorageMock = spy(mock(StandStorage.class)); - final Stand stand = Stand.newBuilder() - .setStorage(standStorageMock) - .build(); - assertNotNull(stand); - } - @Test public void register_projection_repositories() { final Stand stand = Stand.newBuilder() @@ -123,6 +126,77 @@ public void register_aggregate_repositories() { checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); } + @Test + public void use_provided_executor_upon_update_of_watched_type() { + final Executor executor = mock(Executor.class); + final InOrder executorInOrder = inOrder(executor); + final Stand stand = Stand.newBuilder() + .setCallbackExecutor(executor) + .build(); + final BoundedContext boundedContext = newBoundedContext(stand); + final StandTestProjectionRepository standTestProjectionRepo = new StandTestProjectionRepository(boundedContext); + stand.registerTypeSupplier(standTestProjectionRepo); + + final TypeUrl projectProjectionType = TypeUrl.of(Project.class); + stand.watch(projectProjectionType, emptyUpdateCallback()); + + executorInOrder.verify(executor, never()) + .execute(any(Runnable.class)); + + final Any someUpdate = AnyPacker.pack(Project.getDefaultInstance()); + final Object someId = new Object(); + stand.update(someId, someUpdate); + + executorInOrder.verify(executor, calls(1)) + .execute(any(Runnable.class)); + } + + @Test + public void operate_with_storage_provided_through_builder() { + final StandStorage standStorageMock = mock(StandStorage.class); + final InOrder standStorageInOrder = inOrder(standStorageMock); + final Stand stand = Stand.newBuilder() + .setStorage(standStorageMock) + .build(); + assertNotNull(stand); + + final BoundedContext boundedContext = newBoundedContext(stand); + final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + stand.registerTypeSupplier(customerAggregateRepo); + + + final int numericIdValue = 17; + final CustomerId customerId = CustomerId.newBuilder() + .setNumber(numericIdValue) + .build(); + final Given.CustomerAggregate customerAggregate = customerAggregateRepo.create(customerId); + final Customer customerState = customerAggregate.getState(); + final Any packedState = AnyPacker.pack(customerState); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + standStorageInOrder.verify(standStorageMock, never()) + .write(any(AggregateStateId.class), any(EntityStorageRecord.class)); + + stand.update(customerId, packedState); + + final AggregateStateId expectedAggregateStateId = AggregateStateId.of(customerId, customerType); + final EntityStorageRecord expectedRecord = EntityStorageRecord.newBuilder() + .setState(packedState) + .build(); + standStorageInOrder.verify(standStorageMock, calls(1)) + .write(eq(expectedAggregateStateId), recordStateMatcher(expectedRecord)); + } + + private static EntityStorageRecord recordStateMatcher(final EntityStorageRecord expectedRecord) { + return argThat(new ArgumentMatcher() { + @Override + public boolean matches(EntityStorageRecord argument) { + final boolean matchResult = Objects.equals(expectedRecord.getState(), argument.getState()); + return matchResult; + } + }); + } + private static void checkTypesEmpty(Stand stand) { assertTrue(stand.getAvailableTypes() .isEmpty()); @@ -139,6 +213,15 @@ private static void checkHasExactlyOne(Set availableTypes, Descriptors. assertEquals("Type was registered incorrectly", expectedTypeUrl, actualTypeUrl); } + private static Stand.StandUpdateCallback emptyUpdateCallback() { + return new Stand.StandUpdateCallback() { + @Override + public void onEntityStateUpdate(Any newEntityState) { + //do nothing + } + }; + } + // **** Negative scenarios **** From 3e36b5fda8fec33a0cc11af547a2c0c33c1fca08 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 13 Sep 2016 21:41:08 +0300 Subject: [PATCH 039/361] Verify BoundedContext propagates the repository to an instance of Stand during repo registration. --- .../org/spine3/server/bc/BoundedContextShould.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/server/src/test/java/org/spine3/server/bc/BoundedContextShould.java b/server/src/test/java/org/spine3/server/bc/BoundedContextShould.java index a8dbb28ce2e..376883694c8 100644 --- a/server/src/test/java/org/spine3/server/bc/BoundedContextShould.java +++ b/server/src/test/java/org/spine3/server/bc/BoundedContextShould.java @@ -47,6 +47,7 @@ import org.spine3.server.procman.ProcessManagerRepository; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.stand.Stand; import org.spine3.server.storage.StorageFactory; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.server.type.EventClass; @@ -68,6 +69,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -194,6 +196,17 @@ public void not_change_storage_during_registration_if_a_repository_has_one() { verify(spy, never()).initStorage(any(StorageFactory.class)); } + @Test + public void propagate_registered_repositories_to_stand() { + final Stand stand = spy(mock(Stand.class)); + final BoundedContext boundedContext = newBoundedContext(stand); + verify(stand, never()).registerTypeSupplier(any(Repository.class)); + + final ProjectAggregateRepository repository = new ProjectAggregateRepository(boundedContext); + boundedContext.register(repository); + verify(stand).registerTypeSupplier(eq(repository)); + } + /** Returns {@link Mockito#any()} matcher for response observer. */ @SuppressWarnings("unchecked") private static StreamObserver anyResponseObserver() { From 5f68be4aabd988c2a52714d96d2d8f26c91b6b15 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:10:41 +0300 Subject: [PATCH 040/361] Add creation test covering various Builder params. --- .../server/stand/StandFunnelShould.java | 36 ++++++++++++++++++- .../org/spine3/testdata/TestStandFactory.java | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 9e42f37e68e..5eb8fb42bdf 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -27,6 +27,8 @@ import org.spine3.testdata.TestStandFactory; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; @@ -40,7 +42,6 @@ public class StandFunnelShould { // **** Positive scenarios (unit) **** /** - * - Initialize properly with various Builder options; * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. */ @@ -53,6 +54,39 @@ public void initialize_properly_with_stand_only() { Assert.assertNotNull(standFunnel); } + @Test + public void initialize_properly_with_various_builder_options() { + final Stand stand = TestStandFactory.create(); + final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return Thread.currentThread(); + } + }); + + final StandFunnel blockingFunnel = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(executor) + .build(); + Assert.assertNotNull(blockingFunnel); + + final StandFunnel funnelForBusyStand = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(Executors.newSingleThreadExecutor()) + .build(); + Assert.assertNotNull(funnelForBusyStand); + + + final StandFunnel emptyExecutorFunnel = StandFunnel.newBuilder() + .setStand(TestStandFactory.create()) + .setExecutor(new Executor() { + @Override + public void execute(Runnable neverCalled) { } + }) + .build(); + Assert.assertNotNull(emptyExecutorFunnel); + } + @Test public void use_executor_from_builder() { diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index d54df4fae6d..474a6226b30 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -22,13 +22,13 @@ package org.spine3.testdata; import org.spine3.server.stand.Stand; -import org.spine3.server.storage.memory.InMemoryStandStorage; /** * Creates stands for tests. * * @author Alex Tymchenko */ +@SuppressWarnings("UtilityClass") public class TestStandFactory { private TestStandFactory() {} From cad1d94bbafe30991974db1eea60aba4693d5ab3 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:11:56 +0300 Subject: [PATCH 041/361] Bump gradle version, --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 58e7b927f5d..6d19c1542b6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip From d700a419894388c31b86d78c973c1fe847c3f506 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:37:55 +0300 Subject: [PATCH 042/361] Add some more tests (need to be processed). --- .../org/spine3/server/stand/StandFunnel.java | 2 ++ .../server/stand/StandFunnelShould.java | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 9e3cc659ce0..44997adbdf8 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -109,6 +109,7 @@ public Stand getStand() { *

The value must not be null. * *

If this method is not used, a default value will be used. + * // TODO:13-09-16:dmytro.dashenkov: Correct docs. No default value for Stand is used, null value leads to a fail instead. * * @param stand the instance of {@link Stand}. * @return {@code this} instance of {@code Builder} @@ -122,6 +123,7 @@ public Executor getExecutor() { return executor; } + // TODO:13-09-16:dmytro.dashenkov: Complete docs. Here is the place where a default value is used in case of not using the method. /** * Set the {@code Executor} instance for this {@code StandFunnel}. * diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 5eb8fb42bdf..da5f2bffdfd 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -87,6 +87,21 @@ public void execute(Runnable neverCalled) { } Assert.assertNotNull(emptyExecutorFunnel); } + @Test + public void deliver_mock_updates_to_stand() { + final Stand stand = spy(TestStandFactory.create()); + + final StandFunnel funnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + funnel.post(id, state); + + verify(stand).update(id, state); + } + @Test public void use_executor_from_builder() { @@ -114,9 +129,23 @@ public void execute(Runnable command) { // **** Negative scenarios (unit) **** - /** - * - Fail to initialise with improper stand. - */ + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_null_stand() { + @SuppressWarnings("ConstantConditions") + final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); + + builder.build(); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_importer_stand() { + // TODO:13-09-16:dmytro.dashenkov: Implement. +// @SuppressWarnings("ConstantConditions") +// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); +// builder.build(); + } @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) From 09b2231c0d70f4335bcd147435ae09ad012000f7 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 15:16:06 +0300 Subject: [PATCH 043/361] Fix deliver mock updates test. --- .../server/stand/StandFunnelShould.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index da5f2bffdfd..ef7ba6c2b08 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,6 +31,8 @@ import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -89,14 +91,16 @@ public void execute(Runnable neverCalled) { } @Test public void deliver_mock_updates_to_stand() { - final Stand stand = spy(TestStandFactory.create()); + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(id, state); final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) .build(); - final Object id = new Object(); - final Any state = Any.getDefaultInstance(); funnel.post(id, state); verify(stand).update(id, state); @@ -131,22 +135,13 @@ public void execute(Runnable command) { @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_null_stand() { + public void fail_to_initialize_with_inproper_stand() { @SuppressWarnings("ConstantConditions") final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); builder.build(); } - @SuppressWarnings("ResultOfMethodCallIgnored") - @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_importer_stand() { - // TODO:13-09-16:dmytro.dashenkov: Implement. -// @SuppressWarnings("ConstantConditions") -// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); -// builder.build(); - } - @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) public void fail_to_initialize_from_empty_builder() { @@ -162,5 +157,10 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repo() { + + } + } From 16612e210e971f95a4a66d3c5ac317faeacfe47d Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 044/361] Explicit "Given" for stand tests. --- .../java/org/spine3/server/stand/Given.java | 67 +++++++++++++++++++ .../org/spine3/server/stand/StandShould.java | 23 +------ 2 files changed, 68 insertions(+), 22 deletions(-) create mode 100644 server/src/test/java/org/spine3/server/stand/Given.java diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java new file mode 100644 index 00000000000..f1b54d0adfe --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.stand; + +import org.spine3.base.Event; +import org.spine3.base.EventContext; +import org.spine3.protobuf.AnyPacker; +import org.spine3.server.BoundedContext; +import org.spine3.server.projection.Projection; +import org.spine3.server.projection.ProjectionRepository; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.event.ProjectCreated; + +/** + * @author Dmytro Dashenkov + */ +/*package*/ class Given { + + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + private static class StandTestProjection extends Projection { + /** + * Creates a new instance. + * + * Required to be public. + * + * @param id the ID for the new instance + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestProjection(ProjectId id) { + super(id); + } + } + + /*package*/ static Event validEvent() { + return Event.newBuilder() + .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() + .setProjectId(ProjectId.newBuilder().setId("12345AD0")) + .build()) + .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .setContext(EventContext.getDefaultInstance()) + .build(); + } +} diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 98bc0609385..4cbe79e237d 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -34,11 +34,11 @@ import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; -import org.spine3.test.projection.ProjectId; import java.util.Objects; import java.util.Set; @@ -229,25 +229,4 @@ public void onEntityStateUpdate(Any newEntityState) { * - fail to initialize with improper build arguments. */ - - // ***** Inner classes used for tests. ***** - - private static class StandTestProjection extends Projection { - /** - * Creates a new instance. - * - * @param id the ID for the new instance - * @throws IllegalArgumentException if the ID is not of one of the supported types - */ - public StandTestProjection(ProjectId id) { - super(id); - } - } - - - private static class StandTestProjectionRepository extends ProjectionRepository { - protected StandTestProjectionRepository(BoundedContext boundedContext) { - super(boundedContext); - } - } } From 7666072fa933c61b33f8b1b8aedbd8840f079105 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:14:40 +0300 Subject: [PATCH 045/361] Add threading test. --- .../server/stand/StandFunnelShould.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ef7ba6c2b08..899afd85415 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -24,11 +24,16 @@ import com.google.protobuf.Any; import org.junit.Assert; import org.junit.Test; +import org.mockito.ArgumentMatchers; import org.spine3.testdata.TestStandFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; @@ -38,6 +43,7 @@ /** * @author Alex Tymchenko + * @author Dmytro Dashenkov */ public class StandFunnelShould { @@ -157,10 +163,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + + @SuppressWarnings("MethodWithMultipleLoops") @Test - public void deliver_updates_from_projection_repo() { + public void deliver_updates_through_several_threads() throws InterruptedException { + final int threadsCount = 10; - } + final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); + + final StandFunnel standFunnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final ExecutorService processes = Executors.newFixedThreadPool(threadsCount); + + + final Runnable task = new Runnable() { + @Override + public void run() { + final String threadName = Thread.currentThread().getName(); + Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + + standFunnel.post(new Object(), Any.getDefaultInstance()); + + threadInvakationRegistry.put(threadName, new Object()); + } + }; + for (int i = 0; i < threadsCount; i++) { + processes.execute(task); + } + + processes.awaitTermination(10, TimeUnit.SECONDS); + + Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + + } } From 231f7cb0b9b61a0df161138ad7a190dc560ee446 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:16:54 +0300 Subject: [PATCH 046/361] Reduce executor await time. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 899afd85415..f47fa5efb48 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -168,6 +168,7 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; + final int threadExecuteionMaxAwaitSeconds = 2; final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); @@ -197,7 +198,7 @@ public void run() { processes.execute(task); } - processes.awaitTermination(10, TimeUnit.SECONDS); + processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); From e38b6c147d19245c57c494239090a1ff4f4b25b5 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:54:11 +0300 Subject: [PATCH 047/361] Add deliver updates form projection repo test skeleton, --- .../java/org/spine3/server/stand/Given.java | 6 ++- .../server/stand/StandFunnelShould.java | 45 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index f1b54d0adfe..2f631d4995f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -23,6 +23,7 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; @@ -60,7 +61,10 @@ public StandTestProjection(ProjectId id) { .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) - .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .toBuilder() + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + + ProjectCreated.getDescriptor().getFullName()) + .build()) .setContext(EventContext.getDefaultInstance()) .build(); } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index f47fa5efb48..34e7ab2ff0f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -25,6 +25,8 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatchers; +import org.spine3.server.BoundedContext; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; import java.util.Map; @@ -40,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -163,14 +166,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repository() throws Exception { + final BoundedContext boundedContext = mock(BoundedContext.class); + final Stand stand = mock(Stand.class); + when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); + + // Init repository + final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); + + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } + @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; - final int threadExecuteionMaxAwaitSeconds = 2; + @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name + final int threadExecutionMaxAwaitSeconds = 2; - final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); final Stand stand = mock(Stand.class); doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -186,11 +219,11 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio @Override public void run() { final String threadName = Thread.currentThread().getName(); - Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + Assert.assertFalse(threadInvocationRegistry.containsKey(threadName)); standFunnel.post(new Object(), Any.getDefaultInstance()); - threadInvakationRegistry.put(threadName, new Object()); + threadInvocationRegistry.put(threadName, new Object()); } }; @@ -198,9 +231,9 @@ public void run() { processes.execute(task); } - processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); + processes.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); - Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + Assert.assertEquals(threadInvocationRegistry.size(), threadsCount); } From cc95a1317176cc89dee8c6a19f44b72b824588f9 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:57:41 +0300 Subject: [PATCH 048/361] Suppress not yet ready test. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 34e7ab2ff0f..c924469b833 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -166,7 +166,7 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - @Test + //@Test public void deliver_updates_from_projection_repository() throws Exception { final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); From 2f4ccd115b9fce42cb3f97d1cc27b70fd19dadba Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 14 Sep 2016 15:43:06 +0300 Subject: [PATCH 049/361] Simplify obtaining TypeUrl out of Target#getType(). --- server/src/main/java/org/spine3/server/QueryService.java | 7 ++----- server/src/main/java/org/spine3/server/stand/Stand.java | 3 +-- server/src/test/java/org/spine3/server/Given.java | 9 ++++++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index 1d4252263d8..f7ad288814b 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -32,7 +32,6 @@ import org.spine3.client.grpc.QueryServiceGrpc; import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; -import org.spine3.type.ClassName; import java.util.Set; @@ -64,9 +63,7 @@ public void read(Query query, StreamObserver responseObserver) { final String typeAsString = query.getTarget() .getType(); - // TODO[alex.tymchenko]: too complex - final ClassName typeClassName = ClassName.of(typeAsString); - final TypeUrl type = KnownTypes.getTypeUrl(typeClassName); + final TypeUrl type = KnownTypes.getTypeUrl(typeAsString); final BoundedContext boundedContext = typeToContextMap.get(type); try { @@ -108,7 +105,7 @@ public ImmutableMap getBoundedContextMap() { * @throws IllegalStateException if no bounded contexts were added. */ public QueryService build() throws IllegalStateException { - if(boundedContexts.isEmpty()) { + if (boundedContexts.isEmpty()) { throw new IllegalStateException("Query service must have at least one bounded context."); } this.typeToContextMap = createBoundedContextMap(); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 346e4bc3cb8..2d435de96ad 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -264,8 +264,7 @@ private ImmutableCollection internalExecute(Query query) { final Target target = query.getTarget(); final String type = target.getType(); - final ClassName typeClassName = ClassName.of(type); - final TypeUrl typeUrl = KnownTypes.getTypeUrl(typeClassName); + final TypeUrl typeUrl = KnownTypes.getTypeUrl(type); final EntityRepository repository = typeToRepositoryMap.get(typeUrl); if (repository != null) { diff --git a/server/src/test/java/org/spine3/server/Given.java b/server/src/test/java/org/spine3/server/Given.java index 93be90b22aa..70306ec6613 100644 --- a/server/src/test/java/org/spine3/server/Given.java +++ b/server/src/test/java/org/spine3/server/Given.java @@ -27,6 +27,7 @@ import org.spine3.base.Identifiers; import org.spine3.client.Target; import org.spine3.people.PersonName; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.aggregate.Aggregate; import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.aggregate.Apply; @@ -167,10 +168,12 @@ private Command() {} /* package */ static org.spine3.client.Query readAllProjects() { + final String typeName = TypeUrl.of(org.spine3.test.projection.Project.class) + .getTypeName(); final Target queryTarget = Target.newBuilder() - .setType(org.spine3.test.projection.Project.class.getName()) - .setIncludeAll(true) - .build(); + .setType(typeName) + .setIncludeAll(true) + .build(); final org.spine3.client.Query query = org.spine3.client.Query.newBuilder() .setTarget(queryTarget) From 2a379e728c45846e66875e63481c0117fc320ff5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 14 Sep 2016 15:45:47 +0300 Subject: [PATCH 050/361] Test reading the aggregate state with no data present in the stand (includeAll = true). --- .../org/spine3/server/stand/StandShould.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 98bc0609385..520d8ef3ebe 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -21,12 +21,18 @@ */ package org.spine3.server.stand; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; +import io.grpc.stub.StreamObserver; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; +import org.spine3.base.Responses; +import org.spine3.client.Query; +import org.spine3.client.QueryResponse; +import org.spine3.client.Target; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; @@ -40,17 +46,20 @@ import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.calls; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -59,6 +68,7 @@ /** * @author Alex Tymchenko */ +@SuppressWarnings("OverlyCoupledClass") //It's OK for a test. public class StandShould { // **** Positive scenarios **** @@ -187,6 +197,47 @@ public void operate_with_storage_provided_through_builder() { .write(eq(expectedAggregateStateId), recordStateMatcher(expectedRecord)); } + @Test + public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { + final StandStorage standStorageMock = mock(StandStorage.class); + + // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. + final ImmutableList emptyResultList = ImmutableList.builder().build(); + doReturn(emptyResultList).when(standStorageMock) + .readAllByType(any(TypeUrl.class)); + final Stand stand = Stand.newBuilder() + .setStorage(standStorageMock) + .build(); + assertNotNull(stand); + + final BoundedContext boundedContext = newBoundedContext(stand); + final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + stand.registerTypeSupplier(customerAggregateRepo); + + final TypeUrl customerType = TypeUrl.of(Customer.class); + final Target customerTarget = Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .build(); + final Query readAllCustomers = Query.newBuilder() + .setTarget(customerTarget) + .build(); + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); + stand.execute(readAllCustomers, responseObserver); + + assertTrue("Query has not completed successfully", responseObserver.isCompleted); + assertNull("Throwable has been caught upon query execution", responseObserver.throwable); + + final QueryResponse response = responseObserver.responseHandled; + assertEquals("Query response is not OK", Responses.ok(), response.getResponse()); + assertNotNull("Query response must not be null", response); + + final List messagesList = response.getMessagesList(); + assertNotNull("Query response has null message list", messagesList); + assertTrue("Query returned a non-empty response message list though the target was empty", messagesList + .isEmpty()); + } + private static EntityStorageRecord recordStateMatcher(final EntityStorageRecord expectedRecord) { return argThat(new ArgumentMatcher() { @Override @@ -250,4 +301,31 @@ protected StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } } + + + /** + * A {@link StreamObserver} storing the state of {@link Query} execution. + */ + private static class MemoizeQueryResponseObserver implements StreamObserver { + + private QueryResponse responseHandled; + private Throwable throwable; + private boolean isCompleted = false; + + @Override + public void onNext(QueryResponse response) { + this.responseHandled = response; + } + + @Override + public void onError(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public void onCompleted() { + this.isCompleted = true; + } + + } } From 484110224af3ceb0c7d1d5149017a69c93e53c5d Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 16:09:19 +0300 Subject: [PATCH 051/361] Finish delivery from projection repo test. --- .../java/org/spine3/server/stand/Given.java | 36 +++++++++++++++++-- .../server/stand/StandFunnelShould.java | 27 +++++++------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 2f631d4995f..c7da6d078b9 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,11 +20,16 @@ package org.spine3.server.stand; +import com.google.protobuf.Message; +import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; +import org.spine3.base.EventId; +import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.test.projection.Project; @@ -36,10 +41,20 @@ */ /*package*/ class Given { + private static final String PROJECT_UUID = Identifiers.newUuid(); + + private Given() { + } + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } + + @Override + protected ProjectId getEntityId(Message event, EventContext context) { + return ProjectId.newBuilder().setId(PROJECT_UUID).build(); + } } private static class StandTestProjection extends Projection { @@ -54,6 +69,11 @@ private static class StandTestProjection extends Projection public StandTestProjection(ProjectId id) { super(id); } + + @Subscribe + public void handle(ProjectCreated event, EventContext context) { + // Do nothing + } } /*package*/ static Event validEvent() { @@ -62,10 +82,20 @@ public StandTestProjection(ProjectId id) { .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) .toBuilder() - .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + - ProjectCreated.getDescriptor().getFullName()) + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + "/" + ProjectCreated.getDescriptor().getFullName()) .build()) - .setContext(EventContext.getDefaultInstance()) + .setContext(EventContext.newBuilder() + .setDoNotEnrich(true) + .setCommandContext(CommandContext.getDefaultInstance()) + .setEventId(EventId.newBuilder() + .setUuid(PROJECT_UUID) + .build())) .build(); } + + /*package*/ static ProjectionRepository repo(BoundedContext context) { + return new StandTestProjectionRepository(context); + } + + } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index c924469b833..4c9816159f1 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -166,22 +166,21 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - //@Test + @Test public void deliver_updates_from_projection_repository() throws Exception { - final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); - when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() - .setStand(stand) - .setExecutor(new Executor() { - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); - + final BoundedContext boundedContext = spy(BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); // Init repository - final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + final ProjectionRepository repository = Given.repo(boundedContext); stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); From ad275ef86500cd8799c7e625787f814818ad4205 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:26:06 +0300 Subject: [PATCH 052/361] Finish delivery form an aggregate repo test. --- .../java/org/spine3/server/stand/Given.java | 82 ++++++++++++++++++- .../server/stand/StandFunnelShould.java | 44 ++++++---- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index c7da6d078b9..595397ce31c 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,22 +20,34 @@ package org.spine3.server.stand; +import com.google.protobuf.Any; import com.google.protobuf.Message; +import org.spine3.base.Command; import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; +import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.Aggregate; +import org.spine3.server.aggregate.AggregateRepository; +import org.spine3.server.aggregate.Apply; +import org.spine3.server.command.Assign; import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.command.CreateProject; import org.spine3.test.projection.event.ProjectCreated; +import java.util.List; +import java.util.concurrent.Executor; + /** * @author Dmytro Dashenkov */ @@ -46,7 +58,7 @@ private Given() { } - /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } @@ -57,6 +69,48 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } + /*package*/ static class StandTestAggregateRepository extends AggregateRepository { + + /** + * Creates a new repository instance. + * + * @param boundedContext the bounded context to which this repository belongs + */ + /*package*/ StandTestAggregateRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + /*package*/ static class TestFailure extends FailureThrowable { + + /*package*/ TestFailure() { + super(/*generatedMessage=*/null); + } + } + + private static class StandTestAggregate extends Aggregate { + + /** + * Creates a new aggregate instance. + * + * @param id the ID for the new aggregate + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestAggregate(ProjectId id) { + super(id); + } + + @Assign + public List handle(CreateProject createProject, CommandContext context) { + return null; + } + + @Apply + public void handle(ProjectCreated event) { + // Do nothing + } + } + private static class StandTestProjection extends Projection { /** * Creates a new instance. @@ -76,7 +130,7 @@ public void handle(ProjectCreated event, EventContext context) { } } - /*package*/ static Event validEvent() { + /*package*/ static Event validEvent() { return Event.newBuilder() .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) @@ -93,9 +147,31 @@ public void handle(ProjectCreated event, EventContext context) { .build(); } - /*package*/ static ProjectionRepository repo(BoundedContext context) { + /*package*/ static Command validCommand() { + return Command.newBuilder() + .setMessage(AnyPacker.pack(CreateProject.getDefaultInstance())) + .setContext(CommandContext.getDefaultInstance()) + .build(); + } + + /*package*/ static ProjectionRepository projectionRepo(BoundedContext context) { return new StandTestProjectionRepository(context); } + /*package*/ static AggregateRepository aggregateRepo(BoundedContext context) { + return new StandTestAggregateRepository(context); + } + /*package*/ static BoundedContext boundedContext(Stand stand) { + return BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build(); + } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 4c9816159f1..ad82932db72 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -161,28 +162,18 @@ public void fail_to_initialize_from_empty_builder() { // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** /** - * - Deliver updates from projection repo on update; - * - deliver updates from aggregate repo on update; + * - Deliver updates from projection projectionRepo on update; + * - deliver updates from aggregate projectionRepo on update; * - deliver the updates from several projection and aggregate repositories. */ @Test - public void deliver_updates_from_projection_repository() throws Exception { + public void deliver_updates_from_projection_repository() { final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(BoundedContext.newBuilder() - .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); // Init repository - final ProjectionRepository repository = Given.repo(boundedContext); + final ProjectionRepository repository = Given.projectionRepo(boundedContext); - stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); repository.setOnline(); @@ -194,6 +185,29 @@ public void execute(Runnable command) { verify(stand).update(ArgumentMatchers.any(), any(Any.class)); } + @Test + public void deliver_updates_form_aggregate_repository() { + final Stand stand = mock(Stand.class); + + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + // Init repository + final AggregateRepository repository = Given.aggregateRepo(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + + // Dispatch an update from projection projectionRepo + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } @SuppressWarnings("MethodWithMultipleLoops") @Test From c280b88a2f7aa63bfc9d603d3ab5f93f7e263914 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:27:38 +0300 Subject: [PATCH 053/361] Delete redundant "TestFailure" declaration. --- server/src/test/java/org/spine3/server/stand/Given.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 595397ce31c..18b75d76805 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -27,7 +27,6 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; -import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; @@ -81,13 +80,6 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } - /*package*/ static class TestFailure extends FailureThrowable { - - /*package*/ TestFailure() { - super(/*generatedMessage=*/null); - } - } - private static class StandTestAggregate extends Aggregate { /** From 04a211a29e0667546f483ad479740eecc1452f3a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:40:54 +0300 Subject: [PATCH 054/361] Extract common test operations. --- .../server/stand/StandFunnelShould.java | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ad82932db72..3cac4e4acb7 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -39,6 +39,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import static com.google.common.base.Preconditions.checkNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -169,41 +170,53 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final ProjectionRepository repository = Given.projectionRepo(boundedContext); - - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }); } @Test public void deliver_updates_form_aggregate_repository() { - final Stand stand = mock(Stand.class); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final AggregateRepository repository = Given.aggregateRepo(context); + + repository.initStorage(InMemoryStorageFactory.getInstance()); + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + } + }); + } + + private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final AggregateRepository repository = Given.aggregateRepo(boundedContext); - - stand.registerTypeSupplier(repository); - repository.initStorage(InMemoryStorageFactory.getInstance()); - - // Dispatch an update from projection projectionRepo - try { - repository.dispatch(Given.validCommand()); - } catch (IllegalStateException e) { - // Handle null event dispatch after command handling. - Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); } + // Was called ONCE verify(boundedContext).getStandFunnel(); verify(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -250,4 +263,8 @@ public void run() { } + private interface BoundedContextAction { + void perform(BoundedContext context); + } + } From b19ff2bdac1c3b2cd87e26acdd0158044e48f8c3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 14 Sep 2016 17:42:52 +0300 Subject: [PATCH 055/361] Fix and optimise Stand->StandStorage interaction; Test reading the aggregate state by ID. --- .../java/org/spine3/server/stand/Stand.java | 43 ++++++--- .../org/spine3/server/stand/StandShould.java | 94 ++++++++++++++++--- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 2d435de96ad..6f15d357b64 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -51,7 +51,6 @@ import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.memory.InMemoryStandStorage; -import org.spine3.type.ClassName; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; @@ -298,15 +297,16 @@ private ImmutableCollection fetchFromStandStorage(Target ta final EntityIdFilter idFilter = filters.getIdFilter(); final Collection stateIds = Collections2.transform(idFilter.getIdsList(), aggregateStateIdTransformer(typeUrl)); - final Iterable bulkReadResults = storage.readBulk(stateIds); - result = FluentIterable.from(bulkReadResults) - .filter(new Predicate() { - @Override - public boolean apply(@Nullable EntityStorageRecord input) { - return input != null; - } - }) - .toList(); + if (stateIds.size() == 1) { + // no need to trigger bulk reading. + // may be more effective, as bulk reading implies additional time and performance expenses. + final AggregateStateId singleId = stateIds.iterator() + .next(); + final EntityStorageRecord singleResult = storage.read(singleId); + result = ImmutableList.of(singleResult); + } else { + result = handleBulkRead(stateIds); + } } else { result = ImmutableList.of(); } @@ -315,6 +315,20 @@ public boolean apply(@Nullable EntityStorageRecord input) { return result; } + private ImmutableCollection handleBulkRead(Collection stateIds) { + ImmutableCollection result; + final Iterable bulkReadResults = storage.readBulk(stateIds); + result = FluentIterable.from(bulkReadResults) + .filter(new Predicate() { + @Override + public boolean apply(@Nullable EntityStorageRecord input) { + return input != null; + } + }) + .toList(); + return result; + } + private static Function aggregateStateIdTransformer(final TypeUrl typeUrl) { return new Function() { @Nullable @@ -322,7 +336,9 @@ private static Function aggregateStateIdTransformer( public AggregateStateId apply(@Nullable EntityId input) { checkNotNull(input); - final AggregateStateId stateId = AggregateStateId.of(input.getId(), typeUrl); + final Any rawId = input.getId(); + final Message unpackedId = AnyPacker.unpack(rawId); + final AggregateStateId stateId = AggregateStateId.of(unpackedId, typeUrl); return stateId; } }; @@ -349,9 +365,8 @@ private static void feedEntitiesToBuilder(ImmutableSet.Builder resultBuilde private static void feedStateRecordsToBuilder(ImmutableSet.Builder resultBuilder, ImmutableCollection all) { for (EntityStorageRecord record : all) { - final Message state = record.getState(); - final Any packedState = AnyPacker.pack(state); - resultBuilder.add(packedState); + final Any state = record.getState(); + resultBuilder.add(state); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 520d8ef3ebe..7880fe510a9 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -25,11 +25,15 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import org.spine3.base.Responses; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; import org.spine3.client.QueryResponse; import org.spine3.client.Target; @@ -59,10 +63,10 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.calls; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; /** @@ -199,20 +203,13 @@ public void operate_with_storage_provided_through_builder() { @Test public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { - final StandStorage standStorageMock = mock(StandStorage.class); + final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. final ImmutableList emptyResultList = ImmutableList.builder().build(); - doReturn(emptyResultList).when(standStorageMock) - .readAllByType(any(TypeUrl.class)); - final Stand stand = Stand.newBuilder() - .setStorage(standStorageMock) - .build(); - assertNotNull(stand); + when(standStorageMock.readAllByType(any(TypeUrl.class))).thenReturn(emptyResultList); - final BoundedContext boundedContext = newBoundedContext(stand); - final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); - stand.registerTypeSupplier(customerAggregateRepo); + final Stand stand = prepareStandWithAggregateRepo(standStorageMock); final TypeUrl customerType = TypeUrl.of(Customer.class); final Target customerTarget = Target.newBuilder() @@ -222,9 +219,64 @@ public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { final Query readAllCustomers = Query.newBuilder() .setTarget(customerTarget) .build(); + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); stand.execute(readAllCustomers, responseObserver); + final List messageList = checkAndGetMessageList(responseObserver); + assertTrue("Query returned a non-empty response message list though the target was empty", messageList + .isEmpty()); + } + + @Test + public void return_single_result_for_aggregate_state_read_by_id() { + + // Define the types and values used as a test data. + final TypeUrl customerType = TypeUrl.of(Customer.class); + final Customer customer = Customer.getDefaultInstance(); + final Any customerState = AnyPacker.pack(customer); + final CustomerId customerId = CustomerId.newBuilder() + .setNumber(42) + .build(); + final AggregateStateId stateId = AggregateStateId.of(customerId, customerType); + + + // Prepare the stand and its mock storage to act. + final StandStorage standStorageMock = mock(StandStorage.class); + final EntityStorageRecord entityStorageRecord = EntityStorageRecord.newBuilder() + .setState(customerState) + .build(); + when(standStorageMock.read(eq(stateId))).thenReturn(entityStorageRecord); + final Stand stand = prepareStandWithAggregateRepo(standStorageMock); + + // Trigger the update. + stand.update(customerId, customerState); + + // Now we are ready to query. + final EntityIdFilter idFilter = EntityIdFilter.newBuilder() + .addIds(EntityId.newBuilder() + .setId(AnyPacker.pack(customerId))) + .build(); + final Target customerTarget = Target.newBuilder() + .setFilters(EntityFilters.newBuilder() + .setIdFilter(idFilter)) + .setType(customerType.getTypeName()) + .build(); + final Query readSingleCustomer = Query.newBuilder() + .setTarget(customerTarget) + .build(); + + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); + stand.execute(readSingleCustomer, responseObserver); + + final List messageList = checkAndGetMessageList(responseObserver); + assertEquals(1, messageList.size()); + final Any singleRecord = messageList.get(0); + final Message unpackedSingleResult = AnyPacker.unpack(singleRecord); + assertEquals(customer, unpackedSingleResult); + } + + private static List checkAndGetMessageList(MemoizeQueryResponseObserver responseObserver) { assertTrue("Query has not completed successfully", responseObserver.isCompleted); assertNull("Throwable has been caught upon query execution", responseObserver.throwable); @@ -232,10 +284,22 @@ public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { assertEquals("Query response is not OK", Responses.ok(), response.getResponse()); assertNotNull("Query response must not be null", response); - final List messagesList = response.getMessagesList(); - assertNotNull("Query response has null message list", messagesList); - assertTrue("Query returned a non-empty response message list though the target was empty", messagesList - .isEmpty()); + final List messageList = response.getMessagesList(); + assertNotNull("Query response has null message list", messageList); + return messageList; + } + + + private static Stand prepareStandWithAggregateRepo(StandStorage standStorageMock) { + final Stand stand = Stand.newBuilder() + .setStorage(standStorageMock) + .build(); + assertNotNull(stand); + + final BoundedContext boundedContext = newBoundedContext(stand); + final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + stand.registerTypeSupplier(customerAggregateRepo); + return stand; } private static EntityStorageRecord recordStateMatcher(final EntityStorageRecord expectedRecord) { From 0ef157de416bc945e5a04758bc3c9321ba5fe36a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:21:34 +0300 Subject: [PATCH 056/361] Refactor and add delivery tests for several repositories successively and in parallel. --- .../java/org/spine3/server/stand/Given.java | 29 ++++-- .../server/stand/StandFunnelShould.java | 89 +++++++++++++------ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 18b75d76805..ccad28106a2 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -46,12 +46,16 @@ import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** * @author Dmytro Dashenkov */ /*package*/ class Given { + /*package*/ static final int THREADS_COUNT_IN_POOL_EXECUTOR = 10; + /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; + private static final String PROJECT_UUID = Identifiers.newUuid(); private Given() { @@ -154,16 +158,23 @@ public void handle(ProjectCreated event, EventContext context) { return new StandTestAggregateRepository(context); } - /*package*/ static BoundedContext boundedContext(Stand stand) { + /*package*/ static BoundedContext boundedContext(Stand stand, int concurrentThreads) { + final Executor executor = concurrentThreads > 0 ? Executors.newFixedThreadPool(concurrentThreads) : + new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }; + + return boundedContextBuilder(stand) + .setStandFunnelExecutor(executor) + .build(); + } + + private static BoundedContext.Builder boundedContextBuilder(Stand stand) { return BoundedContext.newBuilder() .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build(); + .setStorageFactory(InMemoryStorageFactory.getInstance()); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 3cac4e4acb7..486697b153e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,7 +31,9 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; +import java.security.SecureRandom; import java.util.Map; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** @@ -170,24 +173,55 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - deliverUpdatesTest(new BoundedContextAction() { - @Override - public void perform(BoundedContext context) { - // Init repository - final ProjectionRepository repository = Given.projectionRepo(context); + deliverUpdates(false, projectionRepositoryDispatch()); + } - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + @Test + public void deliver_updates_form_aggregate_repository() { + deliverUpdates(false, aggregateRepositoryDispatch()); + } - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); - } - }); + @Test + public void deliver_updates_from_several_repositories_in_single_thread() { + deliverUpdates(false, getSeveralRepositoryDispatchCalls()); } @Test - public void deliver_updates_form_aggregate_repository() { - deliverUpdatesTest(new BoundedContextAction() { + public void deliver_updates_from_several_repositories_in_multiple_threads() { + deliverUpdates(true, getSeveralRepositoryDispatchCalls()); + } + + private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { + final BoundedContextAction[] result = new BoundedContextAction[Given.SEVERAL]; + final Random random = new SecureRandom(); + + for (int i = 0; i < result.length; i++) { + result[i] = random.nextBoolean() ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); + } + + return result; + } + + private static void deliverUpdates(boolean isMultiThread, BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); + final BoundedContext boundedContext = spy(Given.boundedContext(stand, + isMultiThread ? + Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); + } + + + // Was called ONCE + verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); + verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); + } + + private static BoundedContextAction aggregateRepositoryDispatch() { + return new BoundedContextAction() { @Override public void perform(BoundedContext context) { // Init repository @@ -201,31 +235,32 @@ public void perform(BoundedContext context) { // Handle null event dispatch after command handling. Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); } - } - }); + }; } - private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { - checkNotNull(dispatchActions); + private static BoundedContextAction projectionRepositoryDispatch() { + return new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - for (BoundedContextAction dispatchAction : dispatchActions) { - dispatchAction.perform(boundedContext); - } + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }; + } - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); - } @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { - final int threadsCount = 10; + final int threadsCount = Given.THREADS_COUNT_IN_POOL_EXECUTOR; @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name final int threadExecutionMaxAwaitSeconds = 2; From cfa0535f6842a47fa25a5fe7a979398bfe929eec Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:10:41 +0300 Subject: [PATCH 057/361] Add creation test covering various Builder params. --- .../server/stand/StandFunnelShould.java | 36 ++++++++++++++++++- .../org/spine3/testdata/TestStandFactory.java | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 9e42f37e68e..5eb8fb42bdf 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -27,6 +27,8 @@ import org.spine3.testdata.TestStandFactory; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; @@ -40,7 +42,6 @@ public class StandFunnelShould { // **** Positive scenarios (unit) **** /** - * - Initialize properly with various Builder options; * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. */ @@ -53,6 +54,39 @@ public void initialize_properly_with_stand_only() { Assert.assertNotNull(standFunnel); } + @Test + public void initialize_properly_with_various_builder_options() { + final Stand stand = TestStandFactory.create(); + final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return Thread.currentThread(); + } + }); + + final StandFunnel blockingFunnel = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(executor) + .build(); + Assert.assertNotNull(blockingFunnel); + + final StandFunnel funnelForBusyStand = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(Executors.newSingleThreadExecutor()) + .build(); + Assert.assertNotNull(funnelForBusyStand); + + + final StandFunnel emptyExecutorFunnel = StandFunnel.newBuilder() + .setStand(TestStandFactory.create()) + .setExecutor(new Executor() { + @Override + public void execute(Runnable neverCalled) { } + }) + .build(); + Assert.assertNotNull(emptyExecutorFunnel); + } + @Test public void use_executor_from_builder() { diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index d54df4fae6d..474a6226b30 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -22,13 +22,13 @@ package org.spine3.testdata; import org.spine3.server.stand.Stand; -import org.spine3.server.storage.memory.InMemoryStandStorage; /** * Creates stands for tests. * * @author Alex Tymchenko */ +@SuppressWarnings("UtilityClass") public class TestStandFactory { private TestStandFactory() {} From 000f360e5ef50413b3309c35b604704e889da921 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:11:56 +0300 Subject: [PATCH 058/361] Bump gradle version, --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 58e7b927f5d..6d19c1542b6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip From b33eca7ced546c891309e2632adc651129da3c57 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:37:55 +0300 Subject: [PATCH 059/361] Add some more tests (need to be processed). --- .../org/spine3/server/stand/StandFunnel.java | 2 ++ .../server/stand/StandFunnelShould.java | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 9e3cc659ce0..44997adbdf8 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -109,6 +109,7 @@ public Stand getStand() { *

The value must not be null. * *

If this method is not used, a default value will be used. + * // TODO:13-09-16:dmytro.dashenkov: Correct docs. No default value for Stand is used, null value leads to a fail instead. * * @param stand the instance of {@link Stand}. * @return {@code this} instance of {@code Builder} @@ -122,6 +123,7 @@ public Executor getExecutor() { return executor; } + // TODO:13-09-16:dmytro.dashenkov: Complete docs. Here is the place where a default value is used in case of not using the method. /** * Set the {@code Executor} instance for this {@code StandFunnel}. * diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 5eb8fb42bdf..da5f2bffdfd 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -87,6 +87,21 @@ public void execute(Runnable neverCalled) { } Assert.assertNotNull(emptyExecutorFunnel); } + @Test + public void deliver_mock_updates_to_stand() { + final Stand stand = spy(TestStandFactory.create()); + + final StandFunnel funnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + funnel.post(id, state); + + verify(stand).update(id, state); + } + @Test public void use_executor_from_builder() { @@ -114,9 +129,23 @@ public void execute(Runnable command) { // **** Negative scenarios (unit) **** - /** - * - Fail to initialise with improper stand. - */ + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_null_stand() { + @SuppressWarnings("ConstantConditions") + final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); + + builder.build(); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_importer_stand() { + // TODO:13-09-16:dmytro.dashenkov: Implement. +// @SuppressWarnings("ConstantConditions") +// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); +// builder.build(); + } @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) From 88d48a7a02495a36140bb39624190d3b95cc4fee Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 15:16:06 +0300 Subject: [PATCH 060/361] Fix deliver mock updates test. --- .../server/stand/StandFunnelShould.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index da5f2bffdfd..ef7ba6c2b08 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,6 +31,8 @@ import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -89,14 +91,16 @@ public void execute(Runnable neverCalled) { } @Test public void deliver_mock_updates_to_stand() { - final Stand stand = spy(TestStandFactory.create()); + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(id, state); final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) .build(); - final Object id = new Object(); - final Any state = Any.getDefaultInstance(); funnel.post(id, state); verify(stand).update(id, state); @@ -131,22 +135,13 @@ public void execute(Runnable command) { @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_null_stand() { + public void fail_to_initialize_with_inproper_stand() { @SuppressWarnings("ConstantConditions") final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); builder.build(); } - @SuppressWarnings("ResultOfMethodCallIgnored") - @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_importer_stand() { - // TODO:13-09-16:dmytro.dashenkov: Implement. -// @SuppressWarnings("ConstantConditions") -// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); -// builder.build(); - } - @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) public void fail_to_initialize_from_empty_builder() { @@ -162,5 +157,10 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repo() { + + } + } From 1efce72641cea63b3513941dd0d21f6ac9b42f1a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 061/361] Explicit "Given" for stand tests. --- .../java/org/spine3/server/stand/Given.java | 67 +++++++++++++++++++ .../org/spine3/server/stand/StandShould.java | 21 +----- 2 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 server/src/test/java/org/spine3/server/stand/Given.java diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java new file mode 100644 index 00000000000..f1b54d0adfe --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.stand; + +import org.spine3.base.Event; +import org.spine3.base.EventContext; +import org.spine3.protobuf.AnyPacker; +import org.spine3.server.BoundedContext; +import org.spine3.server.projection.Projection; +import org.spine3.server.projection.ProjectionRepository; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.event.ProjectCreated; + +/** + * @author Dmytro Dashenkov + */ +/*package*/ class Given { + + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + private static class StandTestProjection extends Projection { + /** + * Creates a new instance. + * + * Required to be public. + * + * @param id the ID for the new instance + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestProjection(ProjectId id) { + super(id); + } + } + + /*package*/ static Event validEvent() { + return Event.newBuilder() + .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() + .setProjectId(ProjectId.newBuilder().setId("12345AD0")) + .build()) + .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .setContext(EventContext.getDefaultInstance()) + .build(); + } +} diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 7880fe510a9..5884a55cb1f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -44,6 +44,7 @@ import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; @@ -347,26 +348,6 @@ public void onEntityStateUpdate(Any newEntityState) { // ***** Inner classes used for tests. ***** - private static class StandTestProjection extends Projection { - /** - * Creates a new instance. - * - * @param id the ID for the new instance - * @throws IllegalArgumentException if the ID is not of one of the supported types - */ - public StandTestProjection(ProjectId id) { - super(id); - } - } - - - private static class StandTestProjectionRepository extends ProjectionRepository { - protected StandTestProjectionRepository(BoundedContext boundedContext) { - super(boundedContext); - } - } - - /** * A {@link StreamObserver} storing the state of {@link Query} execution. */ From cddf64c4ea3f100732590bc349d9a3bd7abcd53a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:14:40 +0300 Subject: [PATCH 062/361] Add threading test. --- .../server/stand/StandFunnelShould.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ef7ba6c2b08..899afd85415 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -24,11 +24,16 @@ import com.google.protobuf.Any; import org.junit.Assert; import org.junit.Test; +import org.mockito.ArgumentMatchers; import org.spine3.testdata.TestStandFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; @@ -38,6 +43,7 @@ /** * @author Alex Tymchenko + * @author Dmytro Dashenkov */ public class StandFunnelShould { @@ -157,10 +163,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + + @SuppressWarnings("MethodWithMultipleLoops") @Test - public void deliver_updates_from_projection_repo() { + public void deliver_updates_through_several_threads() throws InterruptedException { + final int threadsCount = 10; - } + final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); + + final StandFunnel standFunnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final ExecutorService processes = Executors.newFixedThreadPool(threadsCount); + + + final Runnable task = new Runnable() { + @Override + public void run() { + final String threadName = Thread.currentThread().getName(); + Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + + standFunnel.post(new Object(), Any.getDefaultInstance()); + + threadInvakationRegistry.put(threadName, new Object()); + } + }; + for (int i = 0; i < threadsCount; i++) { + processes.execute(task); + } + + processes.awaitTermination(10, TimeUnit.SECONDS); + + Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + + } } From 935f7a922f3ef9630172c752434df1e50565e30c Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:16:54 +0300 Subject: [PATCH 063/361] Reduce executor await time. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 899afd85415..f47fa5efb48 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -168,6 +168,7 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; + final int threadExecuteionMaxAwaitSeconds = 2; final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); @@ -197,7 +198,7 @@ public void run() { processes.execute(task); } - processes.awaitTermination(10, TimeUnit.SECONDS); + processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); From 3f90a6f1a10cd33287c6c0eea3eac389bf26c2c7 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:54:11 +0300 Subject: [PATCH 064/361] Add deliver updates form projection repo test skeleton, --- .../java/org/spine3/server/stand/Given.java | 6 ++- .../server/stand/StandFunnelShould.java | 45 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index f1b54d0adfe..2f631d4995f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -23,6 +23,7 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; @@ -60,7 +61,10 @@ public StandTestProjection(ProjectId id) { .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) - .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .toBuilder() + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + + ProjectCreated.getDescriptor().getFullName()) + .build()) .setContext(EventContext.getDefaultInstance()) .build(); } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index f47fa5efb48..34e7ab2ff0f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -25,6 +25,8 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatchers; +import org.spine3.server.BoundedContext; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; import java.util.Map; @@ -40,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -163,14 +166,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repository() throws Exception { + final BoundedContext boundedContext = mock(BoundedContext.class); + final Stand stand = mock(Stand.class); + when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); + + // Init repository + final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); + + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } + @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; - final int threadExecuteionMaxAwaitSeconds = 2; + @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name + final int threadExecutionMaxAwaitSeconds = 2; - final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); final Stand stand = mock(Stand.class); doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -186,11 +219,11 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio @Override public void run() { final String threadName = Thread.currentThread().getName(); - Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + Assert.assertFalse(threadInvocationRegistry.containsKey(threadName)); standFunnel.post(new Object(), Any.getDefaultInstance()); - threadInvakationRegistry.put(threadName, new Object()); + threadInvocationRegistry.put(threadName, new Object()); } }; @@ -198,9 +231,9 @@ public void run() { processes.execute(task); } - processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); + processes.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); - Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + Assert.assertEquals(threadInvocationRegistry.size(), threadsCount); } From 68c908bf6e4b32f19caa3050fa03c0a95f37d5cd Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:57:41 +0300 Subject: [PATCH 065/361] Suppress not yet ready test. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 34e7ab2ff0f..c924469b833 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -166,7 +166,7 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - @Test + //@Test public void deliver_updates_from_projection_repository() throws Exception { final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); From 3b6601c8b9faca2e6b5bb8119648b0c6950f0a29 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 16:09:19 +0300 Subject: [PATCH 066/361] Finish delivery from projection repo test. --- .../java/org/spine3/server/stand/Given.java | 36 +++++++++++++++++-- .../server/stand/StandFunnelShould.java | 27 +++++++------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 2f631d4995f..c7da6d078b9 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,11 +20,16 @@ package org.spine3.server.stand; +import com.google.protobuf.Message; +import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; +import org.spine3.base.EventId; +import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.test.projection.Project; @@ -36,10 +41,20 @@ */ /*package*/ class Given { + private static final String PROJECT_UUID = Identifiers.newUuid(); + + private Given() { + } + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } + + @Override + protected ProjectId getEntityId(Message event, EventContext context) { + return ProjectId.newBuilder().setId(PROJECT_UUID).build(); + } } private static class StandTestProjection extends Projection { @@ -54,6 +69,11 @@ private static class StandTestProjection extends Projection public StandTestProjection(ProjectId id) { super(id); } + + @Subscribe + public void handle(ProjectCreated event, EventContext context) { + // Do nothing + } } /*package*/ static Event validEvent() { @@ -62,10 +82,20 @@ public StandTestProjection(ProjectId id) { .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) .toBuilder() - .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + - ProjectCreated.getDescriptor().getFullName()) + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + "/" + ProjectCreated.getDescriptor().getFullName()) .build()) - .setContext(EventContext.getDefaultInstance()) + .setContext(EventContext.newBuilder() + .setDoNotEnrich(true) + .setCommandContext(CommandContext.getDefaultInstance()) + .setEventId(EventId.newBuilder() + .setUuid(PROJECT_UUID) + .build())) .build(); } + + /*package*/ static ProjectionRepository repo(BoundedContext context) { + return new StandTestProjectionRepository(context); + } + + } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index c924469b833..4c9816159f1 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -166,22 +166,21 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - //@Test + @Test public void deliver_updates_from_projection_repository() throws Exception { - final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); - when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() - .setStand(stand) - .setExecutor(new Executor() { - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); - + final BoundedContext boundedContext = spy(BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); // Init repository - final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + final ProjectionRepository repository = Given.repo(boundedContext); stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); From cba94e5f1198ed6836b51b2419e91adb531394e1 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:26:06 +0300 Subject: [PATCH 067/361] Finish delivery form an aggregate repo test. --- .../java/org/spine3/server/stand/Given.java | 82 ++++++++++++++++++- .../server/stand/StandFunnelShould.java | 44 ++++++---- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index c7da6d078b9..595397ce31c 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,22 +20,34 @@ package org.spine3.server.stand; +import com.google.protobuf.Any; import com.google.protobuf.Message; +import org.spine3.base.Command; import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; +import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.Aggregate; +import org.spine3.server.aggregate.AggregateRepository; +import org.spine3.server.aggregate.Apply; +import org.spine3.server.command.Assign; import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.command.CreateProject; import org.spine3.test.projection.event.ProjectCreated; +import java.util.List; +import java.util.concurrent.Executor; + /** * @author Dmytro Dashenkov */ @@ -46,7 +58,7 @@ private Given() { } - /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } @@ -57,6 +69,48 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } + /*package*/ static class StandTestAggregateRepository extends AggregateRepository { + + /** + * Creates a new repository instance. + * + * @param boundedContext the bounded context to which this repository belongs + */ + /*package*/ StandTestAggregateRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + /*package*/ static class TestFailure extends FailureThrowable { + + /*package*/ TestFailure() { + super(/*generatedMessage=*/null); + } + } + + private static class StandTestAggregate extends Aggregate { + + /** + * Creates a new aggregate instance. + * + * @param id the ID for the new aggregate + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestAggregate(ProjectId id) { + super(id); + } + + @Assign + public List handle(CreateProject createProject, CommandContext context) { + return null; + } + + @Apply + public void handle(ProjectCreated event) { + // Do nothing + } + } + private static class StandTestProjection extends Projection { /** * Creates a new instance. @@ -76,7 +130,7 @@ public void handle(ProjectCreated event, EventContext context) { } } - /*package*/ static Event validEvent() { + /*package*/ static Event validEvent() { return Event.newBuilder() .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) @@ -93,9 +147,31 @@ public void handle(ProjectCreated event, EventContext context) { .build(); } - /*package*/ static ProjectionRepository repo(BoundedContext context) { + /*package*/ static Command validCommand() { + return Command.newBuilder() + .setMessage(AnyPacker.pack(CreateProject.getDefaultInstance())) + .setContext(CommandContext.getDefaultInstance()) + .build(); + } + + /*package*/ static ProjectionRepository projectionRepo(BoundedContext context) { return new StandTestProjectionRepository(context); } + /*package*/ static AggregateRepository aggregateRepo(BoundedContext context) { + return new StandTestAggregateRepository(context); + } + /*package*/ static BoundedContext boundedContext(Stand stand) { + return BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build(); + } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 4c9816159f1..ad82932db72 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -161,28 +162,18 @@ public void fail_to_initialize_from_empty_builder() { // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** /** - * - Deliver updates from projection repo on update; - * - deliver updates from aggregate repo on update; + * - Deliver updates from projection projectionRepo on update; + * - deliver updates from aggregate projectionRepo on update; * - deliver the updates from several projection and aggregate repositories. */ @Test - public void deliver_updates_from_projection_repository() throws Exception { + public void deliver_updates_from_projection_repository() { final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(BoundedContext.newBuilder() - .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); // Init repository - final ProjectionRepository repository = Given.repo(boundedContext); + final ProjectionRepository repository = Given.projectionRepo(boundedContext); - stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); repository.setOnline(); @@ -194,6 +185,29 @@ public void execute(Runnable command) { verify(stand).update(ArgumentMatchers.any(), any(Any.class)); } + @Test + public void deliver_updates_form_aggregate_repository() { + final Stand stand = mock(Stand.class); + + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + // Init repository + final AggregateRepository repository = Given.aggregateRepo(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + + // Dispatch an update from projection projectionRepo + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } @SuppressWarnings("MethodWithMultipleLoops") @Test From 7c49973b3bdb84b15a60cd04730d625e1ed247a9 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:27:38 +0300 Subject: [PATCH 068/361] Delete redundant "TestFailure" declaration. --- server/src/test/java/org/spine3/server/stand/Given.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 595397ce31c..18b75d76805 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -27,7 +27,6 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; -import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; @@ -81,13 +80,6 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } - /*package*/ static class TestFailure extends FailureThrowable { - - /*package*/ TestFailure() { - super(/*generatedMessage=*/null); - } - } - private static class StandTestAggregate extends Aggregate { /** From 85269778da7fdc66e95bf57c56a8a0f9fc960284 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:40:54 +0300 Subject: [PATCH 069/361] Extract common test operations. --- .../server/stand/StandFunnelShould.java | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ad82932db72..3cac4e4acb7 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -39,6 +39,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import static com.google.common.base.Preconditions.checkNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -169,41 +170,53 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final ProjectionRepository repository = Given.projectionRepo(boundedContext); - - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }); } @Test public void deliver_updates_form_aggregate_repository() { - final Stand stand = mock(Stand.class); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final AggregateRepository repository = Given.aggregateRepo(context); + + repository.initStorage(InMemoryStorageFactory.getInstance()); + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + } + }); + } + + private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final AggregateRepository repository = Given.aggregateRepo(boundedContext); - - stand.registerTypeSupplier(repository); - repository.initStorage(InMemoryStorageFactory.getInstance()); - - // Dispatch an update from projection projectionRepo - try { - repository.dispatch(Given.validCommand()); - } catch (IllegalStateException e) { - // Handle null event dispatch after command handling. - Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); } + // Was called ONCE verify(boundedContext).getStandFunnel(); verify(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -250,4 +263,8 @@ public void run() { } + private interface BoundedContextAction { + void perform(BoundedContext context); + } + } From f553109be061eede2059550067d104595442c579 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:21:34 +0300 Subject: [PATCH 070/361] Refactor and add delivery tests for several repositories successively and in parallel. --- .../java/org/spine3/server/stand/Given.java | 29 ++++-- .../server/stand/StandFunnelShould.java | 89 +++++++++++++------ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 18b75d76805..ccad28106a2 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -46,12 +46,16 @@ import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** * @author Dmytro Dashenkov */ /*package*/ class Given { + /*package*/ static final int THREADS_COUNT_IN_POOL_EXECUTOR = 10; + /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; + private static final String PROJECT_UUID = Identifiers.newUuid(); private Given() { @@ -154,16 +158,23 @@ public void handle(ProjectCreated event, EventContext context) { return new StandTestAggregateRepository(context); } - /*package*/ static BoundedContext boundedContext(Stand stand) { + /*package*/ static BoundedContext boundedContext(Stand stand, int concurrentThreads) { + final Executor executor = concurrentThreads > 0 ? Executors.newFixedThreadPool(concurrentThreads) : + new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }; + + return boundedContextBuilder(stand) + .setStandFunnelExecutor(executor) + .build(); + } + + private static BoundedContext.Builder boundedContextBuilder(Stand stand) { return BoundedContext.newBuilder() .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build(); + .setStorageFactory(InMemoryStorageFactory.getInstance()); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 3cac4e4acb7..486697b153e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,7 +31,9 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; +import java.security.SecureRandom; import java.util.Map; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** @@ -170,24 +173,55 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - deliverUpdatesTest(new BoundedContextAction() { - @Override - public void perform(BoundedContext context) { - // Init repository - final ProjectionRepository repository = Given.projectionRepo(context); + deliverUpdates(false, projectionRepositoryDispatch()); + } - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + @Test + public void deliver_updates_form_aggregate_repository() { + deliverUpdates(false, aggregateRepositoryDispatch()); + } - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); - } - }); + @Test + public void deliver_updates_from_several_repositories_in_single_thread() { + deliverUpdates(false, getSeveralRepositoryDispatchCalls()); } @Test - public void deliver_updates_form_aggregate_repository() { - deliverUpdatesTest(new BoundedContextAction() { + public void deliver_updates_from_several_repositories_in_multiple_threads() { + deliverUpdates(true, getSeveralRepositoryDispatchCalls()); + } + + private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { + final BoundedContextAction[] result = new BoundedContextAction[Given.SEVERAL]; + final Random random = new SecureRandom(); + + for (int i = 0; i < result.length; i++) { + result[i] = random.nextBoolean() ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); + } + + return result; + } + + private static void deliverUpdates(boolean isMultiThread, BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); + final BoundedContext boundedContext = spy(Given.boundedContext(stand, + isMultiThread ? + Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); + } + + + // Was called ONCE + verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); + verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); + } + + private static BoundedContextAction aggregateRepositoryDispatch() { + return new BoundedContextAction() { @Override public void perform(BoundedContext context) { // Init repository @@ -201,31 +235,32 @@ public void perform(BoundedContext context) { // Handle null event dispatch after command handling. Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); } - } - }); + }; } - private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { - checkNotNull(dispatchActions); + private static BoundedContextAction projectionRepositoryDispatch() { + return new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - for (BoundedContextAction dispatchAction : dispatchActions) { - dispatchAction.perform(boundedContext); - } + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }; + } - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); - } @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { - final int threadsCount = 10; + final int threadsCount = Given.THREADS_COUNT_IN_POOL_EXECUTOR; @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name final int threadExecutionMaxAwaitSeconds = 2; From 7a7f6a9cd985fb880b60176300fa2ffedd12580e Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:47:07 +0300 Subject: [PATCH 071/361] Fix failing build by adding "await" for a concurrent test. --- .../java/org/spine3/server/stand/Given.java | 1 + .../spine3/server/stand/StandFunnelShould.java | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index ccad28106a2..9e6d31d9f83 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -57,6 +57,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); + public static final int AWAIT_SECONDS = 2; private Given() { } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 486697b153e..0e8f48438fc 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -214,12 +214,23 @@ private static void deliverUpdates(boolean isMultiThread, BoundedContextAction.. dispatchAction.perform(boundedContext); } - - // Was called ONCE + // Was called as much times as there are dispatch actions. verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); + + if (isMultiThread) { + await(Given.AWAIT_SECONDS); + } + verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); } + private static void await(int seconds) { + try { + Thread.sleep(seconds); + } catch (InterruptedException ignore) { + } + } + private static BoundedContextAction aggregateRepositoryDispatch() { return new BoundedContextAction() { @Override @@ -256,13 +267,12 @@ public void perform(BoundedContext context) { } - @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = Given.THREADS_COUNT_IN_POOL_EXECUTOR; @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name - final int threadExecutionMaxAwaitSeconds = 2; + final int threadExecutionMaxAwaitSeconds = Given.AWAIT_SECONDS; final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); From 459a42ba8933e30f43b3b8ec079c478e5924b37d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 14 Sep 2016 20:12:22 +0300 Subject: [PATCH 072/361] Return a query result as an instance of List; test bulk reading of aggregate states by their IDs. --- .../java/org/spine3/server/stand/Stand.java | 8 +- .../org/spine3/server/stand/StandShould.java | 130 ++++++++++++++---- 2 files changed, 106 insertions(+), 32 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 6f15d357b64..c9b9ca3a8a2 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -258,7 +258,7 @@ public void execute(Query query, StreamObserver responseObserver) private ImmutableCollection internalExecute(Query query) { - final ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); + final ImmutableList.Builder resultBuilder = ImmutableList.builder(); final Target target = query.getTarget(); @@ -280,7 +280,7 @@ private ImmutableCollection internalExecute(Query query) { feedStateRecordsToBuilder(resultBuilder, stateRecords); } - final ImmutableSet result = resultBuilder.build(); + final ImmutableList result = resultBuilder.build(); return result; } @@ -355,7 +355,7 @@ private static ImmutableCollection fetchFromEntityRepository(T return result; } - private static void feedEntitiesToBuilder(ImmutableSet.Builder resultBuilder, ImmutableCollection all) { + private static void feedEntitiesToBuilder(ImmutableList.Builder resultBuilder, ImmutableCollection all) { for (Entity record : all) { final Message state = record.getState(); final Any packedState = AnyPacker.pack(state); @@ -363,7 +363,7 @@ private static void feedEntitiesToBuilder(ImmutableSet.Builder resultBuilde } } - private static void feedStateRecordsToBuilder(ImmutableSet.Builder resultBuilder, ImmutableCollection all) { + private static void feedStateRecordsToBuilder(ImmutableList.Builder resultBuilder, ImmutableCollection all) { for (EntityStorageRecord record : all) { final Any state = record.getState(); resultBuilder.add(state); diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 7880fe510a9..7de36fe77c5 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -25,7 +25,6 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; -import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; import org.mockito.ArgumentMatcher; @@ -50,11 +49,15 @@ import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Random; import java.util.Set; import java.util.concurrent.Executor; +import static com.google.common.collect.Maps.newHashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -72,8 +75,9 @@ /** * @author Alex Tymchenko */ -@SuppressWarnings("OverlyCoupledClass") //It's OK for a test. +@SuppressWarnings({"OverlyCoupledClass", "InstanceMethodNamingConvention"}) //It's OK for a test. public class StandShould { + private static final int TOTAL_CUSTOMERS_FOR_BATCH_READING = 10; // **** Positive scenarios **** @@ -230,50 +234,120 @@ public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { @Test public void return_single_result_for_aggregate_state_read_by_id() { + doCheckReadingCustomersById(1); + } + + @Test + public void return_multiple_results_for_aggregate_state_batch_read_by_ids() { + doCheckReadingCustomersById(TOTAL_CUSTOMERS_FOR_BATCH_READING); + } + + private static void doCheckReadingCustomersById(int numberOfCustomers) { // Define the types and values used as a test data. + final Map sampleCustomers = newHashMap(); final TypeUrl customerType = TypeUrl.of(Customer.class); - final Customer customer = Customer.getDefaultInstance(); - final Any customerState = AnyPacker.pack(customer); - final CustomerId customerId = CustomerId.newBuilder() - .setNumber(42) - .build(); - final AggregateStateId stateId = AggregateStateId.of(customerId, customerType); - + fillSampleCustomers(sampleCustomers, numberOfCustomers); // Prepare the stand and its mock storage to act. final StandStorage standStorageMock = mock(StandStorage.class); - final EntityStorageRecord entityStorageRecord = EntityStorageRecord.newBuilder() - .setState(customerState) - .build(); - when(standStorageMock.read(eq(stateId))).thenReturn(entityStorageRecord); - final Stand stand = prepareStandWithAggregateRepo(standStorageMock); + setupExpectedBulkReadBehaviour(sampleCustomers, customerType, standStorageMock); - // Trigger the update. - stand.update(customerId, customerState); + final Stand stand = prepareStandWithAggregateRepo(standStorageMock); + triggerMultipleUpdates(sampleCustomers, stand); // Now we are ready to query. - final EntityIdFilter idFilter = EntityIdFilter.newBuilder() - .addIds(EntityId.newBuilder() - .setId(AnyPacker.pack(customerId))) - .build(); + final EntityIdFilter idFilter = idFilterFor(sampleCustomers.keySet()); + final Target customerTarget = Target.newBuilder() .setFilters(EntityFilters.newBuilder() .setIdFilter(idFilter)) .setType(customerType.getTypeName()) .build(); - final Query readSingleCustomer = Query.newBuilder() - .setTarget(customerTarget) - .build(); + final Query readMultipleCustomers = Query.newBuilder() + .setTarget(customerTarget) + .build(); final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); - stand.execute(readSingleCustomer, responseObserver); + stand.execute(readMultipleCustomers, responseObserver); final List messageList = checkAndGetMessageList(responseObserver); - assertEquals(1, messageList.size()); - final Any singleRecord = messageList.get(0); - final Message unpackedSingleResult = AnyPacker.unpack(singleRecord); - assertEquals(customer, unpackedSingleResult); + assertEquals(sampleCustomers.size(), messageList.size()); + final Collection allCustomers = sampleCustomers.values(); + for (Any singleRecord : messageList) { + final Customer unpackedSingleResult = AnyPacker.unpack(singleRecord); + assertTrue(allCustomers.contains(unpackedSingleResult)); + } + + } + + @SuppressWarnings("ConstantConditions") + private static void setupExpectedBulkReadBehaviour(Map sampleCustomers, TypeUrl customerType, StandStorage standStorageMock) { + final ImmutableList.Builder stateIdsBuilder = ImmutableList.builder(); + final ImmutableList.Builder recordsBuilder = ImmutableList.builder(); + for (CustomerId customerId : sampleCustomers.keySet()) { + final AggregateStateId stateId = AggregateStateId.of(customerId, customerType); + final Customer customer = Customer.getDefaultInstance(); + final Any customerState = AnyPacker.pack(customer); + final EntityStorageRecord entityStorageRecord = EntityStorageRecord.newBuilder() + .setState(customerState) + .build(); + stateIdsBuilder.add(stateId); + recordsBuilder.add(entityStorageRecord); + + when(standStorageMock.read(eq(stateId))).thenReturn(entityStorageRecord); + } + + final ImmutableList stateIds = stateIdsBuilder.build(); + final ImmutableList records = recordsBuilder.build(); + + final Iterable matchingIds = argThat(aggregateIdsIterableMatcher(stateIds)); + when(standStorageMock.readBulk(matchingIds)).thenReturn(records); + } + + private static ArgumentMatcher> aggregateIdsIterableMatcher(final ImmutableList stateIds) { + return new ArgumentMatcher>() { + @Override + public boolean matches(Iterable argument) { + boolean everyElementPresent = true; + for (AggregateStateId aggregateStateId : argument) { + everyElementPresent = everyElementPresent && stateIds.contains(aggregateStateId); + } + return everyElementPresent; + } + }; + } + + private static EntityIdFilter idFilterFor(Collection customerIds) { + final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); + for (CustomerId id : customerIds) { + idFilterBuilder + .addIds(EntityId.newBuilder() + .setId(AnyPacker.pack(id))); + } + return idFilterBuilder.build(); + } + + private static void triggerMultipleUpdates(Map sampleCustomers, Stand stand) { + // Trigger the aggregate state updates. + for (CustomerId id : sampleCustomers.keySet()) { + final Customer sampleCustomer = sampleCustomers.get(id); + final Any customerState = AnyPacker.pack(sampleCustomer); + stand.update(id, customerState); + } + } + + private static void fillSampleCustomers(Map sampleCustomers, int numberOfCustomers) { + for (int customerIndex = 0; customerIndex < numberOfCustomers; customerIndex++) { + final Customer customer = Customer.getDefaultInstance(); + + @SuppressWarnings("UnsecureRandomNumberGeneration") + final Random randomizer = new Random(); + final CustomerId customerId = CustomerId.newBuilder() + .setNumber(randomizer.nextInt()) + .build(); + sampleCustomers.put(customerId, customer); + } } private static List checkAndGetMessageList(MemoizeQueryResponseObserver responseObserver) { From 47ab39e785b541747103bb3106ed3c317b1f2024 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 14 Sep 2016 20:40:47 +0300 Subject: [PATCH 073/361] Test aggregate state query processing in case of: * various empty filter states; * valid filters but empty aggregate storage state. --- .../java/org/spine3/server/stand/Stand.java | 4 +- .../org/spine3/server/stand/StandShould.java | 133 ++++++++++++++++-- 2 files changed, 122 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index c9b9ca3a8a2..ecdc46bffc9 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -293,7 +293,9 @@ private ImmutableCollection fetchFromStandStorage(Target ta } else { final EntityFilters filters = target.getFilters(); - if (filters != null && filters.getIdFilter() != null) { + + // TODO[alex.tymchenko]: do we need to check for null at all? How about, say, Python gRPC client? + if (filters != null && filters.getIdFilter() != null && !filters.getIdFilter().getIdsList().isEmpty()) { final EntityIdFilter idFilter = filters.getIdFilter(); final Collection stateIds = Collections2.transform(idFilter.getIdsList(), aggregateStateIdTransformer(typeUrl)); diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 7de36fe77c5..97efa3352e6 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -23,11 +23,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; import io.grpc.stub.StreamObserver; import org.junit.Test; import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; @@ -75,7 +77,8 @@ /** * @author Alex Tymchenko */ -@SuppressWarnings({"OverlyCoupledClass", "InstanceMethodNamingConvention"}) //It's OK for a test. +//It's OK for a test. +@SuppressWarnings({"OverlyCoupledClass", "InstanceMethodNamingConvention", "ClassWithTooManyMethods"}) public class StandShould { private static final int TOTAL_CUSTOMERS_FOR_BATCH_READING = 10; @@ -208,28 +211,76 @@ public void operate_with_storage_provided_through_builder() { @Test public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { - final StandStorage standStorageMock = mock(StandStorage.class); - // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. - final ImmutableList emptyResultList = ImmutableList.builder().build(); - when(standStorageMock.readAllByType(any(TypeUrl.class))).thenReturn(emptyResultList); + final TypeUrl customerType = TypeUrl.of(Customer.class); + final Target customerTarget = Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .build(); - final Stand stand = prepareStandWithAggregateRepo(standStorageMock); + checkEmptyResultForTargetOnEmptyStorage(customerTarget); + } + + @Test + public void return_empty_list_for_aggregate_read_by_ids_on_empty_stand_storage() { final TypeUrl customerType = TypeUrl.of(Customer.class); + + final EntityId firstId = wrapCustomerId(1); + final EntityId anotherId = wrapCustomerId(2); + final EntityIdFilter idFilter = EntityIdFilter.newBuilder() + .addIds(firstId) + .addIds(anotherId) + .build(); + final EntityFilters filters = EntityFilters.newBuilder() + .setIdFilter(idFilter) + .build(); final Target customerTarget = Target.newBuilder() - .setIncludeAll(true) + .setIncludeAll(false) .setType(customerType.getTypeName()) + .setFilters(filters) .build(); - final Query readAllCustomers = Query.newBuilder() - .setTarget(customerTarget) + + checkEmptyResultForTargetOnEmptyStorage(customerTarget); + } + + @Test + public void return_empty_list_for_aggregate_reads_with_filters_not_set() { + + final TypeUrl customerType = TypeUrl.of(Customer.class); + final Target customerTarget = Target.newBuilder() + .setIncludeAll(false) + .setType(customerType.getTypeName()) .build(); + checkEmptyResultOnNonEmptyStorageForQueryTarget(customerTarget); + } - final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); - stand.execute(readAllCustomers, responseObserver); + @Test + public void return_empty_list_for_aggregate_reads_with_id_filter_not_set() { - final List messageList = checkAndGetMessageList(responseObserver); - assertTrue("Query returned a non-empty response message list though the target was empty", messageList - .isEmpty()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + final EntityFilters filters = EntityFilters.newBuilder() + .build(); + final Target customerTarget = Target.newBuilder() + .setIncludeAll(false) + .setType(customerType.getTypeName()) + .setFilters(filters) + .build(); + checkEmptyResultOnNonEmptyStorageForQueryTarget(customerTarget); + } + + @Test + public void return_empty_list_for_aggregate_reads_with_empty_list_of_ids() { + + final TypeUrl customerType = TypeUrl.of(Customer.class); + final EntityFilters filters = EntityFilters.newBuilder() + .setIdFilter(EntityIdFilter.getDefaultInstance()) + .build(); + final Target customerTarget = Target.newBuilder() + .setIncludeAll(false) + .setType(customerType.getTypeName()) + .setFilters(filters) + .build(); + checkEmptyResultOnNonEmptyStorageForQueryTarget(customerTarget); } @Test @@ -242,6 +293,24 @@ public void return_multiple_results_for_aggregate_state_batch_read_by_ids() { doCheckReadingCustomersById(TOTAL_CUSTOMERS_FOR_BATCH_READING); } + private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { + final StandStorage standStorageMock = mock(StandStorage.class); + // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. + final ImmutableList emptyResultList = ImmutableList.builder().build(); + when(standStorageMock.readAllByType(any(TypeUrl.class))).thenReturn(emptyResultList); + + final Stand stand = prepareStandWithAggregateRepo(standStorageMock); + + final Query readAllCustomers = Query.newBuilder() + .setTarget(customerTarget) + .build(); + + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); + stand.execute(readAllCustomers, responseObserver); + + final List messageList = checkAndGetMessageList(responseObserver); + assertTrue("Query returned a non-empty response message list though the target was empty", messageList.isEmpty()); + } private static void doCheckReadingCustomersById(int numberOfCustomers) { // Define the types and values used as a test data. @@ -281,6 +350,32 @@ private static void doCheckReadingCustomersById(int numberOfCustomers) { } + private static void checkEmptyResultOnNonEmptyStorageForQueryTarget(Target customerTarget) { + final StandStorage standStorageMock = mock(StandStorage.class); + + // Return non-empty results on any storage read call. + final EntityStorageRecord someRecord = EntityStorageRecord.getDefaultInstance(); + final ImmutableList nonEmptyList = ImmutableList.builder().add(someRecord) + .build(); + when(standStorageMock.readAllByType(any(TypeUrl.class))).thenReturn(nonEmptyList); + when(standStorageMock.read(any(AggregateStateId.class))).thenReturn(someRecord); + when(standStorageMock.readAll()).thenReturn(Maps.newHashMap()); + when(standStorageMock.readBulk(ArgumentMatchers.anyIterable())).thenReturn(nonEmptyList); + + final Stand stand = prepareStandWithAggregateRepo(standStorageMock); + + final Query queryWithNoFilters = Query.newBuilder() + .setTarget(customerTarget) + .build(); + + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); + stand.execute(queryWithNoFilters, responseObserver); + + final List messageList = checkAndGetMessageList(responseObserver); + assertTrue("Query returned a non-empty response message list though the filter was not set", messageList.isEmpty()); + } + + @SuppressWarnings("ConstantConditions") private static void setupExpectedBulkReadBehaviour(Map sampleCustomers, TypeUrl customerType, StandStorage standStorageMock) { final ImmutableList.Builder stateIdsBuilder = ImmutableList.builder(); @@ -305,6 +400,16 @@ private static void setupExpectedBulkReadBehaviour(Map sam when(standStorageMock.readBulk(matchingIds)).thenReturn(records); } + private static EntityId wrapCustomerId(int number) { + final CustomerId customerId = CustomerId.newBuilder() + .setNumber(number) + .build(); + final Any packedId = AnyPacker.pack(customerId); + return EntityId.newBuilder() + .setId(packedId) + .build(); + } + private static ArgumentMatcher> aggregateIdsIterableMatcher(final ImmutableList stateIds) { return new ArgumentMatcher>() { @Override From f2c99a1b1a69981e9317bc77d759b21b14be243e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 14 Sep 2016 20:50:38 +0300 Subject: [PATCH 074/361] Stand tests: use Mockito#times() instead of Mockito#calls(), since the order is not important. --- .../org/spine3/server/stand/StandShould.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 97efa3352e6..81778486fe0 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -30,7 +30,6 @@ import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; -import org.mockito.InOrder; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; @@ -67,10 +66,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.calls; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; @@ -150,7 +149,6 @@ public void register_aggregate_repositories() { @Test public void use_provided_executor_upon_update_of_watched_type() { final Executor executor = mock(Executor.class); - final InOrder executorInOrder = inOrder(executor); final Stand stand = Stand.newBuilder() .setCallbackExecutor(executor) .build(); @@ -161,21 +159,18 @@ public void use_provided_executor_upon_update_of_watched_type() { final TypeUrl projectProjectionType = TypeUrl.of(Project.class); stand.watch(projectProjectionType, emptyUpdateCallback()); - executorInOrder.verify(executor, never()) - .execute(any(Runnable.class)); + verify(executor, never()).execute(any(Runnable.class)); final Any someUpdate = AnyPacker.pack(Project.getDefaultInstance()); final Object someId = new Object(); stand.update(someId, someUpdate); - executorInOrder.verify(executor, calls(1)) - .execute(any(Runnable.class)); + verify(executor, times(1)).execute(any(Runnable.class)); } @Test public void operate_with_storage_provided_through_builder() { final StandStorage standStorageMock = mock(StandStorage.class); - final InOrder standStorageInOrder = inOrder(standStorageMock); final Stand stand = Stand.newBuilder() .setStorage(standStorageMock) .build(); @@ -195,8 +190,7 @@ public void operate_with_storage_provided_through_builder() { final Any packedState = AnyPacker.pack(customerState); final TypeUrl customerType = TypeUrl.of(Customer.class); - standStorageInOrder.verify(standStorageMock, never()) - .write(any(AggregateStateId.class), any(EntityStorageRecord.class)); + verify(standStorageMock, never()).write(any(AggregateStateId.class), any(EntityStorageRecord.class)); stand.update(customerId, packedState); @@ -204,8 +198,7 @@ public void operate_with_storage_provided_through_builder() { final EntityStorageRecord expectedRecord = EntityStorageRecord.newBuilder() .setState(packedState) .build(); - standStorageInOrder.verify(standStorageMock, calls(1)) - .write(eq(expectedAggregateStateId), recordStateMatcher(expectedRecord)); + verify(standStorageMock, times(1)).write(eq(expectedAggregateStateId), recordStateMatcher(expectedRecord)); } @Test From ecf11611e4ac089187c40af7f6714ced1c012be3 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:22:03 +0300 Subject: [PATCH 075/361] Fix issues form PR. --- .../java/org/spine3/server/stand/Given.java | 10 +-- .../server/stand/StandFunnelShould.java | 89 ++++++++----------- 2 files changed, 41 insertions(+), 58 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 9e6d31d9f83..4977b9e0a3e 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,6 +20,7 @@ package org.spine3.server.stand; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import com.google.protobuf.Message; import org.spine3.base.Command; @@ -139,7 +140,7 @@ public void handle(ProjectCreated event, EventContext context) { .setDoNotEnrich(true) .setCommandContext(CommandContext.getDefaultInstance()) .setEventId(EventId.newBuilder() - .setUuid(PROJECT_UUID) + .setUuid(Identifiers.newUuid()) .build())) .build(); } @@ -161,12 +162,7 @@ public void handle(ProjectCreated event, EventContext context) { /*package*/ static BoundedContext boundedContext(Stand stand, int concurrentThreads) { final Executor executor = concurrentThreads > 0 ? Executors.newFixedThreadPool(concurrentThreads) : - new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }; + MoreExecutors.directExecutor(); return boundedContextBuilder(stand) .setStandFunnelExecutor(executor) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 0e8f48438fc..b405de4777f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -22,6 +22,7 @@ package org.spine3.server.stand; import com.google.protobuf.Any; +import io.netty.util.internal.ConcurrentSet; import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatchers; @@ -32,13 +33,11 @@ import org.spine3.testdata.TestStandFactory; import java.security.SecureRandom; -import java.util.Map; import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; @@ -57,10 +56,6 @@ public class StandFunnelShould { // **** Positive scenarios (unit) **** - /** - * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. - */ - @Test public void initialize_properly_with_stand_only() { final Stand stand = TestStandFactory.create(); @@ -71,36 +66,25 @@ public void initialize_properly_with_stand_only() { } @Test - public void initialize_properly_with_various_builder_options() { + public void initialize_properly_with_all_builder_options() { final Stand stand = TestStandFactory.create(); - final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return Thread.currentThread(); - } - }); + final Executor executor = Executors.newSingleThreadExecutor(); - final StandFunnel blockingFunnel = StandFunnel.newBuilder() + final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) .setExecutor(executor) .build(); - Assert.assertNotNull(blockingFunnel); + Assert.assertNotNull(funnel); + } + + @Test + public void initialize_properly_with_no_executor() { + final Stand stand = TestStandFactory.create(); final StandFunnel funnelForBusyStand = StandFunnel.newBuilder() .setStand(stand) - .setExecutor(Executors.newSingleThreadExecutor()) .build(); Assert.assertNotNull(funnelForBusyStand); - - - final StandFunnel emptyExecutorFunnel = StandFunnel.newBuilder() - .setStand(TestStandFactory.create()) - .setExecutor(new Executor() { - @Override - public void execute(Runnable neverCalled) { } - }) - .build(); - Assert.assertNotNull(emptyExecutorFunnel); } @Test @@ -149,8 +133,8 @@ public void execute(Runnable command) { @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_inproper_stand() { - @SuppressWarnings("ConstantConditions") + public void fail_to_initialize_with_improper_stand() { + @SuppressWarnings("ConstantConditions") // null is marked as improper with this warning final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); builder.build(); @@ -165,30 +149,24 @@ public void fail_to_initialize_from_empty_builder() { // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** - /** - * - Deliver updates from projection projectionRepo on update; - * - deliver updates from aggregate projectionRepo on update; - * - deliver the updates from several projection and aggregate repositories. - */ - @Test public void deliver_updates_from_projection_repository() { - deliverUpdates(false, projectionRepositoryDispatch()); + checkUpdatesDelivery(false, projectionRepositoryDispatch()); } @Test public void deliver_updates_form_aggregate_repository() { - deliverUpdates(false, aggregateRepositoryDispatch()); + checkUpdatesDelivery(false, aggregateRepositoryDispatch()); } @Test public void deliver_updates_from_several_repositories_in_single_thread() { - deliverUpdates(false, getSeveralRepositoryDispatchCalls()); + checkUpdatesDelivery(false, getSeveralRepositoryDispatchCalls()); } @Test public void deliver_updates_from_several_repositories_in_multiple_threads() { - deliverUpdates(true, getSeveralRepositoryDispatchCalls()); + checkUpdatesDelivery(true, getSeveralRepositoryDispatchCalls()); } private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { @@ -202,22 +180,22 @@ private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { return result; } - private static void deliverUpdates(boolean isMultiThread, BoundedContextAction... dispatchActions) { + private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAction... dispatchActions) { checkNotNull(dispatchActions); final Stand stand = mock(Stand.class); final BoundedContext boundedContext = spy(Given.boundedContext(stand, - isMultiThread ? + isConcurrent ? Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); for (BoundedContextAction dispatchAction : dispatchActions) { dispatchAction.perform(boundedContext); } - // Was called as much times as there are dispatch actions. + // Was called as many times as there are dispatch actions. verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); - if (isMultiThread) { + if (isConcurrent) { await(Given.AWAIT_SECONDS); } @@ -241,10 +219,19 @@ public void perform(BoundedContext context) { repository.initStorage(InMemoryStorageFactory.getInstance()); try { + // Mock aggregate and mock stand are not able to handle events returned after command handling. + // This causes IllegalStateException to be thrown. + // Note that this is not the end of a test case, so we can't just "expect=IllegalStateException" repository.dispatch(Given.validCommand()); } catch (IllegalStateException e) { - // Handle null event dispatch after command handling. - Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + // Handle null event dispatching after the command is handled. + + // Check if this error is caused by returning nuu or empty list after command processing. + // Proceed crash if it's not + if (!e.getMessage() + .contains("No record found for command ID: EMPTY")) { + throw e; + } } } }; @@ -274,7 +261,7 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name final int threadExecutionMaxAwaitSeconds = Given.AWAIT_SECONDS; - final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); + final Set threadInvocationRegistry = new ConcurrentSet<>(); final Stand stand = mock(Stand.class); doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -283,26 +270,26 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio .setStand(stand) .build(); - final ExecutorService processes = Executors.newFixedThreadPool(threadsCount); + final ExecutorService executor = Executors.newFixedThreadPool(threadsCount); final Runnable task = new Runnable() { @Override public void run() { final String threadName = Thread.currentThread().getName(); - Assert.assertFalse(threadInvocationRegistry.containsKey(threadName)); + Assert.assertFalse(threadInvocationRegistry.contains(threadName)); standFunnel.post(new Object(), Any.getDefaultInstance()); - threadInvocationRegistry.put(threadName, new Object()); + threadInvocationRegistry.add(threadName); } }; for (int i = 0; i < threadsCount; i++) { - processes.execute(task); + executor.execute(task); } - processes.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); + executor.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); Assert.assertEquals(threadInvocationRegistry.size(), threadsCount); From 6fa6f1440170c7a7a0d5a642daacf38b07f0a562 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:36:39 +0300 Subject: [PATCH 076/361] Increase await time for concurrent check execution. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 4977b9e0a3e..82fc42e059d 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - public static final int AWAIT_SECONDS = 2; + public static final int AWAIT_SECONDS = 4; private Given() { } From 5226db609bdb4cfca6e0cb277587ff08db2e6c56 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 14 Sep 2016 21:44:17 +0300 Subject: [PATCH 077/361] Add tests for projection repositories used in stand: reading by IDs. --- .../org/spine3/server/stand/StandShould.java | 165 +++++++++++++++++- 1 file changed, 160 insertions(+), 5 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 81778486fe0..75ca1a56694 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -21,11 +21,15 @@ */ package org.spine3.server.stand; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; import org.mockito.ArgumentMatcher; @@ -50,14 +54,17 @@ import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import javax.annotation.Nullable; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.UUID; import java.util.concurrent.Executor; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Maps.newHashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -80,6 +87,7 @@ @SuppressWarnings({"OverlyCoupledClass", "InstanceMethodNamingConvention", "ClassWithTooManyMethods"}) public class StandShould { private static final int TOTAL_CUSTOMERS_FOR_BATCH_READING = 10; + private static final int TOTAL_PROJECTS_FOR_BATCH_READING = 10; // **** Positive scenarios **** @@ -286,6 +294,17 @@ public void return_multiple_results_for_aggregate_state_batch_read_by_ids() { doCheckReadingCustomersById(TOTAL_CUSTOMERS_FOR_BATCH_READING); } + + @Test + public void return_single_result_for_projection_read_by_id() { + doCheckReadingProjectsById(1); + } + + @Test + public void return_multiple_results_for_projection_batch_read_by_ids() { + doCheckReadingProjectsById(TOTAL_PROJECTS_FOR_BATCH_READING); + } + private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. @@ -305,6 +324,42 @@ private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarge assertTrue("Query returned a non-empty response message list though the target was empty", messageList.isEmpty()); } + private static void doCheckReadingProjectsById(int numberOfProjects) { + // Define the types and values used as a test data. + final Map sampleProjects = newHashMap(); + final TypeUrl projectType = TypeUrl.of(Project.class); + fillSampleProjects(sampleProjects, numberOfProjects); + + final StandTestProjectionRepository projectionRepository = mock(StandTestProjectionRepository.class); + when(projectionRepository.getEntityStateType()).thenReturn(projectType); + setupExpectedFindAllBehaviour(sampleProjects, projectionRepository); + + final Stand stand = prepareStandWithProjectionRepo(projectionRepository); + + // Now we are ready to query. + final EntityIdFilter idFilter = idFilterForProjection(sampleProjects.keySet()); + + final Target projectTarget = Target.newBuilder() + .setFilters(EntityFilters.newBuilder() + .setIdFilter(idFilter)) + .setType(projectType.getTypeName()) + .build(); + final Query readMultipleProjects = Query.newBuilder() + .setTarget(projectTarget) + .build(); + + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); + stand.execute(readMultipleProjects, responseObserver); + + final List messageList = checkAndGetMessageList(responseObserver); + assertEquals(sampleProjects.size(), messageList.size()); + final Collection allCustomers = sampleProjects.values(); + for (Any singleRecord : messageList) { + final Project unpackedSingleResult = AnyPacker.unpack(singleRecord); + assertTrue(allCustomers.contains(unpackedSingleResult)); + } + } + private static void doCheckReadingCustomersById(int numberOfCustomers) { // Define the types and values used as a test data. final Map sampleCustomers = newHashMap(); @@ -319,7 +374,7 @@ private static void doCheckReadingCustomersById(int numberOfCustomers) { triggerMultipleUpdates(sampleCustomers, stand); // Now we are ready to query. - final EntityIdFilter idFilter = idFilterFor(sampleCustomers.keySet()); + final EntityIdFilter idFilter = idFilterForAggregate(sampleCustomers.keySet()); final Target customerTarget = Target.newBuilder() .setFilters(EntityFilters.newBuilder() @@ -340,7 +395,6 @@ private static void doCheckReadingCustomersById(int numberOfCustomers) { final Customer unpackedSingleResult = AnyPacker.unpack(singleRecord); assertTrue(allCustomers.contains(unpackedSingleResult)); } - } private static void checkEmptyResultOnNonEmptyStorageForQueryTarget(Target customerTarget) { @@ -370,7 +424,8 @@ private static void checkEmptyResultOnNonEmptyStorageForQueryTarget(Target custo @SuppressWarnings("ConstantConditions") - private static void setupExpectedBulkReadBehaviour(Map sampleCustomers, TypeUrl customerType, StandStorage standStorageMock) { + private static void setupExpectedBulkReadBehaviour(Map sampleCustomers, TypeUrl customerType, + StandStorage standStorageMock) { final ImmutableList.Builder stateIdsBuilder = ImmutableList.builder(); final ImmutableList.Builder recordsBuilder = ImmutableList.builder(); for (CustomerId customerId : sampleCustomers.keySet()) { @@ -393,6 +448,64 @@ private static void setupExpectedBulkReadBehaviour(Map sam when(standStorageMock.readBulk(matchingIds)).thenReturn(records); } + + @SuppressWarnings("ConstantConditions") + private static void setupExpectedFindAllBehaviour(Map sampleProjects, + StandTestProjectionRepository projectionRepository) { + + final Set projectIds = sampleProjects.keySet(); + final ImmutableCollection allResults = toProjectionCollection(projectIds); + + for (ProjectId projectId : projectIds) { + when(projectionRepository.find(eq(projectId))).thenReturn(new StandTestProjection(projectId)); + } + + final Iterable matchingIds = argThat(projectionIdsIterableMatcher(projectIds)); + when(projectionRepository.findBulk(matchingIds)).thenReturn(allResults); + + when(projectionRepository.findAll()).thenReturn(allResults); + + final EntityFilters matchingFilter = argThat(entityFilterMatcher(projectIds)); + when(projectionRepository.findAll(matchingFilter)).thenReturn(allResults); + } + + @SuppressWarnings("OverlyComplexAnonymousInnerClass") + private static ArgumentMatcher entityFilterMatcher(final Set projectIds) { + // This argument matcher does NOT mimic the exact repository behavior. + // Instead, it only matches the EntityFilters instance in case it has EntityIdFilter with ALL the expected IDs. + return new ArgumentMatcher() { + @Override + public boolean matches(EntityFilters argument) { + boolean everyElementPresent = true; + for (EntityId entityId : argument.getIdFilter() + .getIdsList()) { + final Any idAsAny = entityId.getId(); + final Message rawId = AnyPacker.unpack(idAsAny); + if (rawId instanceof ProjectId) { + final ProjectId convertedProjectId = (ProjectId) rawId; + everyElementPresent = everyElementPresent && projectIds.contains(convertedProjectId); + } else { + everyElementPresent = false; + } + } + return everyElementPresent; + } + }; + } + + private static ImmutableCollection toProjectionCollection(Collection values) { + final Collection transformed = Collections2.transform(values, new Function() { + @Nullable + @Override + public StandTestProjection apply(@Nullable ProjectId input) { + checkNotNull(input); + return new StandTestProjection(input); + } + }); + final ImmutableList result = ImmutableList.copyOf(transformed); + return result; + } + private static EntityId wrapCustomerId(int number) { final CustomerId customerId = CustomerId.newBuilder() .setNumber(number) @@ -403,7 +516,20 @@ private static EntityId wrapCustomerId(int number) { .build(); } - private static ArgumentMatcher> aggregateIdsIterableMatcher(final ImmutableList stateIds) { + private static ArgumentMatcher> projectionIdsIterableMatcher(final Set projectIds) { + return new ArgumentMatcher>() { + @Override + public boolean matches(Iterable argument) { + boolean everyElementPresent = true; + for (ProjectId projectId : argument) { + everyElementPresent = everyElementPresent && projectIds.contains(projectId); + } + return everyElementPresent; + } + }; + } + + private static ArgumentMatcher> aggregateIdsIterableMatcher(final List stateIds) { return new ArgumentMatcher>() { @Override public boolean matches(Iterable argument) { @@ -416,7 +542,7 @@ public boolean matches(Iterable argument) { }; } - private static EntityIdFilter idFilterFor(Collection customerIds) { + private static EntityIdFilter idFilterForAggregate(Collection customerIds) { final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); for (CustomerId id : customerIds) { idFilterBuilder @@ -426,6 +552,16 @@ private static EntityIdFilter idFilterFor(Collection customerIds) { return idFilterBuilder.build(); } + private static EntityIdFilter idFilterForProjection(Collection projectIds) { + final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); + for (ProjectId id : projectIds) { + idFilterBuilder + .addIds(EntityId.newBuilder() + .setId(AnyPacker.pack(id))); + } + return idFilterBuilder.build(); + } + private static void triggerMultipleUpdates(Map sampleCustomers, Stand stand) { // Trigger the aggregate state updates. for (CustomerId id : sampleCustomers.keySet()) { @@ -448,6 +584,17 @@ private static void fillSampleCustomers(Map sampleCustomer } } + private static void fillSampleProjects(Map sampleProjects, int numberOfProjects) { + for (int projectIndex = 0; projectIndex < numberOfProjects; projectIndex++) { + final Project project = Project.getDefaultInstance(); + final ProjectId projectId = ProjectId.newBuilder() + .setId(UUID.randomUUID() + .toString()) + .build(); + sampleProjects.put(projectId, project); + } + } + private static List checkAndGetMessageList(MemoizeQueryResponseObserver responseObserver) { assertTrue("Query has not completed successfully", responseObserver.isCompleted); assertNull("Throwable has been caught upon query execution", responseObserver.throwable); @@ -474,6 +621,14 @@ private static Stand prepareStandWithAggregateRepo(StandStorage standStorageMock return stand; } + private static Stand prepareStandWithProjectionRepo(ProjectionRepository projectionRepository) { + final Stand stand = Stand.newBuilder() + .build(); + assertNotNull(stand); + stand.registerTypeSupplier(projectionRepository); + return stand; + } + private static EntityStorageRecord recordStateMatcher(final EntityStorageRecord expectedRecord) { return argThat(new ArgumentMatcher() { @Override From 1eb84455e3275ed7a05ab408888f3d69a32393c5 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:51:17 +0300 Subject: [PATCH 078/361] Increase await time for concurrent check execution again. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 82fc42e059d..e0872d39b9f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - public static final int AWAIT_SECONDS = 4; + /*package*/ static final int AWAIT_SECONDS = 10; private Given() { } From d34a80b67f93a80b849c5f924f089658f8460bbe Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:52:48 +0300 Subject: [PATCH 079/361] Reduce randomness of generated test data. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index e0872d39b9f..d194aecc21f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - /*package*/ static final int AWAIT_SECONDS = 10; + /*package*/ static final int AWAIT_SECONDS = 6; private Given() { } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index b405de4777f..41be840a692 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -174,7 +174,7 @@ private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { final Random random = new SecureRandom(); for (int i = 0; i < result.length; i++) { - result[i] = random.nextBoolean() ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); + result[i] = (i % 2 == 0) ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); } return result; From ecfede812a9657acad0ac873636545f50f94ce28 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 23:29:36 +0300 Subject: [PATCH 080/361] Remove redundant imports. --- .../src/test/java/org/spine3/server/stand/StandShould.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 5884a55cb1f..b2f4c35af8c 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -41,15 +41,12 @@ import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.Given; -import org.spine3.server.projection.Projection; -import org.spine3.server.projection.ProjectionRepository; -import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.stand.Given.StandTestProjectionRepository; +import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; -import org.spine3.test.projection.ProjectId; import java.util.List; import java.util.Objects; From dfd823db4d749779860e25fe307ef2cd9a2e8229 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 15 Sep 2016 15:01:36 +0300 Subject: [PATCH 081/361] Test handling an unknown type in a Query. --- .../org/spine3/server/stand/StandShould.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 75ca1a56694..35170e85d6b 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -221,6 +221,32 @@ public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { checkEmptyResultForTargetOnEmptyStorage(customerTarget); } + @Test + public void return_empty_list_to_unknown_type_reading() { + + final Stand stand = Stand.newBuilder() + .build(); + + checkTypesEmpty(stand); + + // Customer type was NOT registered. + final TypeUrl customerType = TypeUrl.of(Customer.class); + final Target customerTarget = Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .build(); + final Query readAllCustomers = Query.newBuilder() + .setTarget(customerTarget) + .build(); + + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); + stand.execute(readAllCustomers, responseObserver); + + final List messageList = checkAndGetMessageList(responseObserver); + + assertTrue("Query returned a non-empty response message list for an unknown type", messageList.isEmpty()); + } + @Test public void return_empty_list_for_aggregate_read_by_ids_on_empty_stand_storage() { From ff73389a27a26942e5375a35fd086e434fabaf31 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:10:41 +0300 Subject: [PATCH 082/361] Add creation test covering various Builder params. --- .../server/stand/StandFunnelShould.java | 36 ++++++++++++++++++- .../org/spine3/testdata/TestStandFactory.java | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 9e42f37e68e..5eb8fb42bdf 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -27,6 +27,8 @@ import org.spine3.testdata.TestStandFactory; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; @@ -40,7 +42,6 @@ public class StandFunnelShould { // **** Positive scenarios (unit) **** /** - * - Initialize properly with various Builder options; * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. */ @@ -53,6 +54,39 @@ public void initialize_properly_with_stand_only() { Assert.assertNotNull(standFunnel); } + @Test + public void initialize_properly_with_various_builder_options() { + final Stand stand = TestStandFactory.create(); + final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return Thread.currentThread(); + } + }); + + final StandFunnel blockingFunnel = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(executor) + .build(); + Assert.assertNotNull(blockingFunnel); + + final StandFunnel funnelForBusyStand = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(Executors.newSingleThreadExecutor()) + .build(); + Assert.assertNotNull(funnelForBusyStand); + + + final StandFunnel emptyExecutorFunnel = StandFunnel.newBuilder() + .setStand(TestStandFactory.create()) + .setExecutor(new Executor() { + @Override + public void execute(Runnable neverCalled) { } + }) + .build(); + Assert.assertNotNull(emptyExecutorFunnel); + } + @Test public void use_executor_from_builder() { diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index d54df4fae6d..474a6226b30 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -22,13 +22,13 @@ package org.spine3.testdata; import org.spine3.server.stand.Stand; -import org.spine3.server.storage.memory.InMemoryStandStorage; /** * Creates stands for tests. * * @author Alex Tymchenko */ +@SuppressWarnings("UtilityClass") public class TestStandFactory { private TestStandFactory() {} From bfeec821673f8d04a3550da4ea81bfa58f751f0e Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:11:56 +0300 Subject: [PATCH 083/361] Bump gradle version, --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 58e7b927f5d..6d19c1542b6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip From d141f24d8ebbe28bc21c0e6c070d633909879a24 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:37:55 +0300 Subject: [PATCH 084/361] Add some more tests (need to be processed). --- .../org/spine3/server/stand/StandFunnel.java | 2 ++ .../server/stand/StandFunnelShould.java | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 9e3cc659ce0..44997adbdf8 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -109,6 +109,7 @@ public Stand getStand() { *

The value must not be null. * *

If this method is not used, a default value will be used. + * // TODO:13-09-16:dmytro.dashenkov: Correct docs. No default value for Stand is used, null value leads to a fail instead. * * @param stand the instance of {@link Stand}. * @return {@code this} instance of {@code Builder} @@ -122,6 +123,7 @@ public Executor getExecutor() { return executor; } + // TODO:13-09-16:dmytro.dashenkov: Complete docs. Here is the place where a default value is used in case of not using the method. /** * Set the {@code Executor} instance for this {@code StandFunnel}. * diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 5eb8fb42bdf..da5f2bffdfd 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -87,6 +87,21 @@ public void execute(Runnable neverCalled) { } Assert.assertNotNull(emptyExecutorFunnel); } + @Test + public void deliver_mock_updates_to_stand() { + final Stand stand = spy(TestStandFactory.create()); + + final StandFunnel funnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + funnel.post(id, state); + + verify(stand).update(id, state); + } + @Test public void use_executor_from_builder() { @@ -114,9 +129,23 @@ public void execute(Runnable command) { // **** Negative scenarios (unit) **** - /** - * - Fail to initialise with improper stand. - */ + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_null_stand() { + @SuppressWarnings("ConstantConditions") + final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); + + builder.build(); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_importer_stand() { + // TODO:13-09-16:dmytro.dashenkov: Implement. +// @SuppressWarnings("ConstantConditions") +// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); +// builder.build(); + } @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) From 927a50c9fa5b01363e52006c97b75dfb964f69f7 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 15:16:06 +0300 Subject: [PATCH 085/361] Fix deliver mock updates test. --- .../server/stand/StandFunnelShould.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index da5f2bffdfd..ef7ba6c2b08 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,6 +31,8 @@ import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -89,14 +91,16 @@ public void execute(Runnable neverCalled) { } @Test public void deliver_mock_updates_to_stand() { - final Stand stand = spy(TestStandFactory.create()); + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(id, state); final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) .build(); - final Object id = new Object(); - final Any state = Any.getDefaultInstance(); funnel.post(id, state); verify(stand).update(id, state); @@ -131,22 +135,13 @@ public void execute(Runnable command) { @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_null_stand() { + public void fail_to_initialize_with_inproper_stand() { @SuppressWarnings("ConstantConditions") final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); builder.build(); } - @SuppressWarnings("ResultOfMethodCallIgnored") - @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_importer_stand() { - // TODO:13-09-16:dmytro.dashenkov: Implement. -// @SuppressWarnings("ConstantConditions") -// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); -// builder.build(); - } - @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) public void fail_to_initialize_from_empty_builder() { @@ -162,5 +157,10 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repo() { + + } + } From 4e1f868461aa15209a409b99932733b833926475 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 086/361] Explicit "Given" for stand tests. --- .../java/org/spine3/server/stand/Given.java | 67 +++++++++++++++++++ .../org/spine3/server/stand/StandShould.java | 21 +----- 2 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 server/src/test/java/org/spine3/server/stand/Given.java diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java new file mode 100644 index 00000000000..f1b54d0adfe --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.stand; + +import org.spine3.base.Event; +import org.spine3.base.EventContext; +import org.spine3.protobuf.AnyPacker; +import org.spine3.server.BoundedContext; +import org.spine3.server.projection.Projection; +import org.spine3.server.projection.ProjectionRepository; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.event.ProjectCreated; + +/** + * @author Dmytro Dashenkov + */ +/*package*/ class Given { + + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + private static class StandTestProjection extends Projection { + /** + * Creates a new instance. + * + * Required to be public. + * + * @param id the ID for the new instance + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestProjection(ProjectId id) { + super(id); + } + } + + /*package*/ static Event validEvent() { + return Event.newBuilder() + .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() + .setProjectId(ProjectId.newBuilder().setId("12345AD0")) + .build()) + .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .setContext(EventContext.getDefaultInstance()) + .build(); + } +} diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 35170e85d6b..574998ef00d 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -48,6 +48,7 @@ import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; @@ -700,26 +701,6 @@ public void onEntityStateUpdate(Any newEntityState) { // ***** Inner classes used for tests. ***** - private static class StandTestProjection extends Projection { - /** - * Creates a new instance. - * - * @param id the ID for the new instance - * @throws IllegalArgumentException if the ID is not of one of the supported types - */ - public StandTestProjection(ProjectId id) { - super(id); - } - } - - - private static class StandTestProjectionRepository extends ProjectionRepository { - protected StandTestProjectionRepository(BoundedContext boundedContext) { - super(boundedContext); - } - } - - /** * A {@link StreamObserver} storing the state of {@link Query} execution. */ From 7e814e9098a6c06b862b8f6980832eefccdb5914 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:14:40 +0300 Subject: [PATCH 087/361] Add threading test. --- .../server/stand/StandFunnelShould.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ef7ba6c2b08..899afd85415 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -24,11 +24,16 @@ import com.google.protobuf.Any; import org.junit.Assert; import org.junit.Test; +import org.mockito.ArgumentMatchers; import org.spine3.testdata.TestStandFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; @@ -38,6 +43,7 @@ /** * @author Alex Tymchenko + * @author Dmytro Dashenkov */ public class StandFunnelShould { @@ -157,10 +163,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + + @SuppressWarnings("MethodWithMultipleLoops") @Test - public void deliver_updates_from_projection_repo() { + public void deliver_updates_through_several_threads() throws InterruptedException { + final int threadsCount = 10; - } + final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); + + final StandFunnel standFunnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final ExecutorService processes = Executors.newFixedThreadPool(threadsCount); + + + final Runnable task = new Runnable() { + @Override + public void run() { + final String threadName = Thread.currentThread().getName(); + Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + + standFunnel.post(new Object(), Any.getDefaultInstance()); + + threadInvakationRegistry.put(threadName, new Object()); + } + }; + for (int i = 0; i < threadsCount; i++) { + processes.execute(task); + } + + processes.awaitTermination(10, TimeUnit.SECONDS); + + Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + + } } From 13393bf57841f8bb19ee66fcc74b6272be94bac5 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:16:54 +0300 Subject: [PATCH 088/361] Reduce executor await time. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 899afd85415..f47fa5efb48 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -168,6 +168,7 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; + final int threadExecuteionMaxAwaitSeconds = 2; final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); @@ -197,7 +198,7 @@ public void run() { processes.execute(task); } - processes.awaitTermination(10, TimeUnit.SECONDS); + processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); From 8d43860c8b670d55a5548e09939811dd98d5509b Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:54:11 +0300 Subject: [PATCH 089/361] Add deliver updates form projection repo test skeleton, --- .../java/org/spine3/server/stand/Given.java | 6 ++- .../server/stand/StandFunnelShould.java | 45 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index f1b54d0adfe..2f631d4995f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -23,6 +23,7 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; @@ -60,7 +61,10 @@ public StandTestProjection(ProjectId id) { .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) - .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .toBuilder() + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + + ProjectCreated.getDescriptor().getFullName()) + .build()) .setContext(EventContext.getDefaultInstance()) .build(); } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index f47fa5efb48..34e7ab2ff0f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -25,6 +25,8 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatchers; +import org.spine3.server.BoundedContext; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; import java.util.Map; @@ -40,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -163,14 +166,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repository() throws Exception { + final BoundedContext boundedContext = mock(BoundedContext.class); + final Stand stand = mock(Stand.class); + when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); + + // Init repository + final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); + + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } + @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; - final int threadExecuteionMaxAwaitSeconds = 2; + @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name + final int threadExecutionMaxAwaitSeconds = 2; - final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); final Stand stand = mock(Stand.class); doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -186,11 +219,11 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio @Override public void run() { final String threadName = Thread.currentThread().getName(); - Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + Assert.assertFalse(threadInvocationRegistry.containsKey(threadName)); standFunnel.post(new Object(), Any.getDefaultInstance()); - threadInvakationRegistry.put(threadName, new Object()); + threadInvocationRegistry.put(threadName, new Object()); } }; @@ -198,9 +231,9 @@ public void run() { processes.execute(task); } - processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); + processes.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); - Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + Assert.assertEquals(threadInvocationRegistry.size(), threadsCount); } From c95265c980c6abf755f2bf8a144a7795859c25f2 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:57:41 +0300 Subject: [PATCH 090/361] Suppress not yet ready test. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 34e7ab2ff0f..c924469b833 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -166,7 +166,7 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - @Test + //@Test public void deliver_updates_from_projection_repository() throws Exception { final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); From 1fa2b525d2f3dfdaf64ce4ca856f4990ea49c8a8 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 16:09:19 +0300 Subject: [PATCH 091/361] Finish delivery from projection repo test. --- .../java/org/spine3/server/stand/Given.java | 36 +++++++++++++++++-- .../server/stand/StandFunnelShould.java | 27 +++++++------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 2f631d4995f..c7da6d078b9 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,11 +20,16 @@ package org.spine3.server.stand; +import com.google.protobuf.Message; +import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; +import org.spine3.base.EventId; +import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.test.projection.Project; @@ -36,10 +41,20 @@ */ /*package*/ class Given { + private static final String PROJECT_UUID = Identifiers.newUuid(); + + private Given() { + } + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } + + @Override + protected ProjectId getEntityId(Message event, EventContext context) { + return ProjectId.newBuilder().setId(PROJECT_UUID).build(); + } } private static class StandTestProjection extends Projection { @@ -54,6 +69,11 @@ private static class StandTestProjection extends Projection public StandTestProjection(ProjectId id) { super(id); } + + @Subscribe + public void handle(ProjectCreated event, EventContext context) { + // Do nothing + } } /*package*/ static Event validEvent() { @@ -62,10 +82,20 @@ public StandTestProjection(ProjectId id) { .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) .toBuilder() - .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + - ProjectCreated.getDescriptor().getFullName()) + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + "/" + ProjectCreated.getDescriptor().getFullName()) .build()) - .setContext(EventContext.getDefaultInstance()) + .setContext(EventContext.newBuilder() + .setDoNotEnrich(true) + .setCommandContext(CommandContext.getDefaultInstance()) + .setEventId(EventId.newBuilder() + .setUuid(PROJECT_UUID) + .build())) .build(); } + + /*package*/ static ProjectionRepository repo(BoundedContext context) { + return new StandTestProjectionRepository(context); + } + + } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index c924469b833..4c9816159f1 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -166,22 +166,21 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - //@Test + @Test public void deliver_updates_from_projection_repository() throws Exception { - final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); - when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() - .setStand(stand) - .setExecutor(new Executor() { - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); - + final BoundedContext boundedContext = spy(BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); // Init repository - final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + final ProjectionRepository repository = Given.repo(boundedContext); stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); From 2660489d71f55ca208306692ec4b8fefc7e12bd5 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:26:06 +0300 Subject: [PATCH 092/361] Finish delivery form an aggregate repo test. --- .../java/org/spine3/server/stand/Given.java | 82 ++++++++++++++++++- .../server/stand/StandFunnelShould.java | 44 ++++++---- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index c7da6d078b9..595397ce31c 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,22 +20,34 @@ package org.spine3.server.stand; +import com.google.protobuf.Any; import com.google.protobuf.Message; +import org.spine3.base.Command; import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; +import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.Aggregate; +import org.spine3.server.aggregate.AggregateRepository; +import org.spine3.server.aggregate.Apply; +import org.spine3.server.command.Assign; import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.command.CreateProject; import org.spine3.test.projection.event.ProjectCreated; +import java.util.List; +import java.util.concurrent.Executor; + /** * @author Dmytro Dashenkov */ @@ -46,7 +58,7 @@ private Given() { } - /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } @@ -57,6 +69,48 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } + /*package*/ static class StandTestAggregateRepository extends AggregateRepository { + + /** + * Creates a new repository instance. + * + * @param boundedContext the bounded context to which this repository belongs + */ + /*package*/ StandTestAggregateRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + /*package*/ static class TestFailure extends FailureThrowable { + + /*package*/ TestFailure() { + super(/*generatedMessage=*/null); + } + } + + private static class StandTestAggregate extends Aggregate { + + /** + * Creates a new aggregate instance. + * + * @param id the ID for the new aggregate + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestAggregate(ProjectId id) { + super(id); + } + + @Assign + public List handle(CreateProject createProject, CommandContext context) { + return null; + } + + @Apply + public void handle(ProjectCreated event) { + // Do nothing + } + } + private static class StandTestProjection extends Projection { /** * Creates a new instance. @@ -76,7 +130,7 @@ public void handle(ProjectCreated event, EventContext context) { } } - /*package*/ static Event validEvent() { + /*package*/ static Event validEvent() { return Event.newBuilder() .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) @@ -93,9 +147,31 @@ public void handle(ProjectCreated event, EventContext context) { .build(); } - /*package*/ static ProjectionRepository repo(BoundedContext context) { + /*package*/ static Command validCommand() { + return Command.newBuilder() + .setMessage(AnyPacker.pack(CreateProject.getDefaultInstance())) + .setContext(CommandContext.getDefaultInstance()) + .build(); + } + + /*package*/ static ProjectionRepository projectionRepo(BoundedContext context) { return new StandTestProjectionRepository(context); } + /*package*/ static AggregateRepository aggregateRepo(BoundedContext context) { + return new StandTestAggregateRepository(context); + } + /*package*/ static BoundedContext boundedContext(Stand stand) { + return BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build(); + } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 4c9816159f1..ad82932db72 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -161,28 +162,18 @@ public void fail_to_initialize_from_empty_builder() { // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** /** - * - Deliver updates from projection repo on update; - * - deliver updates from aggregate repo on update; + * - Deliver updates from projection projectionRepo on update; + * - deliver updates from aggregate projectionRepo on update; * - deliver the updates from several projection and aggregate repositories. */ @Test - public void deliver_updates_from_projection_repository() throws Exception { + public void deliver_updates_from_projection_repository() { final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(BoundedContext.newBuilder() - .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); // Init repository - final ProjectionRepository repository = Given.repo(boundedContext); + final ProjectionRepository repository = Given.projectionRepo(boundedContext); - stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); repository.setOnline(); @@ -194,6 +185,29 @@ public void execute(Runnable command) { verify(stand).update(ArgumentMatchers.any(), any(Any.class)); } + @Test + public void deliver_updates_form_aggregate_repository() { + final Stand stand = mock(Stand.class); + + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + // Init repository + final AggregateRepository repository = Given.aggregateRepo(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + + // Dispatch an update from projection projectionRepo + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } @SuppressWarnings("MethodWithMultipleLoops") @Test From 84fed4b52adb83ad4c542c389818d3b1a698e0e2 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:27:38 +0300 Subject: [PATCH 093/361] Delete redundant "TestFailure" declaration. --- server/src/test/java/org/spine3/server/stand/Given.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 595397ce31c..18b75d76805 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -27,7 +27,6 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; -import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; @@ -81,13 +80,6 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } - /*package*/ static class TestFailure extends FailureThrowable { - - /*package*/ TestFailure() { - super(/*generatedMessage=*/null); - } - } - private static class StandTestAggregate extends Aggregate { /** From 8c93ffc351baccdedd1376491d849f82db93c1a7 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:40:54 +0300 Subject: [PATCH 094/361] Extract common test operations. --- .../server/stand/StandFunnelShould.java | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ad82932db72..3cac4e4acb7 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -39,6 +39,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import static com.google.common.base.Preconditions.checkNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -169,41 +170,53 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final ProjectionRepository repository = Given.projectionRepo(boundedContext); - - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }); } @Test public void deliver_updates_form_aggregate_repository() { - final Stand stand = mock(Stand.class); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final AggregateRepository repository = Given.aggregateRepo(context); + + repository.initStorage(InMemoryStorageFactory.getInstance()); + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + } + }); + } + + private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final AggregateRepository repository = Given.aggregateRepo(boundedContext); - - stand.registerTypeSupplier(repository); - repository.initStorage(InMemoryStorageFactory.getInstance()); - - // Dispatch an update from projection projectionRepo - try { - repository.dispatch(Given.validCommand()); - } catch (IllegalStateException e) { - // Handle null event dispatch after command handling. - Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); } + // Was called ONCE verify(boundedContext).getStandFunnel(); verify(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -250,4 +263,8 @@ public void run() { } + private interface BoundedContextAction { + void perform(BoundedContext context); + } + } From 15c815fc40fcf17dcdef28e922c9706c4570160e Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:21:34 +0300 Subject: [PATCH 095/361] Refactor and add delivery tests for several repositories successively and in parallel. --- .../java/org/spine3/server/stand/Given.java | 29 ++++-- .../server/stand/StandFunnelShould.java | 89 +++++++++++++------ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 18b75d76805..ccad28106a2 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -46,12 +46,16 @@ import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** * @author Dmytro Dashenkov */ /*package*/ class Given { + /*package*/ static final int THREADS_COUNT_IN_POOL_EXECUTOR = 10; + /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; + private static final String PROJECT_UUID = Identifiers.newUuid(); private Given() { @@ -154,16 +158,23 @@ public void handle(ProjectCreated event, EventContext context) { return new StandTestAggregateRepository(context); } - /*package*/ static BoundedContext boundedContext(Stand stand) { + /*package*/ static BoundedContext boundedContext(Stand stand, int concurrentThreads) { + final Executor executor = concurrentThreads > 0 ? Executors.newFixedThreadPool(concurrentThreads) : + new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }; + + return boundedContextBuilder(stand) + .setStandFunnelExecutor(executor) + .build(); + } + + private static BoundedContext.Builder boundedContextBuilder(Stand stand) { return BoundedContext.newBuilder() .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build(); + .setStorageFactory(InMemoryStorageFactory.getInstance()); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 3cac4e4acb7..486697b153e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,7 +31,9 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; +import java.security.SecureRandom; import java.util.Map; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** @@ -170,24 +173,55 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - deliverUpdatesTest(new BoundedContextAction() { - @Override - public void perform(BoundedContext context) { - // Init repository - final ProjectionRepository repository = Given.projectionRepo(context); + deliverUpdates(false, projectionRepositoryDispatch()); + } - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + @Test + public void deliver_updates_form_aggregate_repository() { + deliverUpdates(false, aggregateRepositoryDispatch()); + } - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); - } - }); + @Test + public void deliver_updates_from_several_repositories_in_single_thread() { + deliverUpdates(false, getSeveralRepositoryDispatchCalls()); } @Test - public void deliver_updates_form_aggregate_repository() { - deliverUpdatesTest(new BoundedContextAction() { + public void deliver_updates_from_several_repositories_in_multiple_threads() { + deliverUpdates(true, getSeveralRepositoryDispatchCalls()); + } + + private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { + final BoundedContextAction[] result = new BoundedContextAction[Given.SEVERAL]; + final Random random = new SecureRandom(); + + for (int i = 0; i < result.length; i++) { + result[i] = random.nextBoolean() ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); + } + + return result; + } + + private static void deliverUpdates(boolean isMultiThread, BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); + final BoundedContext boundedContext = spy(Given.boundedContext(stand, + isMultiThread ? + Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); + } + + + // Was called ONCE + verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); + verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); + } + + private static BoundedContextAction aggregateRepositoryDispatch() { + return new BoundedContextAction() { @Override public void perform(BoundedContext context) { // Init repository @@ -201,31 +235,32 @@ public void perform(BoundedContext context) { // Handle null event dispatch after command handling. Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); } - } - }); + }; } - private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { - checkNotNull(dispatchActions); + private static BoundedContextAction projectionRepositoryDispatch() { + return new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - for (BoundedContextAction dispatchAction : dispatchActions) { - dispatchAction.perform(boundedContext); - } + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }; + } - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); - } @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { - final int threadsCount = 10; + final int threadsCount = Given.THREADS_COUNT_IN_POOL_EXECUTOR; @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name final int threadExecutionMaxAwaitSeconds = 2; From e1256f459553e1e99e48c31366f8b665dd3432c9 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:10:41 +0300 Subject: [PATCH 096/361] Add creation test covering various Builder params. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 486697b153e..5b39a7d1122 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -166,8 +166,8 @@ public void fail_to_initialize_from_empty_builder() { // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** /** - * - Deliver updates from projection projectionRepo on update; - * - deliver updates from aggregate projectionRepo on update; + * - Deliver updates from projection repo on update; + * - deliver updates from aggregate repo on update; * - deliver the updates from several projection and aggregate repositories. */ From f2697c216bf93ffab4b3eb7b182f5324d953230a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:37:55 +0300 Subject: [PATCH 097/361] Add some more tests (need to be processed). --- .../org/spine3/server/stand/StandFunnelShould.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 5b39a7d1122..04054ea196e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -105,16 +105,14 @@ public void execute(Runnable neverCalled) { } @Test public void deliver_mock_updates_to_stand() { - final Object id = new Object(); - final Any state = Any.getDefaultInstance(); - - final Stand stand = mock(Stand.class); - doNothing().when(stand).update(id, state); + final Stand stand = spy(TestStandFactory.create()); final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) .build(); + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); funnel.post(id, state); verify(stand).update(id, state); @@ -149,13 +147,14 @@ public void execute(Runnable command) { @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_inproper_stand() { + public void fail_to_initialize_with_null_stand() { @SuppressWarnings("ConstantConditions") final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); builder.build(); } + @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) public void fail_to_initialize_from_empty_builder() { From 21fd7ae9c748f53ab2834b951b64f39e1fc36121 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 15:16:06 +0300 Subject: [PATCH 098/361] Fix deliver mock updates test. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 04054ea196e..59eeb7211d9 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -111,8 +111,6 @@ public void deliver_mock_updates_to_stand() { .setStand(stand) .build(); - final Object id = new Object(); - final Any state = Any.getDefaultInstance(); funnel.post(id, state); verify(stand).update(id, state); @@ -154,7 +152,6 @@ public void fail_to_initialize_with_null_stand() { builder.build(); } - @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) public void fail_to_initialize_from_empty_builder() { From 83da39fa7af6195ca0405b101b115451770626e8 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 099/361] Explicit "Given" for stand tests. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index ccad28106a2..c9bcb9499ac 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -126,7 +126,7 @@ public void handle(ProjectCreated event, EventContext context) { } } - /*package*/ static Event validEvent() { + /*package*/ static Event validEvent() { return Event.newBuilder() .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) From 10f2add7994c88eba884adc7b8f708216c7bfd54 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:14:40 +0300 Subject: [PATCH 100/361] Add threading test. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 59eeb7211d9..338d67bf343 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,6 +31,8 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.security.SecureRandom; import java.util.Map; import java.util.Random; @@ -167,6 +169,8 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + + @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_from_projection_repository() { deliverUpdates(false, projectionRepositoryDispatch()); From c22281568e8f90076567f59e49ad9d60c389afa7 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:54:11 +0300 Subject: [PATCH 101/361] Add deliver updates form projection repo test skeleton, --- .../src/test/java/org/spine3/server/stand/StandFunnelShould.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 338d67bf343..ca3ba5e60ed 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -50,6 +50,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Alex Tymchenko From 7f8665b85dddaf2d06a79718c2abb696e7cce061 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:21:34 +0300 Subject: [PATCH 102/361] Refactor and add delivery tests for several repositories successively and in parallel. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ca3ba5e60ed..b6197714be4 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,7 +31,9 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; +import java.security.SecureRandom; import java.util.Map; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.security.SecureRandom; import java.util.Map; From b3b7a1fbf36d86775eef8dc4a568423ce047a46b Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:47:07 +0300 Subject: [PATCH 103/361] Fix failing build by adding "await" for a concurrent test. --- .../java/org/spine3/server/stand/Given.java | 1 + .../spine3/server/stand/StandFunnelShould.java | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index c9bcb9499ac..6ab3e9b5145 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -57,6 +57,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); + public static final int AWAIT_SECONDS = 2; private Given() { } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index b6197714be4..19eb9f85e96 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -217,12 +217,23 @@ private static void deliverUpdates(boolean isMultiThread, BoundedContextAction.. dispatchAction.perform(boundedContext); } - - // Was called ONCE + // Was called as much times as there are dispatch actions. verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); + + if (isMultiThread) { + await(Given.AWAIT_SECONDS); + } + verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); } + private static void await(int seconds) { + try { + Thread.sleep(seconds); + } catch (InterruptedException ignore) { + } + } + private static BoundedContextAction aggregateRepositoryDispatch() { return new BoundedContextAction() { @Override @@ -259,13 +270,12 @@ public void perform(BoundedContext context) { } - @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = Given.THREADS_COUNT_IN_POOL_EXECUTOR; @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name - final int threadExecutionMaxAwaitSeconds = 2; + final int threadExecutionMaxAwaitSeconds = Given.AWAIT_SECONDS; final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); From a5528d57450dfd84ce94f8df09ac3122b454e20c Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:22:03 +0300 Subject: [PATCH 104/361] Fix issues form PR. --- .../src/test/java/org/spine3/server/stand/Given.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 6ab3e9b5145..7f26931bdd8 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,6 +20,7 @@ package org.spine3.server.stand; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import com.google.protobuf.Message; import org.spine3.base.Command; @@ -139,7 +140,7 @@ public void handle(ProjectCreated event, EventContext context) { .setDoNotEnrich(true) .setCommandContext(CommandContext.getDefaultInstance()) .setEventId(EventId.newBuilder() - .setUuid(PROJECT_UUID) + .setUuid(Identifiers.newUuid()) .build())) .build(); } @@ -161,12 +162,7 @@ public void handle(ProjectCreated event, EventContext context) { /*package*/ static BoundedContext boundedContext(Stand stand, int concurrentThreads) { final Executor executor = concurrentThreads > 0 ? Executors.newFixedThreadPool(concurrentThreads) : - new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }; + MoreExecutors.directExecutor(); return boundedContextBuilder(stand) .setStandFunnelExecutor(executor) From 7a794f7428a104ff9531200dd2053df0555c7821 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:36:39 +0300 Subject: [PATCH 105/361] Increase await time for concurrent check execution. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 7f26931bdd8..d1bbd98a8d5 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - public static final int AWAIT_SECONDS = 2; + public static final int AWAIT_SECONDS = 4; private Given() { } From 8669683a169bf9eea3a59ef3a677dc3ef4276786 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:51:17 +0300 Subject: [PATCH 106/361] Increase await time for concurrent check execution again. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index d1bbd98a8d5..d5481c2d6bf 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - public static final int AWAIT_SECONDS = 4; + /*package*/ static final int AWAIT_SECONDS = 10; private Given() { } From a383f882c6d43b7076aa51075084156b7c0e3cff Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:52:48 +0300 Subject: [PATCH 107/361] Reduce randomness of generated test data. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index d5481c2d6bf..fa15cd2f6df 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - /*package*/ static final int AWAIT_SECONDS = 10; + /*package*/ static final int AWAIT_SECONDS = 6; private Given() { } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 19eb9f85e96..b91d293681d 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -199,7 +199,7 @@ private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { final Random random = new SecureRandom(); for (int i = 0; i < result.length; i++) { - result[i] = random.nextBoolean() ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); + result[i] = (i % 2 == 0) ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); } return result; From 57f08a9aa053b04a40b2e37c33de1161a8a8e784 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 23:29:36 +0300 Subject: [PATCH 108/361] Remove redundant imports. --- .../src/test/java/org/spine3/server/stand/StandShould.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 574998ef00d..dd464363e70 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -45,15 +45,12 @@ import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.Given; -import org.spine3.server.projection.Projection; -import org.spine3.server.projection.ProjectionRepository; -import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.stand.Given.StandTestProjectionRepository; +import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; -import org.spine3.test.projection.ProjectId; import javax.annotation.Nullable; import java.util.Collection; From cf7b94fe976d86a317803e65a1bdc5e66a628d64 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 15 Sep 2016 16:20:16 +0300 Subject: [PATCH 109/361] Add a straightforward Stand#watch() test. --- .../org/spine3/server/stand/StandShould.java | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 35170e85d6b..9228d51f346 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -331,6 +331,26 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { doCheckReadingProjectsById(TOTAL_PROJECTS_FOR_BATCH_READING); } + @Test + public void trigger_callback_upon_change_of_watched_aggregate_state() { + final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); + stand.watch(customerType, memoizeCallback); + assertNull(memoizeCallback.newEntityState); + + final Map.Entry sampleData = fillSampleCustomers(1).entrySet() + .iterator() + .next(); + final CustomerId customerId = sampleData.getKey(); + final Customer customer = sampleData.getValue(); + final Any packedState = AnyPacker.pack(customer); + stand.update(customerId, packedState); + + assertEquals(packedState, memoizeCallback.newEntityState); + } + private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. @@ -388,9 +408,8 @@ private static void doCheckReadingProjectsById(int numberOfProjects) { private static void doCheckReadingCustomersById(int numberOfCustomers) { // Define the types and values used as a test data. - final Map sampleCustomers = newHashMap(); final TypeUrl customerType = TypeUrl.of(Customer.class); - fillSampleCustomers(sampleCustomers, numberOfCustomers); + final Map sampleCustomers = fillSampleCustomers(numberOfCustomers); // Prepare the stand and its mock storage to act. final StandStorage standStorageMock = mock(StandStorage.class); @@ -597,7 +616,8 @@ private static void triggerMultipleUpdates(Map sampleCusto } } - private static void fillSampleCustomers(Map sampleCustomers, int numberOfCustomers) { + private static Map fillSampleCustomers(int numberOfCustomers) { + final Map sampleCustomers = newHashMap(); for (int customerIndex = 0; customerIndex < numberOfCustomers; customerIndex++) { final Customer customer = Customer.getDefaultInstance(); @@ -608,6 +628,7 @@ private static void fillSampleCustomers(Map sampleCustomer .build(); sampleCustomers.put(customerId, customer); } + return sampleCustomers; } private static void fillSampleProjects(Map sampleProjects, int numberOfProjects) { @@ -745,4 +766,18 @@ public void onCompleted() { } } + + + /** + * A {@link StreamObserver} storing the state of {@link Query} execution. + */ + private static class MemoizeStandUpdateCallback implements Stand.StandUpdateCallback { + + private Any newEntityState; + + @Override + public void onEntityStateUpdate(Any newEntityState) { + this.newEntityState = newEntityState; + } + } } From 1659a0a402e7e12a6227597add9a0fe5edcfb3a0 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 15 Sep 2016 16:26:26 +0300 Subject: [PATCH 110/361] * Define SubscriptionService and extract related objects as separate .proto files; * remove the SubscriptionService-related methods from the ClientService; * perform import cleanup in the modified files. --- .../src/main/proto/spine/client/client.proto | 1 - .../proto/spine/client/client_service.proto | 15 ---- .../main/proto/spine/client/entities.proto | 3 - .../proto/spine/client/query_service.proto | 1 - .../proto/spine/client/subscription.proto | 83 +++++++++++++++++++ .../spine/client/subscription_service.proto | 46 ++++++++++ .../java/org/spine3/server/ClientService.java | 19 ----- 7 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 client/src/main/proto/spine/client/subscription.proto create mode 100644 client/src/main/proto/spine/client/subscription_service.proto diff --git a/client/src/main/proto/spine/client/client.proto b/client/src/main/proto/spine/client/client.proto index a20e7cb2e7b..c9f439fd170 100644 --- a/client/src/main/proto/spine/client/client.proto +++ b/client/src/main/proto/spine/client/client.proto @@ -28,7 +28,6 @@ option java_outer_classname = "ClientProto"; option java_package = "org.spine3.client"; import "spine/annotations.proto"; -import "spine/base/event.proto"; // Version of the code executed on the client. message CodeVersion { diff --git a/client/src/main/proto/spine/client/client_service.proto b/client/src/main/proto/spine/client/client_service.proto index e8a347e8133..d17e8e03585 100644 --- a/client/src/main/proto/spine/client/client_service.proto +++ b/client/src/main/proto/spine/client/client_service.proto @@ -31,26 +31,11 @@ option java_generate_equals_and_hash = true; import "spine/annotations.proto"; import "spine/base/command.proto"; -import "spine/base/event.proto"; import "spine/base/response.proto"; -// A topic of interest the client can subscribe and unsubscribe. -message Topic { - //TODO:2016-01-14:alexander.yevsyukov: Define this type. E.g. there can be some structure, which describes many - // points of interest at once. See Pub-sub for possible API inspiration. Chances are it's going to be one of underlying - // implementations. - string value = 1; -} // A service for sending commands from clients. service ClientService { // Request to handle a command. rpc Post(base.Command) returns (base.Response); - - // Request to receive events on the topic of interest. - rpc Subscribe(Topic) returns (stream base.Event); - - // The request to unsubscribe from the topic. - // This should close the stream opened by `Subscribe` call with the same `Topic` value. - rpc Unsubscribe(Topic) returns (base.Response); } diff --git a/client/src/main/proto/spine/client/entities.proto b/client/src/main/proto/spine/client/entities.proto index 617ef40e6d2..ab9d3596f09 100644 --- a/client/src/main/proto/spine/client/entities.proto +++ b/client/src/main/proto/spine/client/entities.proto @@ -28,11 +28,8 @@ option java_outer_classname = "EntitiesProto"; option java_package = "org.spine3.client"; import "google/protobuf/any.proto"; -import "google/protobuf/field_mask.proto"; import "spine/annotations.proto"; -import "spine/ui/language.proto"; -import "spine/base/response.proto"; // Represents an ID of an entity. // diff --git a/client/src/main/proto/spine/client/query_service.proto b/client/src/main/proto/spine/client/query_service.proto index 4689c7d8c38..36e7afec695 100644 --- a/client/src/main/proto/spine/client/query_service.proto +++ b/client/src/main/proto/spine/client/query_service.proto @@ -27,7 +27,6 @@ option java_multiple_files = true; option java_outer_classname = "QueryServiceProto"; option java_generate_equals_and_hash = true; -import "google/protobuf/any.proto"; import "spine/annotations.proto"; import "spine/client/query.proto"; diff --git a/client/src/main/proto/spine/client/subscription.proto b/client/src/main/proto/spine/client/subscription.proto new file mode 100644 index 00000000000..ddd579cd8c0 --- /dev/null +++ b/client/src/main/proto/spine/client/subscription.proto @@ -0,0 +1,83 @@ +// +// Copyright 2016, TeamDev Ltd. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +syntax = "proto3"; + +package spine.client; + +option (type_url_prefix) = "type.spine3.org"; +option java_generate_equals_and_hash = true; +option java_multiple_files = true; +option java_outer_classname = "SubscriptionProto"; +option java_package = "org.spine3.client"; + + +import "google/protobuf/any.proto"; +import "google/protobuf/field_mask.proto"; + +import "spine/annotations.proto"; +import "spine/base/response.proto"; +import "spine/client/entities.proto"; + +// An object defining a unit of subscription. +// +// Defines the target (entities and criteria) of subscription. +message Topic { + + // Defines the entity of interest, e.g. entity type URL and a set of subscription criteria. + Target target = 1; + + // Field mask to be applied to the entity updates applicable to this topic. + // + // Applied to each of the entity state messages before returning in scope of SubscriptionUpdate. + google.protobuf.FieldMask field_mask = 2; + + // Reserved for utility fields. + reserved 3 to 6; +} + +// Wrapped set of read-side entity updates on a topic with the specific subscription ID. +message SubscriptionUpdate { + + // The ID of the current subscription. + SubscriptionId id = 1; + + // Represents the base part of the response. I.e. whether the Topic subscription requires has been acked or not. + base.Response response = 2; + + // Reserved for more subscription update attributes. + reserved 3 to 9; + + // Entity updates packed as Any. + // + // Each of the update messages is affected by the field mask set for the current subscription. + repeated google.protobuf.Any updates = 10; +} + +// The ID of the subscription. +// +// Created when the client subscribes to a topic. +// See SubscriptionService#Subscribe(Topic). +message SubscriptionId { + + // Unique identifier of the subscription. + // + // Typically built using Java's UUID.toString() functionality. + string uuid = 1; +} diff --git a/client/src/main/proto/spine/client/subscription_service.proto b/client/src/main/proto/spine/client/subscription_service.proto new file mode 100644 index 00000000000..0f679b04671 --- /dev/null +++ b/client/src/main/proto/spine/client/subscription_service.proto @@ -0,0 +1,46 @@ +// +// Copyright 2016, TeamDev Ltd. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +syntax = "proto3"; + +package spine.client; + +option (type_url_prefix) = "type.spine3.org"; +option java_package = "org.spine3.client.grpc"; +option java_multiple_files = true; +option java_outer_classname = "SubscriptionServiceProto"; +option java_generate_equals_and_hash = true; + + +import "spine/annotations.proto"; +import "spine/client/subscription.proto"; +import "spine/base/response.proto"; + +// A service for subscribing to the read-side changes by clients. +service SubscriptionService { + + // Subscribe to a particular read-side updates. + // + // Topic defines the target of subscription and other attributes (like field masks). + // The result is a SubscriptionUpdate stream, + rpc Subscribe (Topic) returns (stream SubscriptionUpdate); + + // Cancel the subscription by its ID. + rpc CancelSubscription (SubscriptionId) returns (base.Response); +} diff --git a/server/src/main/java/org/spine3/server/ClientService.java b/server/src/main/java/org/spine3/server/ClientService.java index 7728c93007a..1ab5f73018b 100644 --- a/server/src/main/java/org/spine3/server/ClientService.java +++ b/server/src/main/java/org/spine3/server/ClientService.java @@ -26,10 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spine3.base.Command; -import org.spine3.base.Event; import org.spine3.base.Response; -import org.spine3.base.Responses; -import org.spine3.client.grpc.Topic; import org.spine3.server.command.error.CommandException; import org.spine3.server.command.error.UnsupportedCommandException; import org.spine3.server.type.CommandClass; @@ -75,22 +72,6 @@ private static void handleUnsupported(Command request, StreamObserver responseObserver.onError(Statuses.invalidArgumentWithCause(unsupported)); } - @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. - @Override - public void subscribe(Topic request, StreamObserver responseObserver) { - //TODO:2016-05-25:alexander.yevsyukov: Subscribe the client to the topic in the corresponding BoundedContext. - // This API is likely to change to support Firebase-like registration where listening is - // done by the client SDK implementation. - } - - @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. - @Override - public void unsubscribe(Topic request, StreamObserver responseObserver) { - //TODO:2016-05-25:alexander.yevsyukov: Unsubscribe the client from the topic in the corresponding BoundedContext. - responseObserver.onNext(Responses.ok()); - responseObserver.onCompleted(); - } - public static class Builder { private final Set boundedContexts = Sets.newHashSet(); From a23c6b848351acfcc9513f58f65d9b8fce229508 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 16:32:06 +0300 Subject: [PATCH 111/361] Merge. --- .../java/org/spine3/server/stand/Given.java | 2 +- .../server/stand/StandFunnelShould.java | 10 +++---- .../org/spine3/server/stand/StandShould.java | 29 ++++++++++--------- .../org/spine3/testdata/TestStandFactory.java | 5 ++++ 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index fa15cd2f6df..2184ac0511d 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -109,7 +109,7 @@ public void handle(ProjectCreated event) { } } - private static class StandTestProjection extends Projection { + /*package*/ static class StandTestProjection extends Projection { /** * Creates a new instance. * diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index b91d293681d..5ecf2b8facc 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,10 +31,6 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; -import java.security.SecureRandom; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; import java.security.SecureRandom; import java.util.Map; import java.util.Random; @@ -52,7 +48,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -110,7 +105,10 @@ public void execute(Runnable neverCalled) { } @Test public void deliver_mock_updates_to_stand() { - final Stand stand = spy(TestStandFactory.create()); + final Stand stand = TestStandFactory.createMock(); + doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index dd464363e70..50197c17bae 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -44,13 +44,16 @@ import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; -import org.spine3.server.Given; +import org.spine3.server.Given.CustomerAggregate; +import org.spine3.server.Given.CustomerAggregateRepository; +import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; import javax.annotation.Nullable; import java.util.Collection; @@ -138,7 +141,7 @@ public void register_aggregate_repositories() { checkTypesEmpty(stand); - final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository customerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); final Descriptors.Descriptor customerEntityDescriptor = Customer.getDescriptor(); @@ -146,7 +149,7 @@ public void register_aggregate_repositories() { checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); @SuppressWarnings("LocalVariableNamingConvention") - final Given.CustomerAggregateRepository anotherCustomerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository anotherCustomerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(anotherCustomerAggregateRepo); checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); @@ -183,7 +186,7 @@ public void operate_with_storage_provided_through_builder() { assertNotNull(stand); final BoundedContext boundedContext = newBoundedContext(stand); - final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository customerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); @@ -191,7 +194,7 @@ public void operate_with_storage_provided_through_builder() { final CustomerId customerId = CustomerId.newBuilder() .setNumber(numericIdValue) .build(); - final Given.CustomerAggregate customerAggregate = customerAggregateRepo.create(customerId); + final CustomerAggregate customerAggregate = customerAggregateRepo.create(customerId); final Customer customerState = customerAggregate.getState(); final Any packedState = AnyPacker.pack(customerState); final TypeUrl customerType = TypeUrl.of(Customer.class); @@ -478,10 +481,10 @@ private static void setupExpectedFindAllBehaviour(Map sample StandTestProjectionRepository projectionRepository) { final Set projectIds = sampleProjects.keySet(); - final ImmutableCollection allResults = toProjectionCollection(projectIds); + final ImmutableCollection allResults = toProjectionCollection(projectIds); for (ProjectId projectId : projectIds) { - when(projectionRepository.find(eq(projectId))).thenReturn(new StandTestProjection(projectId)); + when(projectionRepository.find(eq(projectId))).thenReturn(new Given.StandTestProjection(projectId)); } final Iterable matchingIds = argThat(projectionIdsIterableMatcher(projectIds)); @@ -517,16 +520,16 @@ public boolean matches(EntityFilters argument) { }; } - private static ImmutableCollection toProjectionCollection(Collection values) { - final Collection transformed = Collections2.transform(values, new Function() { + private static ImmutableCollection toProjectionCollection(Collection values) { + final Collection transformed = Collections2.transform(values, new Function() { @Nullable @Override - public StandTestProjection apply(@Nullable ProjectId input) { + public Given.StandTestProjection apply(@Nullable ProjectId input) { checkNotNull(input); - return new StandTestProjection(input); + return new Given.StandTestProjection(input); } }); - final ImmutableList result = ImmutableList.copyOf(transformed); + final ImmutableList result = ImmutableList.copyOf(transformed); return result; } @@ -640,7 +643,7 @@ private static Stand prepareStandWithAggregateRepo(StandStorage standStorageMock assertNotNull(stand); final BoundedContext boundedContext = newBoundedContext(stand); - final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final org.spine3.server.Given.CustomerAggregateRepository customerAggregateRepo = new org.spine3.server.Given.CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); return stand; } diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index 474a6226b30..63477a00a7c 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -21,6 +21,7 @@ */ package org.spine3.testdata; +import org.mockito.Mockito; import org.spine3.server.stand.Stand; /** @@ -38,4 +39,8 @@ public static Stand create() { .build(); return stand; } + + public static Stand createMock() { + return Mockito.mock(Stand.class); + } } From 7cc18e231d4ee3ea95686c909762f1b82af13788 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 16:49:57 +0300 Subject: [PATCH 112/361] Address issues in PR. --- .../server/stand/StandFunnelShould.java | 82 ++++++------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 5ecf2b8facc..43cf6ed579c 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -21,7 +21,9 @@ */ package org.spine3.server.stand; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import io.netty.util.internal.ConcurrentSet; import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatchers; @@ -31,14 +33,10 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; -import java.security.SecureRandom; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; @@ -71,36 +69,14 @@ public void initialize_properly_with_stand_only() { } @Test - public void initialize_properly_with_various_builder_options() { + public void initialize_properly_with_all_options() { final Stand stand = TestStandFactory.create(); - final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return Thread.currentThread(); - } - }); - final StandFunnel blockingFunnel = StandFunnel.newBuilder() + final StandFunnel standFunnel = StandFunnel.newBuilder() .setStand(stand) - .setExecutor(executor) + .setExecutor(Executors.newSingleThreadExecutor()) .build(); - Assert.assertNotNull(blockingFunnel); - - final StandFunnel funnelForBusyStand = StandFunnel.newBuilder() - .setStand(stand) - .setExecutor(Executors.newSingleThreadExecutor()) - .build(); - Assert.assertNotNull(funnelForBusyStand); - - - final StandFunnel emptyExecutorFunnel = StandFunnel.newBuilder() - .setStand(TestStandFactory.create()) - .setExecutor(new Executor() { - @Override - public void execute(Runnable neverCalled) { } - }) - .build(); - Assert.assertNotNull(emptyExecutorFunnel); + Assert.assertNotNull(standFunnel); } @Test @@ -122,13 +98,8 @@ public void deliver_mock_updates_to_stand() { @Test public void use_executor_from_builder() { - final Stand stand = spy(TestStandFactory.create()); - final Executor executor = spy(new Executor() { - @Override - public void execute(Runnable command) { - - } - }); + final Stand stand = TestStandFactory.createMock(); + final Executor executor = spy(MoreExecutors.directExecutor()); final StandFunnel.Builder builder = StandFunnel.newBuilder() .setStand(stand) .setExecutor(executor); @@ -174,27 +145,26 @@ public void fail_to_initialize_from_empty_builder() { @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_from_projection_repository() { - deliverUpdates(false, projectionRepositoryDispatch()); + checkUpdatesDelivery(false, projectionRepositoryDispatch()); } @Test public void deliver_updates_form_aggregate_repository() { - deliverUpdates(false, aggregateRepositoryDispatch()); + checkUpdatesDelivery(false, aggregateRepositoryDispatch()); } @Test public void deliver_updates_from_several_repositories_in_single_thread() { - deliverUpdates(false, getSeveralRepositoryDispatchCalls()); + checkUpdatesDelivery(false, getSeveralRepositoryDispatchCalls()); } @Test public void deliver_updates_from_several_repositories_in_multiple_threads() { - deliverUpdates(true, getSeveralRepositoryDispatchCalls()); + checkUpdatesDelivery(true, getSeveralRepositoryDispatchCalls()); } private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { final BoundedContextAction[] result = new BoundedContextAction[Given.SEVERAL]; - final Random random = new SecureRandom(); for (int i = 0; i < result.length; i++) { result[i] = (i % 2 == 0) ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); @@ -203,22 +173,22 @@ private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { return result; } - private static void deliverUpdates(boolean isMultiThread, BoundedContextAction... dispatchActions) { + private static void checkUpdatesDelivery(boolean isMultiThreaded, BoundedContextAction... dispatchActions) { checkNotNull(dispatchActions); final Stand stand = mock(Stand.class); final BoundedContext boundedContext = spy(Given.boundedContext(stand, - isMultiThread ? + isMultiThreaded ? Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); for (BoundedContextAction dispatchAction : dispatchActions) { dispatchAction.perform(boundedContext); } - // Was called as much times as there are dispatch actions. + // Was called as many times as there are dispatch actions. verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); - if (isMultiThread) { + if (isMultiThreaded) { await(Given.AWAIT_SECONDS); } @@ -244,8 +214,10 @@ public void perform(BoundedContext context) { try { repository.dispatch(Given.validCommand()); } catch (IllegalStateException e) { - // Handle null event dispatch after command handling. - Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + // Handle null event dispatching after the command is handled. + if(!e.getMessage().contains("No record found for command ID: EMPTY")) { + throw e; + } } } }; @@ -275,7 +247,7 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name final int threadExecutionMaxAwaitSeconds = Given.AWAIT_SECONDS; - final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); + final Set threadInvocationRegistry = new ConcurrentSet<>(); final Stand stand = mock(Stand.class); doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -284,26 +256,26 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio .setStand(stand) .build(); - final ExecutorService processes = Executors.newFixedThreadPool(threadsCount); + final ExecutorService executor = Executors.newFixedThreadPool(threadsCount); final Runnable task = new Runnable() { @Override public void run() { final String threadName = Thread.currentThread().getName(); - Assert.assertFalse(threadInvocationRegistry.containsKey(threadName)); + Assert.assertFalse(threadInvocationRegistry.contains(threadName)); standFunnel.post(new Object(), Any.getDefaultInstance()); - threadInvocationRegistry.put(threadName, new Object()); + threadInvocationRegistry.add(threadName); } }; for (int i = 0; i < threadsCount; i++) { - processes.execute(task); + executor.execute(task); } - processes.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); + executor.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); Assert.assertEquals(threadInvocationRegistry.size(), threadsCount); From 36b6d957c86dd680d830425a8638545667cdf485 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 16:56:10 +0300 Subject: [PATCH 113/361] Fix broken test. --- .../java/org/spine3/server/stand/StandFunnelShould.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 43cf6ed579c..e69e1a30c30 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -21,7 +21,6 @@ */ package org.spine3.server.stand; -import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import io.netty.util.internal.ConcurrentSet; import org.junit.Assert; @@ -99,7 +98,11 @@ public void deliver_mock_updates_to_stand() { @Test public void use_executor_from_builder() { final Stand stand = TestStandFactory.createMock(); - final Executor executor = spy(MoreExecutors.directExecutor()); + final Executor executor = spy(new Executor() { + @Override + public void execute(Runnable command) { + } + }); final StandFunnel.Builder builder = StandFunnel.newBuilder() .setStand(stand) .setExecutor(executor); From 9b9dde6e23973edc4a94b8c914a9c62d963a2d5b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 15 Sep 2016 17:08:01 +0300 Subject: [PATCH 114/361] Resolve compilation errors after moving Topic message. --- .../java/org/spine3/examples/aggregate/ClientApp.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java b/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java index c4599abcb6c..9df51a9117c 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java @@ -31,7 +31,6 @@ import org.spine3.base.Response; import org.spine3.client.CommandFactory; import org.spine3.client.grpc.ClientServiceGrpc; -import org.spine3.client.grpc.Topic; import org.spine3.examples.aggregate.command.AddOrderLine; import org.spine3.examples.aggregate.command.CreateOrder; import org.spine3.examples.aggregate.command.PayForOrder; @@ -66,9 +65,9 @@ public class ClientApp { private static final int SHUTDOWN_TIMEOUT_SEC = 5; private final CommandFactory commandFactory; - private final Topic topic = Topic.getDefaultInstance(); private final ManagedChannel channel; private final ClientServiceGrpc.ClientServiceBlockingStub blockingClient; + // TODO[alex.tymchenko]: switch to SubscriptionService instead. private final ClientServiceGrpc.ClientServiceStub nonBlockingClient; private final StreamObserver observer = new StreamObserver() { @@ -140,14 +139,10 @@ private Command payForOrder(OrderId orderId) { return commandFactory.create(msg); } - private void subscribe() { - nonBlockingClient.subscribe(topic, observer); - } /** Sends requests to the server. */ public static void main(String[] args) throws InterruptedException { final ClientApp client = new ClientApp(SERVICE_HOST, DEFAULT_CLIENT_SERVICE_PORT); - client.subscribe(); final List requests = client.generateRequests(); @@ -178,7 +173,6 @@ private List generateRequests() { * @throws InterruptedException if waiting is interrupted. */ private void shutdown() throws InterruptedException { - blockingClient.unsubscribe(topic); channel.shutdown().awaitTermination(SHUTDOWN_TIMEOUT_SEC, SECONDS); } From ed8784b4346fc6730f75cc268bc8128c6c350c33 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:10:41 +0300 Subject: [PATCH 115/361] Add creation test covering various Builder params. --- .../server/stand/StandFunnelShould.java | 36 ++++++++++++++++++- .../org/spine3/testdata/TestStandFactory.java | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 9e42f37e68e..5eb8fb42bdf 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -27,6 +27,8 @@ import org.spine3.testdata.TestStandFactory; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; @@ -40,7 +42,6 @@ public class StandFunnelShould { // **** Positive scenarios (unit) **** /** - * - Initialize properly with various Builder options; * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. */ @@ -53,6 +54,39 @@ public void initialize_properly_with_stand_only() { Assert.assertNotNull(standFunnel); } + @Test + public void initialize_properly_with_various_builder_options() { + final Stand stand = TestStandFactory.create(); + final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return Thread.currentThread(); + } + }); + + final StandFunnel blockingFunnel = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(executor) + .build(); + Assert.assertNotNull(blockingFunnel); + + final StandFunnel funnelForBusyStand = StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(Executors.newSingleThreadExecutor()) + .build(); + Assert.assertNotNull(funnelForBusyStand); + + + final StandFunnel emptyExecutorFunnel = StandFunnel.newBuilder() + .setStand(TestStandFactory.create()) + .setExecutor(new Executor() { + @Override + public void execute(Runnable neverCalled) { } + }) + .build(); + Assert.assertNotNull(emptyExecutorFunnel); + } + @Test public void use_executor_from_builder() { diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index d54df4fae6d..474a6226b30 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -22,13 +22,13 @@ package org.spine3.testdata; import org.spine3.server.stand.Stand; -import org.spine3.server.storage.memory.InMemoryStandStorage; /** * Creates stands for tests. * * @author Alex Tymchenko */ +@SuppressWarnings("UtilityClass") public class TestStandFactory { private TestStandFactory() {} From c5ab931fbfe8cf8257ee636382e965ff45965e18 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:11:56 +0300 Subject: [PATCH 116/361] Bump gradle version, --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 58e7b927f5d..6d19c1542b6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip From 756df6429802a48a01d8701ade0636bccce3898e Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 14:37:55 +0300 Subject: [PATCH 117/361] Add some more tests (need to be processed). --- .../org/spine3/server/stand/StandFunnel.java | 2 ++ .../server/stand/StandFunnelShould.java | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 9e3cc659ce0..44997adbdf8 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -109,6 +109,7 @@ public Stand getStand() { *

The value must not be null. * *

If this method is not used, a default value will be used. + * // TODO:13-09-16:dmytro.dashenkov: Correct docs. No default value for Stand is used, null value leads to a fail instead. * * @param stand the instance of {@link Stand}. * @return {@code this} instance of {@code Builder} @@ -122,6 +123,7 @@ public Executor getExecutor() { return executor; } + // TODO:13-09-16:dmytro.dashenkov: Complete docs. Here is the place where a default value is used in case of not using the method. /** * Set the {@code Executor} instance for this {@code StandFunnel}. * diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 5eb8fb42bdf..da5f2bffdfd 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -87,6 +87,21 @@ public void execute(Runnable neverCalled) { } Assert.assertNotNull(emptyExecutorFunnel); } + @Test + public void deliver_mock_updates_to_stand() { + final Stand stand = spy(TestStandFactory.create()); + + final StandFunnel funnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + funnel.post(id, state); + + verify(stand).update(id, state); + } + @Test public void use_executor_from_builder() { @@ -114,9 +129,23 @@ public void execute(Runnable command) { // **** Negative scenarios (unit) **** - /** - * - Fail to initialise with improper stand. - */ + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_null_stand() { + @SuppressWarnings("ConstantConditions") + final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); + + builder.build(); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = NullPointerException.class) + public void fail_to_initialize_with_importer_stand() { + // TODO:13-09-16:dmytro.dashenkov: Implement. +// @SuppressWarnings("ConstantConditions") +// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); +// builder.build(); + } @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) From 26fb807f08c2004dbcde4e5942b93ea59f31adc6 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 13 Sep 2016 15:16:06 +0300 Subject: [PATCH 118/361] Fix deliver mock updates test. --- .../server/stand/StandFunnelShould.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index da5f2bffdfd..ef7ba6c2b08 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,6 +31,8 @@ import java.util.concurrent.ThreadFactory; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -89,14 +91,16 @@ public void execute(Runnable neverCalled) { } @Test public void deliver_mock_updates_to_stand() { - final Stand stand = spy(TestStandFactory.create()); + final Object id = new Object(); + final Any state = Any.getDefaultInstance(); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(id, state); final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) .build(); - final Object id = new Object(); - final Any state = Any.getDefaultInstance(); funnel.post(id, state); verify(stand).update(id, state); @@ -131,22 +135,13 @@ public void execute(Runnable command) { @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_null_stand() { + public void fail_to_initialize_with_inproper_stand() { @SuppressWarnings("ConstantConditions") final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); builder.build(); } - @SuppressWarnings("ResultOfMethodCallIgnored") - @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_importer_stand() { - // TODO:13-09-16:dmytro.dashenkov: Implement. -// @SuppressWarnings("ConstantConditions") -// final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); -// builder.build(); - } - @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalStateException.class) public void fail_to_initialize_from_empty_builder() { @@ -162,5 +157,10 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repo() { + + } + } From abec37b74eeb37f7da6d8555f504cd3bfcb5ab94 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 119/361] Explicit "Given" for stand tests. --- .../java/org/spine3/server/stand/Given.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 server/src/test/java/org/spine3/server/stand/Given.java diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java new file mode 100644 index 00000000000..f1b54d0adfe --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.stand; + +import org.spine3.base.Event; +import org.spine3.base.EventContext; +import org.spine3.protobuf.AnyPacker; +import org.spine3.server.BoundedContext; +import org.spine3.server.projection.Projection; +import org.spine3.server.projection.ProjectionRepository; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.event.ProjectCreated; + +/** + * @author Dmytro Dashenkov + */ +/*package*/ class Given { + + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + private static class StandTestProjection extends Projection { + /** + * Creates a new instance. + * + * Required to be public. + * + * @param id the ID for the new instance + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestProjection(ProjectId id) { + super(id); + } + } + + /*package*/ static Event validEvent() { + return Event.newBuilder() + .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() + .setProjectId(ProjectId.newBuilder().setId("12345AD0")) + .build()) + .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .setContext(EventContext.getDefaultInstance()) + .build(); + } +} From e3313a87a3f7805a833375a8f764e27e8ca01aab Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:14:40 +0300 Subject: [PATCH 120/361] Add threading test. --- .../server/stand/StandFunnelShould.java | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ef7ba6c2b08..899afd85415 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -24,11 +24,16 @@ import com.google.protobuf.Any; import org.junit.Assert; import org.junit.Test; +import org.mockito.ArgumentMatchers; import org.spine3.testdata.TestStandFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; @@ -38,6 +43,7 @@ /** * @author Alex Tymchenko + * @author Dmytro Dashenkov */ public class StandFunnelShould { @@ -157,10 +163,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + + @SuppressWarnings("MethodWithMultipleLoops") @Test - public void deliver_updates_from_projection_repo() { + public void deliver_updates_through_several_threads() throws InterruptedException { + final int threadsCount = 10; - } + final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + + final Stand stand = mock(Stand.class); + doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); + + final StandFunnel standFunnel = StandFunnel.newBuilder() + .setStand(stand) + .build(); + + final ExecutorService processes = Executors.newFixedThreadPool(threadsCount); + + + final Runnable task = new Runnable() { + @Override + public void run() { + final String threadName = Thread.currentThread().getName(); + Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + + standFunnel.post(new Object(), Any.getDefaultInstance()); + + threadInvakationRegistry.put(threadName, new Object()); + } + }; + for (int i = 0; i < threadsCount; i++) { + processes.execute(task); + } + + processes.awaitTermination(10, TimeUnit.SECONDS); + + Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + + } } From 1e4338a06eaecc5c69ac6e12144c6f377ce71bf6 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:16:54 +0300 Subject: [PATCH 121/361] Reduce executor await time. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 899afd85415..f47fa5efb48 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -168,6 +168,7 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; + final int threadExecuteionMaxAwaitSeconds = 2; final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); @@ -197,7 +198,7 @@ public void run() { processes.execute(task); } - processes.awaitTermination(10, TimeUnit.SECONDS); + processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); From ca292e9184da4202951c407cacc2ba7aa55f2932 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:54:11 +0300 Subject: [PATCH 122/361] Add deliver updates form projection repo test skeleton, --- .../java/org/spine3/server/stand/Given.java | 6 ++- .../server/stand/StandFunnelShould.java | 45 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index f1b54d0adfe..2f631d4995f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -23,6 +23,7 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; @@ -60,7 +61,10 @@ public StandTestProjection(ProjectId id) { .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) - .toBuilder().setTypeUrl(ProjectCreated.getDescriptor().getFullName()).build()) + .toBuilder() + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + + ProjectCreated.getDescriptor().getFullName()) + .build()) .setContext(EventContext.getDefaultInstance()) .build(); } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index f47fa5efb48..34e7ab2ff0f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -25,6 +25,8 @@ import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatchers; +import org.spine3.server.BoundedContext; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; import java.util.Map; @@ -40,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -163,14 +166,44 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ + @Test + public void deliver_updates_from_projection_repository() throws Exception { + final BoundedContext boundedContext = mock(BoundedContext.class); + final Stand stand = mock(Stand.class); + when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() + .setStand(stand) + .setExecutor(new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); + + // Init repository + final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); + + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } + @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = 10; - final int threadExecuteionMaxAwaitSeconds = 2; + @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name + final int threadExecutionMaxAwaitSeconds = 2; - final Map threadInvakationRegistry = new ConcurrentHashMap<>(threadsCount); + final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); final Stand stand = mock(Stand.class); doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -186,11 +219,11 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio @Override public void run() { final String threadName = Thread.currentThread().getName(); - Assert.assertFalse(threadInvakationRegistry.containsKey(threadName)); + Assert.assertFalse(threadInvocationRegistry.containsKey(threadName)); standFunnel.post(new Object(), Any.getDefaultInstance()); - threadInvakationRegistry.put(threadName, new Object()); + threadInvocationRegistry.put(threadName, new Object()); } }; @@ -198,9 +231,9 @@ public void run() { processes.execute(task); } - processes.awaitTermination(threadExecuteionMaxAwaitSeconds, TimeUnit.SECONDS); + processes.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); - Assert.assertEquals(threadInvakationRegistry.size(), threadsCount); + Assert.assertEquals(threadInvocationRegistry.size(), threadsCount); } From 1fae7e380560d50646ef3d08552734ba8f2e93b3 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:57:41 +0300 Subject: [PATCH 123/361] Suppress not yet ready test. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 34e7ab2ff0f..c924469b833 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -166,7 +166,7 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - @Test + //@Test public void deliver_updates_from_projection_repository() throws Exception { final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); From f9e01c53f7e11e5e7959a8ba936eaee2b7d4461a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 16:09:19 +0300 Subject: [PATCH 124/361] Finish delivery from projection repo test. --- .../java/org/spine3/server/stand/Given.java | 36 +++++++++++++++++-- .../server/stand/StandFunnelShould.java | 27 +++++++------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 2f631d4995f..c7da6d078b9 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,11 +20,16 @@ package org.spine3.server.stand; +import com.google.protobuf.Message; +import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; +import org.spine3.base.EventId; +import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.test.projection.Project; @@ -36,10 +41,20 @@ */ /*package*/ class Given { + private static final String PROJECT_UUID = Identifiers.newUuid(); + + private Given() { + } + /*package*/static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } + + @Override + protected ProjectId getEntityId(Message event, EventContext context) { + return ProjectId.newBuilder().setId(PROJECT_UUID).build(); + } } private static class StandTestProjection extends Projection { @@ -54,6 +69,11 @@ private static class StandTestProjection extends Projection public StandTestProjection(ProjectId id) { super(id); } + + @Subscribe + public void handle(ProjectCreated event, EventContext context) { + // Do nothing + } } /*package*/ static Event validEvent() { @@ -62,10 +82,20 @@ public StandTestProjection(ProjectId id) { .setProjectId(ProjectId.newBuilder().setId("12345AD0")) .build()) .toBuilder() - .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + '/' + - ProjectCreated.getDescriptor().getFullName()) + .setTypeUrl(TypeUrl.SPINE_TYPE_URL_PREFIX + "/" + ProjectCreated.getDescriptor().getFullName()) .build()) - .setContext(EventContext.getDefaultInstance()) + .setContext(EventContext.newBuilder() + .setDoNotEnrich(true) + .setCommandContext(CommandContext.getDefaultInstance()) + .setEventId(EventId.newBuilder() + .setUuid(PROJECT_UUID) + .build())) .build(); } + + /*package*/ static ProjectionRepository repo(BoundedContext context) { + return new StandTestProjectionRepository(context); + } + + } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index c924469b833..4c9816159f1 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -42,7 +43,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -166,22 +166,21 @@ public void fail_to_initialize_from_empty_builder() { * - deliver the updates from several projection and aggregate repositories. */ - //@Test + @Test public void deliver_updates_from_projection_repository() throws Exception { - final BoundedContext boundedContext = mock(BoundedContext.class); final Stand stand = mock(Stand.class); - when(boundedContext.getStandFunnel()).thenReturn(StandFunnel.newBuilder() - .setStand(stand) - .setExecutor(new Executor() { - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); - + final BoundedContext boundedContext = spy(BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build()); // Init repository - final Given.StandTestProjectionRepository repository = new Given.StandTestProjectionRepository(boundedContext); + final ProjectionRepository repository = Given.repo(boundedContext); stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); From 5c6e5780718d4789c23a6bccbc977ce667332f76 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:26:06 +0300 Subject: [PATCH 125/361] Finish delivery form an aggregate repo test. --- .../java/org/spine3/server/stand/Given.java | 82 ++++++++++++++++++- .../server/stand/StandFunnelShould.java | 44 ++++++---- 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index c7da6d078b9..595397ce31c 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,22 +20,34 @@ package org.spine3.server.stand; +import com.google.protobuf.Any; import com.google.protobuf.Message; +import org.spine3.base.Command; import org.spine3.base.CommandContext; import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; +import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.Aggregate; +import org.spine3.server.aggregate.AggregateRepository; +import org.spine3.server.aggregate.Apply; +import org.spine3.server.command.Assign; import org.spine3.server.event.Subscribe; import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; +import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.command.CreateProject; import org.spine3.test.projection.event.ProjectCreated; +import java.util.List; +import java.util.concurrent.Executor; + /** * @author Dmytro Dashenkov */ @@ -46,7 +58,7 @@ private Given() { } - /*package*/static class StandTestProjectionRepository extends ProjectionRepository { + /*package*/ static class StandTestProjectionRepository extends ProjectionRepository { /*package*/ StandTestProjectionRepository(BoundedContext boundedContext) { super(boundedContext); } @@ -57,6 +69,48 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } + /*package*/ static class StandTestAggregateRepository extends AggregateRepository { + + /** + * Creates a new repository instance. + * + * @param boundedContext the bounded context to which this repository belongs + */ + /*package*/ StandTestAggregateRepository(BoundedContext boundedContext) { + super(boundedContext); + } + } + + /*package*/ static class TestFailure extends FailureThrowable { + + /*package*/ TestFailure() { + super(/*generatedMessage=*/null); + } + } + + private static class StandTestAggregate extends Aggregate { + + /** + * Creates a new aggregate instance. + * + * @param id the ID for the new aggregate + * @throws IllegalArgumentException if the ID is not of one of the supported types + */ + public StandTestAggregate(ProjectId id) { + super(id); + } + + @Assign + public List handle(CreateProject createProject, CommandContext context) { + return null; + } + + @Apply + public void handle(ProjectCreated event) { + // Do nothing + } + } + private static class StandTestProjection extends Projection { /** * Creates a new instance. @@ -76,7 +130,7 @@ public void handle(ProjectCreated event, EventContext context) { } } - /*package*/ static Event validEvent() { + /*package*/ static Event validEvent() { return Event.newBuilder() .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) @@ -93,9 +147,31 @@ public void handle(ProjectCreated event, EventContext context) { .build(); } - /*package*/ static ProjectionRepository repo(BoundedContext context) { + /*package*/ static Command validCommand() { + return Command.newBuilder() + .setMessage(AnyPacker.pack(CreateProject.getDefaultInstance())) + .setContext(CommandContext.getDefaultInstance()) + .build(); + } + + /*package*/ static ProjectionRepository projectionRepo(BoundedContext context) { return new StandTestProjectionRepository(context); } + /*package*/ static AggregateRepository aggregateRepo(BoundedContext context) { + return new StandTestAggregateRepository(context); + } + /*package*/ static BoundedContext boundedContext(Stand stand) { + return BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .setStandFunnelExecutor(new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }) + .build(); + } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 4c9816159f1..ad82932db72 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.mockito.ArgumentMatchers; import org.spine3.server.BoundedContext; +import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; @@ -161,28 +162,18 @@ public void fail_to_initialize_from_empty_builder() { // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** /** - * - Deliver updates from projection repo on update; - * - deliver updates from aggregate repo on update; + * - Deliver updates from projection projectionRepo on update; + * - deliver updates from aggregate projectionRepo on update; * - deliver the updates from several projection and aggregate repositories. */ @Test - public void deliver_updates_from_projection_repository() throws Exception { + public void deliver_updates_from_projection_repository() { final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(BoundedContext.newBuilder() - .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build()); + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); // Init repository - final ProjectionRepository repository = Given.repo(boundedContext); + final ProjectionRepository repository = Given.projectionRepo(boundedContext); - stand.registerTypeSupplier(repository); repository.initStorage(InMemoryStorageFactory.getInstance()); repository.setOnline(); @@ -194,6 +185,29 @@ public void execute(Runnable command) { verify(stand).update(ArgumentMatchers.any(), any(Any.class)); } + @Test + public void deliver_updates_form_aggregate_repository() { + final Stand stand = mock(Stand.class); + + final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + // Init repository + final AggregateRepository repository = Given.aggregateRepo(boundedContext); + + stand.registerTypeSupplier(repository); + repository.initStorage(InMemoryStorageFactory.getInstance()); + + // Dispatch an update from projection projectionRepo + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + // Was called ONCE + verify(boundedContext).getStandFunnel(); + verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + } @SuppressWarnings("MethodWithMultipleLoops") @Test From 196848bc7d0909404d56bdb6e476ff6beb4d999d Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:27:38 +0300 Subject: [PATCH 126/361] Delete redundant "TestFailure" declaration. --- server/src/test/java/org/spine3/server/stand/Given.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 595397ce31c..18b75d76805 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -27,7 +27,6 @@ import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.EventId; -import org.spine3.base.FailureThrowable; import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; @@ -81,13 +80,6 @@ protected ProjectId getEntityId(Message event, EventContext context) { } } - /*package*/ static class TestFailure extends FailureThrowable { - - /*package*/ TestFailure() { - super(/*generatedMessage=*/null); - } - } - private static class StandTestAggregate extends Aggregate { /** From 0ae977c022060da9eaec3b8c4d2d6fa06a0d639a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 17:40:54 +0300 Subject: [PATCH 127/361] Extract common test operations. --- .../server/stand/StandFunnelShould.java | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index ad82932db72..3cac4e4acb7 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -39,6 +39,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import static com.google.common.base.Preconditions.checkNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -169,41 +170,53 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final ProjectionRepository repository = Given.projectionRepo(boundedContext); - - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }); } @Test public void deliver_updates_form_aggregate_repository() { - final Stand stand = mock(Stand.class); + deliverUpdatesTest(new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final AggregateRepository repository = Given.aggregateRepo(context); + + repository.initStorage(InMemoryStorageFactory.getInstance()); + try { + repository.dispatch(Given.validCommand()); + } catch (IllegalStateException e) { + // Handle null event dispatch after command handling. + Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + } + + } + }); + } + + private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); final BoundedContext boundedContext = spy(Given.boundedContext(stand)); - // Init repository - final AggregateRepository repository = Given.aggregateRepo(boundedContext); - - stand.registerTypeSupplier(repository); - repository.initStorage(InMemoryStorageFactory.getInstance()); - - // Dispatch an update from projection projectionRepo - try { - repository.dispatch(Given.validCommand()); - } catch (IllegalStateException e) { - // Handle null event dispatch after command handling. - Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); } + // Was called ONCE verify(boundedContext).getStandFunnel(); verify(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -250,4 +263,8 @@ public void run() { } + private interface BoundedContextAction { + void perform(BoundedContext context); + } + } From 5d3c7192804040b6ea3825c76bd489cdbc0f5cc2 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:21:34 +0300 Subject: [PATCH 128/361] Refactor and add delivery tests for several repositories successively and in parallel. --- .../java/org/spine3/server/stand/Given.java | 29 ++++-- .../server/stand/StandFunnelShould.java | 89 +++++++++++++------ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 18b75d76805..ccad28106a2 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -46,12 +46,16 @@ import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** * @author Dmytro Dashenkov */ /*package*/ class Given { + /*package*/ static final int THREADS_COUNT_IN_POOL_EXECUTOR = 10; + /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; + private static final String PROJECT_UUID = Identifiers.newUuid(); private Given() { @@ -154,16 +158,23 @@ public void handle(ProjectCreated event, EventContext context) { return new StandTestAggregateRepository(context); } - /*package*/ static BoundedContext boundedContext(Stand stand) { + /*package*/ static BoundedContext boundedContext(Stand stand, int concurrentThreads) { + final Executor executor = concurrentThreads > 0 ? Executors.newFixedThreadPool(concurrentThreads) : + new Executor() { // Straightforward executor + @Override + public void execute(Runnable command) { + command.run(); + } + }; + + return boundedContextBuilder(stand) + .setStandFunnelExecutor(executor) + .build(); + } + + private static BoundedContext.Builder boundedContextBuilder(Stand stand) { return BoundedContext.newBuilder() .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .setStandFunnelExecutor(new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }) - .build(); + .setStorageFactory(InMemoryStorageFactory.getInstance()); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 3cac4e4acb7..486697b153e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -31,7 +31,9 @@ import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.testdata.TestStandFactory; +import java.security.SecureRandom; import java.util.Map; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** @@ -170,24 +173,55 @@ public void fail_to_initialize_from_empty_builder() { @Test public void deliver_updates_from_projection_repository() { - deliverUpdatesTest(new BoundedContextAction() { - @Override - public void perform(BoundedContext context) { - // Init repository - final ProjectionRepository repository = Given.projectionRepo(context); + deliverUpdates(false, projectionRepositoryDispatch()); + } - repository.initStorage(InMemoryStorageFactory.getInstance()); - repository.setOnline(); + @Test + public void deliver_updates_form_aggregate_repository() { + deliverUpdates(false, aggregateRepositoryDispatch()); + } - // Dispatch an update from projection repo - repository.dispatch(Given.validEvent()); - } - }); + @Test + public void deliver_updates_from_several_repositories_in_single_thread() { + deliverUpdates(false, getSeveralRepositoryDispatchCalls()); } @Test - public void deliver_updates_form_aggregate_repository() { - deliverUpdatesTest(new BoundedContextAction() { + public void deliver_updates_from_several_repositories_in_multiple_threads() { + deliverUpdates(true, getSeveralRepositoryDispatchCalls()); + } + + private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { + final BoundedContextAction[] result = new BoundedContextAction[Given.SEVERAL]; + final Random random = new SecureRandom(); + + for (int i = 0; i < result.length; i++) { + result[i] = random.nextBoolean() ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); + } + + return result; + } + + private static void deliverUpdates(boolean isMultiThread, BoundedContextAction... dispatchActions) { + checkNotNull(dispatchActions); + + final Stand stand = mock(Stand.class); + final BoundedContext boundedContext = spy(Given.boundedContext(stand, + isMultiThread ? + Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); + + for (BoundedContextAction dispatchAction : dispatchActions) { + dispatchAction.perform(boundedContext); + } + + + // Was called ONCE + verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); + verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); + } + + private static BoundedContextAction aggregateRepositoryDispatch() { + return new BoundedContextAction() { @Override public void perform(BoundedContext context) { // Init repository @@ -201,31 +235,32 @@ public void perform(BoundedContext context) { // Handle null event dispatch after command handling. Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); } - } - }); + }; } - private static void deliverUpdatesTest(BoundedContextAction... dispatchActions) { - checkNotNull(dispatchActions); + private static BoundedContextAction projectionRepositoryDispatch() { + return new BoundedContextAction() { + @Override + public void perform(BoundedContext context) { + // Init repository + final ProjectionRepository repository = Given.projectionRepo(context); - final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand)); + repository.initStorage(InMemoryStorageFactory.getInstance()); + repository.setOnline(); - for (BoundedContextAction dispatchAction : dispatchActions) { - dispatchAction.perform(boundedContext); - } + // Dispatch an update from projection repo + repository.dispatch(Given.validEvent()); + } + }; + } - // Was called ONCE - verify(boundedContext).getStandFunnel(); - verify(stand).update(ArgumentMatchers.any(), any(Any.class)); - } @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { - final int threadsCount = 10; + final int threadsCount = Given.THREADS_COUNT_IN_POOL_EXECUTOR; @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name final int threadExecutionMaxAwaitSeconds = 2; From 94aa4e5d024e6b4e69a13bc1aa112e20453e20a4 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 129/361] Explicit "Given" for stand tests. --- .../org/spine3/server/stand/StandShould.java | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 9228d51f346..f0a672ae0d3 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -48,6 +48,7 @@ import org.spine3.server.projection.Projection; import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; @@ -721,26 +722,6 @@ public void onEntityStateUpdate(Any newEntityState) { // ***** Inner classes used for tests. ***** - private static class StandTestProjection extends Projection { - /** - * Creates a new instance. - * - * @param id the ID for the new instance - * @throws IllegalArgumentException if the ID is not of one of the supported types - */ - public StandTestProjection(ProjectId id) { - super(id); - } - } - - - private static class StandTestProjectionRepository extends ProjectionRepository { - protected StandTestProjectionRepository(BoundedContext boundedContext) { - super(boundedContext); - } - } - - /** * A {@link StreamObserver} storing the state of {@link Query} execution. */ From fa2017a32b850c383ec22d397e5348bcc63ed8cd Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 18:47:07 +0300 Subject: [PATCH 130/361] Fix failing build by adding "await" for a concurrent test. --- .../java/org/spine3/server/stand/Given.java | 1 + .../spine3/server/stand/StandFunnelShould.java | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index ccad28106a2..9e6d31d9f83 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -57,6 +57,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); + public static final int AWAIT_SECONDS = 2; private Given() { } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 486697b153e..0e8f48438fc 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -214,12 +214,23 @@ private static void deliverUpdates(boolean isMultiThread, BoundedContextAction.. dispatchAction.perform(boundedContext); } - - // Was called ONCE + // Was called as much times as there are dispatch actions. verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); + + if (isMultiThread) { + await(Given.AWAIT_SECONDS); + } + verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); } + private static void await(int seconds) { + try { + Thread.sleep(seconds); + } catch (InterruptedException ignore) { + } + } + private static BoundedContextAction aggregateRepositoryDispatch() { return new BoundedContextAction() { @Override @@ -256,13 +267,12 @@ public void perform(BoundedContext context) { } - @SuppressWarnings("MethodWithMultipleLoops") @Test public void deliver_updates_through_several_threads() throws InterruptedException { final int threadsCount = Given.THREADS_COUNT_IN_POOL_EXECUTOR; @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name - final int threadExecutionMaxAwaitSeconds = 2; + final int threadExecutionMaxAwaitSeconds = Given.AWAIT_SECONDS; final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); From 1edb4cce1a9719993c370b19c4f0d5c63c310a84 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:22:03 +0300 Subject: [PATCH 131/361] Fix issues form PR. --- .../java/org/spine3/server/stand/Given.java | 10 +-- .../server/stand/StandFunnelShould.java | 89 ++++++++----------- 2 files changed, 41 insertions(+), 58 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 9e6d31d9f83..4977b9e0a3e 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -20,6 +20,7 @@ package org.spine3.server.stand; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import com.google.protobuf.Message; import org.spine3.base.Command; @@ -139,7 +140,7 @@ public void handle(ProjectCreated event, EventContext context) { .setDoNotEnrich(true) .setCommandContext(CommandContext.getDefaultInstance()) .setEventId(EventId.newBuilder() - .setUuid(PROJECT_UUID) + .setUuid(Identifiers.newUuid()) .build())) .build(); } @@ -161,12 +162,7 @@ public void handle(ProjectCreated event, EventContext context) { /*package*/ static BoundedContext boundedContext(Stand stand, int concurrentThreads) { final Executor executor = concurrentThreads > 0 ? Executors.newFixedThreadPool(concurrentThreads) : - new Executor() { // Straightforward executor - @Override - public void execute(Runnable command) { - command.run(); - } - }; + MoreExecutors.directExecutor(); return boundedContextBuilder(stand) .setStandFunnelExecutor(executor) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 0e8f48438fc..b405de4777f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -22,6 +22,7 @@ package org.spine3.server.stand; import com.google.protobuf.Any; +import io.netty.util.internal.ConcurrentSet; import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatchers; @@ -32,13 +33,11 @@ import org.spine3.testdata.TestStandFactory; import java.security.SecureRandom; -import java.util.Map; import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; @@ -57,10 +56,6 @@ public class StandFunnelShould { // **** Positive scenarios (unit) **** - /** - * - deliver mock updates to the stand (invoke proper methods with particular arguments) - test the delivery only. - */ - @Test public void initialize_properly_with_stand_only() { final Stand stand = TestStandFactory.create(); @@ -71,36 +66,25 @@ public void initialize_properly_with_stand_only() { } @Test - public void initialize_properly_with_various_builder_options() { + public void initialize_properly_with_all_builder_options() { final Stand stand = TestStandFactory.create(); - final Executor executor = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return Thread.currentThread(); - } - }); + final Executor executor = Executors.newSingleThreadExecutor(); - final StandFunnel blockingFunnel = StandFunnel.newBuilder() + final StandFunnel funnel = StandFunnel.newBuilder() .setStand(stand) .setExecutor(executor) .build(); - Assert.assertNotNull(blockingFunnel); + Assert.assertNotNull(funnel); + } + + @Test + public void initialize_properly_with_no_executor() { + final Stand stand = TestStandFactory.create(); final StandFunnel funnelForBusyStand = StandFunnel.newBuilder() .setStand(stand) - .setExecutor(Executors.newSingleThreadExecutor()) .build(); Assert.assertNotNull(funnelForBusyStand); - - - final StandFunnel emptyExecutorFunnel = StandFunnel.newBuilder() - .setStand(TestStandFactory.create()) - .setExecutor(new Executor() { - @Override - public void execute(Runnable neverCalled) { } - }) - .build(); - Assert.assertNotNull(emptyExecutorFunnel); } @Test @@ -149,8 +133,8 @@ public void execute(Runnable command) { @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = NullPointerException.class) - public void fail_to_initialize_with_inproper_stand() { - @SuppressWarnings("ConstantConditions") + public void fail_to_initialize_with_improper_stand() { + @SuppressWarnings("ConstantConditions") // null is marked as improper with this warning final StandFunnel.Builder builder = StandFunnel.newBuilder().setStand(null); builder.build(); @@ -165,30 +149,24 @@ public void fail_to_initialize_from_empty_builder() { // **** Integration scenarios ( -> StandFunnel -> Mock Stand) **** - /** - * - Deliver updates from projection projectionRepo on update; - * - deliver updates from aggregate projectionRepo on update; - * - deliver the updates from several projection and aggregate repositories. - */ - @Test public void deliver_updates_from_projection_repository() { - deliverUpdates(false, projectionRepositoryDispatch()); + checkUpdatesDelivery(false, projectionRepositoryDispatch()); } @Test public void deliver_updates_form_aggregate_repository() { - deliverUpdates(false, aggregateRepositoryDispatch()); + checkUpdatesDelivery(false, aggregateRepositoryDispatch()); } @Test public void deliver_updates_from_several_repositories_in_single_thread() { - deliverUpdates(false, getSeveralRepositoryDispatchCalls()); + checkUpdatesDelivery(false, getSeveralRepositoryDispatchCalls()); } @Test public void deliver_updates_from_several_repositories_in_multiple_threads() { - deliverUpdates(true, getSeveralRepositoryDispatchCalls()); + checkUpdatesDelivery(true, getSeveralRepositoryDispatchCalls()); } private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { @@ -202,22 +180,22 @@ private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { return result; } - private static void deliverUpdates(boolean isMultiThread, BoundedContextAction... dispatchActions) { + private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAction... dispatchActions) { checkNotNull(dispatchActions); final Stand stand = mock(Stand.class); final BoundedContext boundedContext = spy(Given.boundedContext(stand, - isMultiThread ? + isConcurrent ? Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); for (BoundedContextAction dispatchAction : dispatchActions) { dispatchAction.perform(boundedContext); } - // Was called as much times as there are dispatch actions. + // Was called as many times as there are dispatch actions. verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); - if (isMultiThread) { + if (isConcurrent) { await(Given.AWAIT_SECONDS); } @@ -241,10 +219,19 @@ public void perform(BoundedContext context) { repository.initStorage(InMemoryStorageFactory.getInstance()); try { + // Mock aggregate and mock stand are not able to handle events returned after command handling. + // This causes IllegalStateException to be thrown. + // Note that this is not the end of a test case, so we can't just "expect=IllegalStateException" repository.dispatch(Given.validCommand()); } catch (IllegalStateException e) { - // Handle null event dispatch after command handling. - Assert.assertTrue(e.getMessage().contains("No record found for command ID: EMPTY")); + // Handle null event dispatching after the command is handled. + + // Check if this error is caused by returning nuu or empty list after command processing. + // Proceed crash if it's not + if (!e.getMessage() + .contains("No record found for command ID: EMPTY")) { + throw e; + } } } }; @@ -274,7 +261,7 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio @SuppressWarnings("LocalVariableNamingConvention") // Too long variable name final int threadExecutionMaxAwaitSeconds = Given.AWAIT_SECONDS; - final Map threadInvocationRegistry = new ConcurrentHashMap<>(threadsCount); + final Set threadInvocationRegistry = new ConcurrentSet<>(); final Stand stand = mock(Stand.class); doNothing().when(stand).update(ArgumentMatchers.any(), any(Any.class)); @@ -283,26 +270,26 @@ public void deliver_updates_through_several_threads() throws InterruptedExceptio .setStand(stand) .build(); - final ExecutorService processes = Executors.newFixedThreadPool(threadsCount); + final ExecutorService executor = Executors.newFixedThreadPool(threadsCount); final Runnable task = new Runnable() { @Override public void run() { final String threadName = Thread.currentThread().getName(); - Assert.assertFalse(threadInvocationRegistry.containsKey(threadName)); + Assert.assertFalse(threadInvocationRegistry.contains(threadName)); standFunnel.post(new Object(), Any.getDefaultInstance()); - threadInvocationRegistry.put(threadName, new Object()); + threadInvocationRegistry.add(threadName); } }; for (int i = 0; i < threadsCount; i++) { - processes.execute(task); + executor.execute(task); } - processes.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); + executor.awaitTermination(threadExecutionMaxAwaitSeconds, TimeUnit.SECONDS); Assert.assertEquals(threadInvocationRegistry.size(), threadsCount); From d31c2aeb1fc5814e437081fec6f10a2a0c633e6b Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:36:39 +0300 Subject: [PATCH 132/361] Increase await time for concurrent check execution. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 4977b9e0a3e..82fc42e059d 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - public static final int AWAIT_SECONDS = 2; + public static final int AWAIT_SECONDS = 4; private Given() { } From c534ae8a433f6227fed6fe310fa71957b190ab2a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:51:17 +0300 Subject: [PATCH 133/361] Increase await time for concurrent check execution again. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 82fc42e059d..e0872d39b9f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - public static final int AWAIT_SECONDS = 4; + /*package*/ static final int AWAIT_SECONDS = 10; private Given() { } From f88c0b171686a2024c5509588cad2aa236f49998 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 21:52:48 +0300 Subject: [PATCH 134/361] Reduce randomness of generated test data. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index e0872d39b9f..d194aecc21f 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -58,7 +58,7 @@ /*package*/ static final int SEVERAL = THREADS_COUNT_IN_POOL_EXECUTOR; private static final String PROJECT_UUID = Identifiers.newUuid(); - /*package*/ static final int AWAIT_SECONDS = 10; + /*package*/ static final int AWAIT_SECONDS = 6; private Given() { } diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index b405de4777f..41be840a692 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -174,7 +174,7 @@ private static BoundedContextAction[] getSeveralRepositoryDispatchCalls() { final Random random = new SecureRandom(); for (int i = 0; i < result.length; i++) { - result[i] = random.nextBoolean() ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); + result[i] = (i % 2 == 0) ? aggregateRepositoryDispatch() : projectionRepositoryDispatch(); } return result; From 3daae3b0d27c374fedb1db30746c87f8f7e979e8 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 23:29:36 +0300 Subject: [PATCH 135/361] Remove redundant imports. --- .../src/test/java/org/spine3/server/stand/StandShould.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index f0a672ae0d3..997c95d7c83 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -45,15 +45,12 @@ import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; import org.spine3.server.Given; -import org.spine3.server.projection.Projection; -import org.spine3.server.projection.ProjectionRepository; -import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.stand.Given.StandTestProjectionRepository; +import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; -import org.spine3.test.projection.ProjectId; import javax.annotation.Nullable; import java.util.Collection; From 91f673c8b2123e9e0d42e89816931090c805cfdb Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 136/361] Explicit "Given" for stand tests. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 997c95d7c83..d2c2c1ac92c 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -47,6 +47,7 @@ import org.spine3.server.Given; import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; From 7eb64546ab3e64d4dc05ee93f0d5fbad4e24282c Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 13:57:38 +0300 Subject: [PATCH 137/361] Explicit "Given" for stand tests. --- server/src/test/java/org/spine3/server/stand/Given.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index d194aecc21f..fa15cd2f6df 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -128,7 +128,7 @@ public void handle(ProjectCreated event, EventContext context) { } } - /*package*/ static Event validEvent() { + /*package*/ static Event validEvent() { return Event.newBuilder() .setMessage(AnyPacker.pack(ProjectCreated.newBuilder() .setProjectId(ProjectId.newBuilder().setId("12345AD0")) From 3aeb517e0bee86318a78a2484248f3c8de6ca002 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 14 Sep 2016 14:54:11 +0300 Subject: [PATCH 138/361] Add deliver updates form projection repo test skeleton, --- .../src/test/java/org/spine3/server/stand/StandFunnelShould.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 41be840a692..d952c30ea84 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -47,6 +47,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Alex Tymchenko From 646964a2629e9b2ddb579ebf1b5ad6fb97c61e21 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 16:32:06 +0300 Subject: [PATCH 139/361] Merge. --- .../java/org/spine3/server/stand/Given.java | 2 +- .../org/spine3/server/stand/StandShould.java | 29 ++++++++++--------- .../org/spine3/testdata/TestStandFactory.java | 5 ++++ 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index fa15cd2f6df..2184ac0511d 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -109,7 +109,7 @@ public void handle(ProjectCreated event) { } } - private static class StandTestProjection extends Projection { + /*package*/ static class StandTestProjection extends Projection { /** * Creates a new instance. * diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index d2c2c1ac92c..2dfcc386eb5 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -44,7 +44,9 @@ import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; -import org.spine3.server.Given; +import org.spine3.server.Given.CustomerAggregate; +import org.spine3.server.Given.CustomerAggregateRepository; +import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.stand.Given.StandTestProjectionRepository; @@ -52,6 +54,7 @@ import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; import javax.annotation.Nullable; import java.util.Collection; @@ -139,7 +142,7 @@ public void register_aggregate_repositories() { checkTypesEmpty(stand); - final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository customerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); final Descriptors.Descriptor customerEntityDescriptor = Customer.getDescriptor(); @@ -147,7 +150,7 @@ public void register_aggregate_repositories() { checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); @SuppressWarnings("LocalVariableNamingConvention") - final Given.CustomerAggregateRepository anotherCustomerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository anotherCustomerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(anotherCustomerAggregateRepo); checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); @@ -184,7 +187,7 @@ public void operate_with_storage_provided_through_builder() { assertNotNull(stand); final BoundedContext boundedContext = newBoundedContext(stand); - final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository customerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); @@ -192,7 +195,7 @@ public void operate_with_storage_provided_through_builder() { final CustomerId customerId = CustomerId.newBuilder() .setNumber(numericIdValue) .build(); - final Given.CustomerAggregate customerAggregate = customerAggregateRepo.create(customerId); + final CustomerAggregate customerAggregate = customerAggregateRepo.create(customerId); final Customer customerState = customerAggregate.getState(); final Any packedState = AnyPacker.pack(customerState); final TypeUrl customerType = TypeUrl.of(Customer.class); @@ -498,10 +501,10 @@ private static void setupExpectedFindAllBehaviour(Map sample StandTestProjectionRepository projectionRepository) { final Set projectIds = sampleProjects.keySet(); - final ImmutableCollection allResults = toProjectionCollection(projectIds); + final ImmutableCollection allResults = toProjectionCollection(projectIds); for (ProjectId projectId : projectIds) { - when(projectionRepository.find(eq(projectId))).thenReturn(new StandTestProjection(projectId)); + when(projectionRepository.find(eq(projectId))).thenReturn(new Given.StandTestProjection(projectId)); } final Iterable matchingIds = argThat(projectionIdsIterableMatcher(projectIds)); @@ -537,16 +540,16 @@ public boolean matches(EntityFilters argument) { }; } - private static ImmutableCollection toProjectionCollection(Collection values) { - final Collection transformed = Collections2.transform(values, new Function() { + private static ImmutableCollection toProjectionCollection(Collection values) { + final Collection transformed = Collections2.transform(values, new Function() { @Nullable @Override - public StandTestProjection apply(@Nullable ProjectId input) { + public Given.StandTestProjection apply(@Nullable ProjectId input) { checkNotNull(input); - return new StandTestProjection(input); + return new Given.StandTestProjection(input); } }); - final ImmutableList result = ImmutableList.copyOf(transformed); + final ImmutableList result = ImmutableList.copyOf(transformed); return result; } @@ -662,7 +665,7 @@ private static Stand prepareStandWithAggregateRepo(StandStorage standStorageMock assertNotNull(stand); final BoundedContext boundedContext = newBoundedContext(stand); - final Given.CustomerAggregateRepository customerAggregateRepo = new Given.CustomerAggregateRepository(boundedContext); + final org.spine3.server.Given.CustomerAggregateRepository customerAggregateRepo = new org.spine3.server.Given.CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); return stand; } diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index 474a6226b30..63477a00a7c 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -21,6 +21,7 @@ */ package org.spine3.testdata; +import org.mockito.Mockito; import org.spine3.server.stand.Stand; /** @@ -38,4 +39,8 @@ public static Stand create() { .build(); return stand; } + + public static Stand createMock() { + return Mockito.mock(Stand.class); + } } From 64058636ad3eaf8e9d6b8a2cff5f34efa5785727 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 17:23:32 +0300 Subject: [PATCH 140/361] Correct docs. --- .../src/main/java/org/spine3/server/stand/StandFunnel.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 44997adbdf8..1c4e9afc294 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -108,9 +108,6 @@ public Stand getStand() { * *

The value must not be null. * - *

If this method is not used, a default value will be used. - * // TODO:13-09-16:dmytro.dashenkov: Correct docs. No default value for Stand is used, null value leads to a fail instead. - * * @param stand the instance of {@link Stand}. * @return {@code this} instance of {@code Builder} */ @@ -123,12 +120,13 @@ public Executor getExecutor() { return executor; } - // TODO:13-09-16:dmytro.dashenkov: Complete docs. Here is the place where a default value is used in case of not using the method. /** * Set the {@code Executor} instance for this {@code StandFunnel}. * *

The value must not be {@code null}. * + *

If this method is not used, a default value will be used. + * * @param executor the instance of {@code Executor}. * @return {@code this} instance of {@code Builder} */ From 5e9077febd5a5704efcef9f6dcfa3127beecb581 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 18:13:15 +0300 Subject: [PATCH 141/361] Create filter method "applyFieldMask" for Query execution. --- .../java/org/spine3/server/stand/Stand.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index ecdc46bffc9..e5a71468ca7 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -31,7 +31,9 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; import com.google.protobuf.Message; +import com.google.protobuf.ProtocolStringList; import io.grpc.stub.StreamObserver; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; @@ -54,9 +56,11 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -249,7 +253,7 @@ public ImmutableSet getKnownAggregateTypes() { public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(readResult) + .addAllMessages(applyFieldMask(readResult, query.getFieldMask())) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -285,6 +289,23 @@ private ImmutableCollection internalExecute(Query query) { return result; } + private static Iterable applyFieldMask(Collection entities, FieldMask mask) { + final List filtered = new ArrayList<>(); + final ProtocolStringList filter = mask.getPathsList(); + + if (filter.isEmpty()) { + return Collections.unmodifiableCollection(entities); + } + + for (Any any : entities) { + if (filter.contains(any.getTypeUrl())) { + filtered.add(any); + } + } + + return Collections.unmodifiableList(filtered); + } + private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { final ImmutableCollection result; From ae74d273b5ddf0abec9e2fa100b3dc9b1b4acfcb Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 19:35:38 +0300 Subject: [PATCH 142/361] Use "equals" instead of "==" in InMemoryStorage::writeInternal. --- .../org/spine3/server/storage/memory/InMemoryStandStorage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 75d8a0e5716..7ce5714bf5b 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -97,7 +97,7 @@ protected Map readAllInternal() { protected void writeInternal(AggregateStateId id, EntityStorageRecord record) { final TypeUrl recordType = TypeUrl.of(record.getState() .getTypeUrl()); - checkState(id.getStateType() == recordType, "The typeUrl of the record does not correspond to id"); + checkState(id.getStateType().equals(recordType), "The typeUrl of the record does not correspond to id"); recordStorage.write(id, record); } From 8037a411294e43addab5eba2308e90f567b98530 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 19:38:33 +0300 Subject: [PATCH 143/361] Add test for retrieving all data from a query with empty field mask. --- .../org/spine3/server/stand/StandShould.java | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 2dfcc386eb5..a58fc25284f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -41,6 +41,7 @@ import org.spine3.client.Query; import org.spine3.client.QueryResponse; import org.spine3.client.Target; +import org.spine3.people.PersonName; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; @@ -49,8 +50,8 @@ import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; -import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.StandStorage; +import org.spine3.server.storage.memory.InMemoryStandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; @@ -69,6 +70,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Maps.newHashMap; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -353,6 +355,55 @@ public void trigger_callback_upon_change_of_watched_aggregate_state() { assertEquals(packedState, memoizeCallback.newEntityState); } + @Test + public void retrieve_all_data_if_field_mask_is_not_set() { + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder().setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .build(); + + + //noinspection OverlyComplexAnonymousInnerClass + stand.execute(customerQuery, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + for (Descriptors.FieldDescriptor field : customer.getDescriptorForType().getFields()) { + assertTrue(customer.getField(field).equals(sampleCustomer.getField(field))); + } + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + + private static Customer getSampleCustomer() { + return Customer.newBuilder() + .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() + .getLeastSignificantBits())) + .setName(PersonName.newBuilder().setGivenName("Socrates").build()) + .build(); + + } + private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. @@ -658,14 +709,14 @@ private static List checkAndGetMessageList(MemoizeQueryResponseObserver res } - private static Stand prepareStandWithAggregateRepo(StandStorage standStorageMock) { + private static Stand prepareStandWithAggregateRepo(StandStorage standStorage) { final Stand stand = Stand.newBuilder() - .setStorage(standStorageMock) + .setStorage(standStorage) .build(); assertNotNull(stand); final BoundedContext boundedContext = newBoundedContext(stand); - final org.spine3.server.Given.CustomerAggregateRepository customerAggregateRepo = new org.spine3.server.Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository customerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); return stand; } From 5ba27cd68d92bd360dc38377fdd9255b666d9cbb Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 15 Sep 2016 20:45:19 +0300 Subject: [PATCH 144/361] Rework SubscriptionService API and related Stand features: * simplify naming; * restructure subscription storage on a Stand side; * update unit tests accordingly. --- .../proto/spine/client/subscription.proto | 13 +- .../spine/client/subscription_service.proto | 6 +- .../java/org/spine3/server/stand/Stand.java | 172 ++++++++++++++---- .../org/spine3/server/stand/StandShould.java | 22 ++- 4 files changed, 163 insertions(+), 50 deletions(-) diff --git a/client/src/main/proto/spine/client/subscription.proto b/client/src/main/proto/spine/client/subscription.proto index ddd579cd8c0..c44443c343f 100644 --- a/client/src/main/proto/spine/client/subscription.proto +++ b/client/src/main/proto/spine/client/subscription.proto @@ -52,11 +52,11 @@ message Topic { reserved 3 to 6; } -// Wrapped set of read-side entity updates on a topic with the specific subscription ID. +// Wrapped collection of read-side entity updates on a topic with the specific subscription ID. message SubscriptionUpdate { - // The ID of the current subscription. - SubscriptionId id = 1; + // The subscription in which scope this update is propagated. + Subscription subscription = 1; // Represents the base part of the response. I.e. whether the Topic subscription requires has been acked or not. base.Response response = 2; @@ -70,14 +70,15 @@ message SubscriptionUpdate { repeated google.protobuf.Any updates = 10; } -// The ID of the subscription. +// The subscription object. // // Created when the client subscribes to a topic. // See SubscriptionService#Subscribe(Topic). -message SubscriptionId { +message Subscription { // Unique identifier of the subscription. // // Typically built using Java's UUID.toString() functionality. - string uuid = 1; + // Must be unique in scope of a bounded context. + string id = 1; } diff --git a/client/src/main/proto/spine/client/subscription_service.proto b/client/src/main/proto/spine/client/subscription_service.proto index 0f679b04671..83a0e16ccdc 100644 --- a/client/src/main/proto/spine/client/subscription_service.proto +++ b/client/src/main/proto/spine/client/subscription_service.proto @@ -41,6 +41,8 @@ service SubscriptionService { // The result is a SubscriptionUpdate stream, rpc Subscribe (Topic) returns (stream SubscriptionUpdate); - // Cancel the subscription by its ID. - rpc CancelSubscription (SubscriptionId) returns (base.Response); + // Cancel the given subscription. + // + // Clients will stop receiving the SubscriptionUpdate after this method is called. + rpc Cancel (Subscription) returns (base.Response); } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index ecdc46bffc9..5205be6cc0c 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -22,6 +22,7 @@ package org.spine3.server.stand; import com.google.common.base.Function; +import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.FluentIterable; @@ -39,6 +40,7 @@ import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; import org.spine3.client.QueryResponse; +import org.spine3.client.Subscription; import org.spine3.client.Target; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.KnownTypes; @@ -55,9 +57,11 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; @@ -88,12 +92,10 @@ public class Stand { private final StandStorage storage; /** - * A set of callbacks to be executed upon the incoming updates. - * - *

Each callback is triggerred if the entity with a matching {@code TypeUrl} is delivered to this {@code Stand}. - *

There may be any number of callbacks for a given {@code TypeUrl}. + * Manages the subscriptions for this instance of {@code Stand}. */ - private final ConcurrentMap> callbacks = new ConcurrentHashMap<>(); + private final StandSubscriptionRegistry subscriptionRegistry = new StandSubscriptionRegistry(); + /** An instance of executor used to invoke callbacks */ private final Executor callbackExecutor; @@ -158,53 +160,48 @@ public void update(final Object id, final Any entityState) { storage.write(aggregateStateId, record); } - if (callbacks.containsKey(typeUrl)) { - for (final StandUpdateCallback callback : callbacks.get(typeUrl)) { - callbackExecutor.execute(new Runnable() { - @Override - public void run() { - callback.onEntityStateUpdate(entityState); - } - }); + if (subscriptionRegistry.hasType(typeUrl)) { + final Set allRecords = subscriptionRegistry.byType(typeUrl); + for (final SubscriptionRecord subscriptionRecord : allRecords) { + if (subscriptionRecord.matches(typeUrl, id, entityState)) { + callbackExecutor.execute(new Runnable() { + @Override + public void run() { + subscriptionRecord.callback.onEntityStateUpdate(entityState); + } + }); + } } } } /** - * Watch for a change of an entity state with a certain {@link TypeUrl}. + * Subscribe for all further changes of an entity state, which satisfies the {@link Target}. * *

Once this instance of {@code Stand} receives an update of an entity with the given {@code TypeUrl}, * all such callbacks are executed. * - * @param typeUrl an instance of entity {@link TypeUrl} to watch for changes + * @param target an instance {@link Target}, defining the entity and criteria, + * which changes should be propagated to the {@code callback} * @param callback an instance of {@link StandUpdateCallback} executed upon entity update. */ - public void watch(TypeUrl typeUrl, StandUpdateCallback callback) { - if (!callbacks.containsKey(typeUrl)) { - final Set emptySet = Collections.synchronizedSet(new HashSet()); - callbacks.put(typeUrl, emptySet); - } - - callbacks.get(typeUrl) - .add(callback); + @CheckReturnValue + public Subscription subscribe(Target target, StandUpdateCallback callback) { + final Subscription subscription = subscriptionRegistry.addSubscription(target, callback); + return subscription; } /** - * Stop watching for a change of an entity state with a certain {@link TypeUrl}. + * Cancel the {@link Subscription}. * - *

Typically invoked to cancel the previous {@link #watch(TypeUrl, StandUpdateCallback)} call with the same arguments. - *

If no {@code watch} method was executed for the same {@code TypeUrl} and {@code StandUpdateCallback}, - * then {@code unwatch} has no effect. + *

Typically invoked to cancel the previous {@link #subscribe(Target, StandUpdateCallback)} call. + *

After this method is called, the subscribers stop receiving the updates, + * related to the given {@code Subscription}. * - * @param typeUrl an instance of entity {@link TypeUrl} to stop watch for changes - * @param callback an instance of {@link StandUpdateCallback} to be cancelled upon entity update. + * @param subscription a subscription to cancel. */ - public void unwatch(TypeUrl typeUrl, StandUpdateCallback callback) { - final Set registeredCallbacks = callbacks.get(typeUrl); - - if (registeredCallbacks != null && registeredCallbacks.contains(callback)) { - registeredCallbacks.remove(callback); - } + public void cancel(Subscription subscription) { + subscriptionRegistry.removeSubscription(subscription); } /** @@ -295,7 +292,9 @@ private ImmutableCollection fetchFromStandStorage(Target ta final EntityFilters filters = target.getFilters(); // TODO[alex.tymchenko]: do we need to check for null at all? How about, say, Python gRPC client? - if (filters != null && filters.getIdFilter() != null && !filters.getIdFilter().getIdsList().isEmpty()) { + if (filters != null && filters.getIdFilter() != null && !filters.getIdFilter() + .getIdsList() + .isEmpty()) { final EntityIdFilter idFilter = filters.getIdFilter(); final Collection stateIds = Collections2.transform(idFilter.getIdsList(), aggregateStateIdTransformer(typeUrl)); @@ -407,8 +406,8 @@ public void deregisterSupplierForType(TypeUrl typeUrl) { /** * A contract for the callbacks to be executed upon entity state change. * - * @see #watch(TypeUrl, StandUpdateCallback) - * @see #unwatch(TypeUrl, StandUpdateCallback) + * @see #subscribe(Target, StandUpdateCallback) + * @see #cancel(Subscription) */ @SuppressWarnings("InterfaceNeverImplemented") //it's OK, there may be no callbacks in the codebase public interface StandUpdateCallback { @@ -478,4 +477,99 @@ public Stand build() { return result; } } + + /** + * Registry for subscription management. + * + *

Provides a quick access to the subscription records by {@link TypeUrl}. + *

Responsible for {@link Subscription} object instantiation. + */ + private static final class StandSubscriptionRegistry { + private final Map> typeToAttrs = new HashMap<>(); + private final Map subscriptionToAttrs = new HashMap<>(); + + + private synchronized Subscription addSubscription(Target target, StandUpdateCallback callback) { + final String subscriptionId = UUID.randomUUID() + .toString(); + final Subscription subscription = Subscription.newBuilder() + .setId(subscriptionId) + .build(); + final TypeUrl type = TypeUrl.of(target.getType()); + final SubscriptionRecord attributes = new SubscriptionRecord(subscription, target, type, callback); + + if (!typeToAttrs.containsKey(type)) { + typeToAttrs.put(type, new HashSet()); + } + typeToAttrs.get(type) + .add(attributes); + + subscriptionToAttrs.put(subscription, attributes); + return subscription; + } + + private synchronized void removeSubscription(Subscription subscription) { + if (!subscriptionToAttrs.containsKey(subscription)) { + return; + } + final SubscriptionRecord attributes = subscriptionToAttrs.get(subscription); + + if (typeToAttrs.containsKey(attributes.type)) { + typeToAttrs.get(attributes.type) + .remove(attributes); + } + + subscriptionToAttrs.remove(subscription); + } + + private synchronized Set byType(TypeUrl type) { + final Set result = typeToAttrs.get(type); + return result; + } + + private synchronized boolean hasType(TypeUrl type) { + final boolean result = typeToAttrs.containsKey(type); + return result; + } + } + + /** + * Represents the attributes of a single subscription. + */ + private static final class SubscriptionRecord { + private final Subscription subscription; + private final Target target; + private final TypeUrl type; + private final StandUpdateCallback callback; + + private SubscriptionRecord(Subscription subscription, Target target, TypeUrl type, StandUpdateCallback callback) { + this.subscription = subscription; + this.target = target; + this.type = type; + this.callback = callback; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SubscriptionRecord)) { + return false; + } + SubscriptionRecord that = (SubscriptionRecord) o; + return Objects.equal(subscription, that.subscription); + } + + @Override + public int hashCode() { + return Objects.hashCode(subscription); + } + + private boolean matches(TypeUrl type, Object id, Any entityState) { + final boolean typeMatches = this.type.equals(type); + // TODO[alex.tymchenko]: use EntityFilter to match ID and state against it + return typeMatches; + } + } } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 2dfcc386eb5..b956fc2b933 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -49,7 +49,6 @@ import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; -import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.StandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; @@ -167,7 +166,16 @@ public void use_provided_executor_upon_update_of_watched_type() { stand.registerTypeSupplier(standTestProjectionRepo); final TypeUrl projectProjectionType = TypeUrl.of(Project.class); - stand.watch(projectProjectionType, emptyUpdateCallback()); + final EntityFilters filters = EntityFilters.newBuilder() + .build(); + final Target projectProjectionTarget = Target.newBuilder() + .setIncludeAll(true) + .setType(projectProjectionType.getTypeName()) + .setFilters(filters) + .build(); + + + stand.subscribe(projectProjectionTarget, emptyUpdateCallback()); verify(executor, never()).execute(any(Runnable.class)); @@ -338,8 +346,16 @@ public void trigger_callback_upon_change_of_watched_aggregate_state() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final TypeUrl customerType = TypeUrl.of(Customer.class); + final EntityFilters filters = EntityFilters.newBuilder() + .build(); + final Target customerTarget = Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .setFilters(filters) + .build(); + final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); - stand.watch(customerType, memoizeCallback); + stand.subscribe(customerTarget, memoizeCallback); assertNull(memoizeCallback.newEntityState); final Map.Entry sampleData = fillSampleCustomers(1).entrySet() From d5248f2fa2ad4187d1a5438ec4ff8e4490a8f0e9 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 15 Sep 2016 20:46:09 +0300 Subject: [PATCH 145/361] Remove outdated StandShould test comments. --- .../java/org/spine3/server/stand/StandShould.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index b956fc2b933..bd845d13d38 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -90,14 +90,7 @@ public class StandShould { private static final int TOTAL_CUSTOMERS_FOR_BATCH_READING = 10; private static final int TOTAL_PROJECTS_FOR_BATCH_READING = 10; -// **** Positive scenarios **** - /** - * - initialize properly with various Builder options; - * - register aggregate repositories by changing the known aggregate types. - * - register entity repositories properly - * - avoid duplicates while registering repositories - */ @Test public void initialize_with_empty_builder() { @@ -730,13 +723,6 @@ public void onEntityStateUpdate(Any newEntityState) { } - // **** Negative scenarios **** - - /** - * - fail to initialize with improper build arguments. - */ - - // ***** Inner classes used for tests. ***** /** From b17a1db847dfc95a8f62eff8a6a13eb5ca463183 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 16 Sep 2016 17:40:01 +0300 Subject: [PATCH 146/361] Fix basic field mask functionality. --- .../java/org/spine3/server/stand/Stand.java | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index e5a71468ca7..535ba771de4 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -31,6 +31,7 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import com.google.protobuf.Descriptors; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.ProtocolStringList; @@ -56,6 +57,9 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -252,8 +256,18 @@ public ImmutableSet getKnownAggregateTypes() { */ public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); + + Class builderClass; + try { + //noinspection unchecked + builderClass = (Class) Class.forName(KnownTypes.getClassName(TypeUrl.of(query.getTarget().getType())).value()) + .getClasses()[0]; + } catch (ClassNotFoundException | ClassCastException e) { + builderClass = null; + } + final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(applyFieldMask(readResult, query.getFieldMask())) + .addAllMessages(applyFieldMask(readResult, query.getFieldMask(), builderClass)) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -289,23 +303,53 @@ private ImmutableCollection internalExecute(Query query) { return result; } - private static Iterable applyFieldMask(Collection entities, FieldMask mask) { + @SuppressWarnings("MethodWithMultipleLoops") // Nested loops: each field in each entity. + private static Iterable applyFieldMask(Collection entities, FieldMask mask, @Nullable Class builderClass) { final List filtered = new ArrayList<>(); final ProtocolStringList filter = mask.getPathsList(); - if (filter.isEmpty()) { + if (filter.isEmpty() || builderClass == null) { return Collections.unmodifiableCollection(entities); } - for (Any any : entities) { - if (filter.contains(any.getTypeUrl())) { - filtered.add(any); + try { + final Constructor builderConstructor = builderClass.getDeclaredConstructor(); + builderConstructor.setAccessible(true); + + for (Any any : entities) { + final Message wholeMessage = AnyPacker.unpack(any); + final B builder = builderConstructor.newInstance(); + + for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { + if (filter.contains(field.getFullName())) { + invokeSetterOnBuilder(builder, field, wholeMessage.getField(field)); + } + } + + filtered.add(AnyPacker.pack(builder.build())); } + + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { + // If any reflection failure happens, return all the data without any mask applied. + // TODO:16-09-16:dmytro.dashenkov: Handle this exception for each field separately. + return Collections.unmodifiableCollection(entities); } return Collections.unmodifiableList(filtered); } + private static void invokeSetterOnBuilder(B builder, Descriptors.FieldDescriptor descriptor, Object argument) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + // TODO:16-09-16:dmytro.dashenkov: Handle collection case. + final String fieldName = descriptor.getName(); + + final Method setter = builder.getClass().getDeclaredMethod( + String.format("set%s%s", fieldName.substring(0, 1).toUpperCase(), fieldName.substring(1)), + argument.getClass()); + + setter.invoke(builder, argument); + } + private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { final ImmutableCollection result; From b089546999f50f940cc9448bc40eb461f203e9c9 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 16 Sep 2016 17:40:17 +0300 Subject: [PATCH 147/361] Fix basic field mask functionality test. --- .../org/spine3/server/stand/StandShould.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index a58fc25284f..8f91286f62f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -29,6 +29,7 @@ import com.google.common.collect.Maps; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; +import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; @@ -395,6 +396,47 @@ public void onCompleted() { }); } + @Test + public void retrieve_only_selected_params_for_query() { + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder().setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .setFieldMask(FieldMask.newBuilder() + .addPaths(Customer.getDescriptor().getFields().get(1).getFullName())) + .build(); + + + //noinspection OverlyComplexAnonymousInnerClass + stand.execute(customerQuery, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + assertTrue(customer.getName().equals(sampleCustomer.getName())); + assertFalse(customer.hasId()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + private static Customer getSampleCustomer() { return Customer.newBuilder() .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() From 7ef1d7abb4e302e3a5822aec53139eacc818e4f0 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 16 Sep 2016 18:37:02 +0300 Subject: [PATCH 148/361] Refactor field mask applying. --- .../java/org/spine3/server/stand/Stand.java | 42 ++++------ .../org/spine3/server/stand/StandShould.java | 77 +++++++++++++++---- .../clientservice/customer/customer.proto | 3 + 3 files changed, 80 insertions(+), 42 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 535ba771de4..be20af7246e 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -32,7 +32,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; -import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.ProtocolStringList; import io.grpc.stub.StreamObserver; @@ -59,7 +58,6 @@ import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -256,18 +254,8 @@ public ImmutableSet getKnownAggregateTypes() { */ public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); - - Class builderClass; - try { - //noinspection unchecked - builderClass = (Class) Class.forName(KnownTypes.getClassName(TypeUrl.of(query.getTarget().getType())).value()) - .getClasses()[0]; - } catch (ClassNotFoundException | ClassCastException e) { - builderClass = null; - } - final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(applyFieldMask(readResult, query.getFieldMask(), builderClass)) + .addAllMessages(applyFieldMask(readResult, query)) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -304,9 +292,11 @@ private ImmutableCollection internalExecute(Query query) { } @SuppressWarnings("MethodWithMultipleLoops") // Nested loops: each field in each entity. - private static Iterable applyFieldMask(Collection entities, FieldMask mask, @Nullable Class builderClass) { + private static Iterable applyFieldMask(Collection entities, Query query) { final List filtered = new ArrayList<>(); - final ProtocolStringList filter = mask.getPathsList(); + final ProtocolStringList filter = query.getFieldMask().getPathsList(); + + final Class builderClass = getBuilderForType(query.getTarget().getType()); if (filter.isEmpty() || builderClass == null) { return Collections.unmodifiableCollection(entities); @@ -322,7 +312,7 @@ private static Iterable applyFieldMas for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { if (filter.contains(field.getFullName())) { - invokeSetterOnBuilder(builder, field, wholeMessage.getField(field)); + builder.setField(field, wholeMessage.getField(field)); } } @@ -331,23 +321,25 @@ private static Iterable applyFieldMas } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { // If any reflection failure happens, return all the data without any mask applied. - // TODO:16-09-16:dmytro.dashenkov: Handle this exception for each field separately. return Collections.unmodifiableCollection(entities); } return Collections.unmodifiableList(filtered); } - private static void invokeSetterOnBuilder(B builder, Descriptors.FieldDescriptor descriptor, Object argument) - throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - // TODO:16-09-16:dmytro.dashenkov: Handle collection case. - final String fieldName = descriptor.getName(); + @Nullable + private static Class getBuilderForType(String typeUrlString) { + Class builderClass; + try { + //noinspection unchecked + builderClass = (Class) Class.forName(KnownTypes.getClassName(TypeUrl.of(typeUrlString)).value()) + .getClasses()[0]; + } catch (ClassNotFoundException | ClassCastException e) { + builderClass = null; + } - final Method setter = builder.getClass().getDeclaredMethod( - String.format("set%s%s", fieldName.substring(0, 1).toUpperCase(), fieldName.substring(1)), - argument.getClass()); + return builderClass; - setter.invoke(builder, argument); } private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 8f91286f62f..8dfa0b85d0e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -397,33 +397,44 @@ public void onCompleted() { } @Test - public void retrieve_only_selected_params_for_query() { - final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); - final TypeUrl customerType = TypeUrl.of(Customer.class); - - final Customer sampleCustomer = getSampleCustomer(); + public void retrieve_only_selected_param_for_query() { + reqestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); - stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + final Customer sampleCustomer = getSampleCustomer(); + final Customer customer = AnyPacker.unpack(messages.get(0)); + assertTrue(customer.getName() + .equals(sampleCustomer.getName())); + assertFalse(customer.hasId()); + assertTrue(customer.getFamilyList().isEmpty()); + } - final Query customerQuery = Query.newBuilder() - .setTarget( - Target.newBuilder().setIncludeAll(true) - .setType(customerType.getTypeName()) - .build()) - .setFieldMask(FieldMask.newBuilder() - .addPaths(Customer.getDescriptor().getFields().get(1).getFullName())) - .build(); + @Override + public void onError(Throwable t) { + } + @Override + public void onCompleted() { + } + }); + } - //noinspection OverlyComplexAnonymousInnerClass - stand.execute(customerQuery, new StreamObserver() { + @Test + public void retrieve_collection_fields_if_required() { + reqestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); + final Customer sampleCustomer = getSampleCustomer(); final Customer customer = AnyPacker.unpack(messages.get(0)); - assertTrue(customer.getName().equals(sampleCustomer.getName())); + assertEquals(customer.getFamilyList(), sampleCustomer.getFamilyList()); + + assertFalse(customer.hasName()); assertFalse(customer.hasId()); } @@ -442,10 +453,42 @@ private static Customer getSampleCustomer() { .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() .getLeastSignificantBits())) .setName(PersonName.newBuilder().setGivenName("Socrates").build()) + .addFamily(PersonName.newBuilder().setGivenName("Socrates's mother")) + .addFamily(PersonName.newBuilder().setGivenName("Socrates's father")) .build(); } + private static void reqestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + final FieldMask.Builder fieldMask = FieldMask.newBuilder(); + + for (int index : fieldIndexes) { + fieldMask.addPaths(Customer.getDescriptor() + .getFields() + .get(index) + .getFullName()); + } + + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .setFieldMask(fieldMask) + .build(); + + + stand.execute(customerQuery, observer); + } + private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. diff --git a/server/src/test/proto/spine/test/clientservice/customer/customer.proto b/server/src/test/proto/spine/test/clientservice/customer/customer.proto index adcd0866996..2389b93e5e9 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/customer.proto +++ b/server/src/test/proto/spine/test/clientservice/customer/customer.proto @@ -46,4 +46,7 @@ message Customer { // The name of the customer. people.PersonName name = 2; + + // Info about customer's family + repeated people.PersonName family = 3; } From 49d09a248692c0ee3c5d180e008859bfa05f7a60 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 10:34:42 +0300 Subject: [PATCH 149/361] Add tests for cases: several fields are requested; no fields are requested; field mask is invalid. --- .../org/spine3/server/stand/StandShould.java | 114 +++++++++++++++++- 1 file changed, 111 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 8dfa0b85d0e..a5ff9412eb3 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -72,6 +72,7 @@ import static com.google.common.collect.Maps.newHashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -87,6 +88,7 @@ /** * @author Alex Tymchenko + * @author Dmytro Dashenkov */ //It's OK for a test. @SuppressWarnings({"OverlyCoupledClass", "InstanceMethodNamingConvention", "ClassWithTooManyMethods"}) @@ -398,7 +400,7 @@ public void onCompleted() { @Test public void retrieve_only_selected_param_for_query() { - reqestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -424,7 +426,7 @@ public void onCompleted() { @Test public void retrieve_collection_fields_if_required() { - reqestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -448,6 +450,112 @@ public void onCompleted() { }); } + @Test + public void retrieve_all_requested_fields() { + requestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer sampleCustomer = getSampleCustomer(); + final Customer customer = AnyPacker.unpack(messages.get(0)); + assertEquals(customer.getFamilyList(), sampleCustomer.getFamilyList()); + + assertFalse(customer.hasName()); + assertTrue(customer.hasId()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + + @Test + public void retrieve_whole_entity_if_nothing_is_requested() { + //noinspection ZeroLengthArrayAllocation + requestSampleCustomer(new int[] {}, getDuplicateCostumerStreamObserver()); + } + + @Test + public void handle_mistakes_in_query_silently() { + //noinspection ZeroLengthArrayAllocation + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + // FieldMask with invalid type URLs. + final FieldMask.Builder fieldMask = FieldMask.newBuilder() + .addPaths("blablabla") + .addPaths(Project.getDescriptor().getFields().get(2).getFullName()); + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .setFieldMask(fieldMask) + .build(); + + + stand.execute(customerQuery, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + + assertNotEquals(customer, null); + + assertFalse(customer.hasId()); + assertFalse(customer.hasName()); + assertTrue(customer.getFamilyList().isEmpty()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + + private static StreamObserver getDuplicateCostumerStreamObserver() { + return new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + final Customer sampleCustomer = getSampleCustomer(); + + assertEquals(sampleCustomer.getName(), customer.getName()); + assertEquals(sampleCustomer.getFamilyList(), customer.getFamilyList()); + assertTrue(customer.hasId()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }; + } + private static Customer getSampleCustomer() { return Customer.newBuilder() .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() @@ -459,7 +567,7 @@ private static Customer getSampleCustomer() { } - private static void reqestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { + private static void requestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); final TypeUrl customerType = TypeUrl.of(Customer.class); From f4f610a9b0c6dcdaa4bde6b01757aa3c6f8ae905 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 18:13:15 +0300 Subject: [PATCH 150/361] Create filter method "applyFieldMask" for Query execution. --- .../java/org/spine3/server/stand/Stand.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 5205be6cc0c..f4a3b227965 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -32,7 +32,9 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; import com.google.protobuf.Message; +import com.google.protobuf.ProtocolStringList; import io.grpc.stub.StreamObserver; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; @@ -56,9 +58,11 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -246,7 +250,7 @@ public ImmutableSet getKnownAggregateTypes() { public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(readResult) + .addAllMessages(applyFieldMask(readResult, query.getFieldMask())) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -282,6 +286,23 @@ private ImmutableCollection internalExecute(Query query) { return result; } + private static Iterable applyFieldMask(Collection entities, FieldMask mask) { + final List filtered = new ArrayList<>(); + final ProtocolStringList filter = mask.getPathsList(); + + if (filter.isEmpty()) { + return Collections.unmodifiableCollection(entities); + } + + for (Any any : entities) { + if (filter.contains(any.getTypeUrl())) { + filtered.add(any); + } + } + + return Collections.unmodifiableList(filtered); + } + private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { final ImmutableCollection result; From 79e723a9d540d1c7e4d08650c1d31a38002ab1db Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 19:35:38 +0300 Subject: [PATCH 151/361] Use "equals" instead of "==" in InMemoryStorage::writeInternal. --- .../org/spine3/server/storage/memory/InMemoryStandStorage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 75d8a0e5716..7ce5714bf5b 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -97,7 +97,7 @@ protected Map readAllInternal() { protected void writeInternal(AggregateStateId id, EntityStorageRecord record) { final TypeUrl recordType = TypeUrl.of(record.getState() .getTypeUrl()); - checkState(id.getStateType() == recordType, "The typeUrl of the record does not correspond to id"); + checkState(id.getStateType().equals(recordType), "The typeUrl of the record does not correspond to id"); recordStorage.write(id, record); } From 995f3a7a91b9adf808ce7c454f58697f01da0803 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 15 Sep 2016 19:38:33 +0300 Subject: [PATCH 152/361] Add test for retrieving all data from a query with empty field mask. --- .../org/spine3/server/stand/StandShould.java | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index bd845d13d38..445ac199362 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -41,6 +41,7 @@ import org.spine3.client.Query; import org.spine3.client.QueryResponse; import org.spine3.client.Target; +import org.spine3.people.PersonName; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; @@ -50,6 +51,7 @@ import org.spine3.server.stand.Given.StandTestProjectionRepository; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; +import org.spine3.server.storage.memory.InMemoryStandStorage; import org.spine3.test.clientservice.customer.Customer; import org.spine3.test.clientservice.customer.CustomerId; import org.spine3.test.projection.Project; @@ -68,6 +70,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Maps.newHashMap; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -362,6 +365,55 @@ public void trigger_callback_upon_change_of_watched_aggregate_state() { assertEquals(packedState, memoizeCallback.newEntityState); } + @Test + public void retrieve_all_data_if_field_mask_is_not_set() { + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder().setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .build(); + + + //noinspection OverlyComplexAnonymousInnerClass + stand.execute(customerQuery, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + for (Descriptors.FieldDescriptor field : customer.getDescriptorForType().getFields()) { + assertTrue(customer.getField(field).equals(sampleCustomer.getField(field))); + } + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + + private static Customer getSampleCustomer() { + return Customer.newBuilder() + .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() + .getLeastSignificantBits())) + .setName(PersonName.newBuilder().setGivenName("Socrates").build()) + .build(); + + } + private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. @@ -667,14 +719,14 @@ private static List checkAndGetMessageList(MemoizeQueryResponseObserver res } - private static Stand prepareStandWithAggregateRepo(StandStorage standStorageMock) { + private static Stand prepareStandWithAggregateRepo(StandStorage standStorage) { final Stand stand = Stand.newBuilder() - .setStorage(standStorageMock) + .setStorage(standStorage) .build(); assertNotNull(stand); final BoundedContext boundedContext = newBoundedContext(stand); - final org.spine3.server.Given.CustomerAggregateRepository customerAggregateRepo = new org.spine3.server.Given.CustomerAggregateRepository(boundedContext); + final CustomerAggregateRepository customerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); return stand; } From 461d6b904dbf941bbb7c9e8d980d0c81a69d4b87 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 16 Sep 2016 17:40:01 +0300 Subject: [PATCH 153/361] Fix basic field mask functionality. --- .../java/org/spine3/server/stand/Stand.java | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index f4a3b227965..551917b3de7 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -32,6 +32,7 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import com.google.protobuf.Descriptors; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.ProtocolStringList; @@ -58,6 +59,9 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -249,8 +253,18 @@ public ImmutableSet getKnownAggregateTypes() { */ public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); + + Class builderClass; + try { + //noinspection unchecked + builderClass = (Class) Class.forName(KnownTypes.getClassName(TypeUrl.of(query.getTarget().getType())).value()) + .getClasses()[0]; + } catch (ClassNotFoundException | ClassCastException e) { + builderClass = null; + } + final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(applyFieldMask(readResult, query.getFieldMask())) + .addAllMessages(applyFieldMask(readResult, query.getFieldMask(), builderClass)) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -286,23 +300,53 @@ private ImmutableCollection internalExecute(Query query) { return result; } - private static Iterable applyFieldMask(Collection entities, FieldMask mask) { + @SuppressWarnings("MethodWithMultipleLoops") // Nested loops: each field in each entity. + private static Iterable applyFieldMask(Collection entities, FieldMask mask, @Nullable Class builderClass) { final List filtered = new ArrayList<>(); final ProtocolStringList filter = mask.getPathsList(); - if (filter.isEmpty()) { + if (filter.isEmpty() || builderClass == null) { return Collections.unmodifiableCollection(entities); } - for (Any any : entities) { - if (filter.contains(any.getTypeUrl())) { - filtered.add(any); + try { + final Constructor builderConstructor = builderClass.getDeclaredConstructor(); + builderConstructor.setAccessible(true); + + for (Any any : entities) { + final Message wholeMessage = AnyPacker.unpack(any); + final B builder = builderConstructor.newInstance(); + + for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { + if (filter.contains(field.getFullName())) { + invokeSetterOnBuilder(builder, field, wholeMessage.getField(field)); + } + } + + filtered.add(AnyPacker.pack(builder.build())); } + + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { + // If any reflection failure happens, return all the data without any mask applied. + // TODO:16-09-16:dmytro.dashenkov: Handle this exception for each field separately. + return Collections.unmodifiableCollection(entities); } return Collections.unmodifiableList(filtered); } + private static void invokeSetterOnBuilder(B builder, Descriptors.FieldDescriptor descriptor, Object argument) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + // TODO:16-09-16:dmytro.dashenkov: Handle collection case. + final String fieldName = descriptor.getName(); + + final Method setter = builder.getClass().getDeclaredMethod( + String.format("set%s%s", fieldName.substring(0, 1).toUpperCase(), fieldName.substring(1)), + argument.getClass()); + + setter.invoke(builder, argument); + } + private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { final ImmutableCollection result; From dd879bf2cc48538111cbbc83773e9e4a2b0b7374 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 16 Sep 2016 17:40:17 +0300 Subject: [PATCH 154/361] Fix basic field mask functionality test. --- .../org/spine3/server/stand/StandShould.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 445ac199362..c6a3173464f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -29,6 +29,7 @@ import com.google.common.collect.Maps; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; +import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; @@ -405,6 +406,47 @@ public void onCompleted() { }); } + @Test + public void retrieve_only_selected_params_for_query() { + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder().setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .setFieldMask(FieldMask.newBuilder() + .addPaths(Customer.getDescriptor().getFields().get(1).getFullName())) + .build(); + + + //noinspection OverlyComplexAnonymousInnerClass + stand.execute(customerQuery, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + assertTrue(customer.getName().equals(sampleCustomer.getName())); + assertFalse(customer.hasId()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + private static Customer getSampleCustomer() { return Customer.newBuilder() .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() From 9ab0bcea53d237c84c964ca6ad35070c71b392e5 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 16 Sep 2016 18:37:02 +0300 Subject: [PATCH 155/361] Refactor field mask applying. --- .../java/org/spine3/server/stand/Stand.java | 42 ++++------ .../org/spine3/server/stand/StandShould.java | 77 +++++++++++++++---- .../clientservice/customer/customer.proto | 3 + 3 files changed, 80 insertions(+), 42 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 551917b3de7..5a7e49ffbe2 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -33,7 +33,6 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import com.google.protobuf.Descriptors; -import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.ProtocolStringList; import io.grpc.stub.StreamObserver; @@ -61,7 +60,6 @@ import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -253,18 +251,8 @@ public ImmutableSet getKnownAggregateTypes() { */ public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); - - Class builderClass; - try { - //noinspection unchecked - builderClass = (Class) Class.forName(KnownTypes.getClassName(TypeUrl.of(query.getTarget().getType())).value()) - .getClasses()[0]; - } catch (ClassNotFoundException | ClassCastException e) { - builderClass = null; - } - final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(applyFieldMask(readResult, query.getFieldMask(), builderClass)) + .addAllMessages(applyFieldMask(readResult, query)) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -301,9 +289,11 @@ private ImmutableCollection internalExecute(Query query) { } @SuppressWarnings("MethodWithMultipleLoops") // Nested loops: each field in each entity. - private static Iterable applyFieldMask(Collection entities, FieldMask mask, @Nullable Class builderClass) { + private static Iterable applyFieldMask(Collection entities, Query query) { final List filtered = new ArrayList<>(); - final ProtocolStringList filter = mask.getPathsList(); + final ProtocolStringList filter = query.getFieldMask().getPathsList(); + + final Class builderClass = getBuilderForType(query.getTarget().getType()); if (filter.isEmpty() || builderClass == null) { return Collections.unmodifiableCollection(entities); @@ -319,7 +309,7 @@ private static Iterable applyFieldMas for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { if (filter.contains(field.getFullName())) { - invokeSetterOnBuilder(builder, field, wholeMessage.getField(field)); + builder.setField(field, wholeMessage.getField(field)); } } @@ -328,23 +318,25 @@ private static Iterable applyFieldMas } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { // If any reflection failure happens, return all the data without any mask applied. - // TODO:16-09-16:dmytro.dashenkov: Handle this exception for each field separately. return Collections.unmodifiableCollection(entities); } return Collections.unmodifiableList(filtered); } - private static void invokeSetterOnBuilder(B builder, Descriptors.FieldDescriptor descriptor, Object argument) - throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - // TODO:16-09-16:dmytro.dashenkov: Handle collection case. - final String fieldName = descriptor.getName(); + @Nullable + private static Class getBuilderForType(String typeUrlString) { + Class builderClass; + try { + //noinspection unchecked + builderClass = (Class) Class.forName(KnownTypes.getClassName(TypeUrl.of(typeUrlString)).value()) + .getClasses()[0]; + } catch (ClassNotFoundException | ClassCastException e) { + builderClass = null; + } - final Method setter = builder.getClass().getDeclaredMethod( - String.format("set%s%s", fieldName.substring(0, 1).toUpperCase(), fieldName.substring(1)), - argument.getClass()); + return builderClass; - setter.invoke(builder, argument); } private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index c6a3173464f..cf51e352058 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -407,33 +407,44 @@ public void onCompleted() { } @Test - public void retrieve_only_selected_params_for_query() { - final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); - final TypeUrl customerType = TypeUrl.of(Customer.class); - - final Customer sampleCustomer = getSampleCustomer(); + public void retrieve_only_selected_param_for_query() { + reqestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); - stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + final Customer sampleCustomer = getSampleCustomer(); + final Customer customer = AnyPacker.unpack(messages.get(0)); + assertTrue(customer.getName() + .equals(sampleCustomer.getName())); + assertFalse(customer.hasId()); + assertTrue(customer.getFamilyList().isEmpty()); + } - final Query customerQuery = Query.newBuilder() - .setTarget( - Target.newBuilder().setIncludeAll(true) - .setType(customerType.getTypeName()) - .build()) - .setFieldMask(FieldMask.newBuilder() - .addPaths(Customer.getDescriptor().getFields().get(1).getFullName())) - .build(); + @Override + public void onError(Throwable t) { + } + @Override + public void onCompleted() { + } + }); + } - //noinspection OverlyComplexAnonymousInnerClass - stand.execute(customerQuery, new StreamObserver() { + @Test + public void retrieve_collection_fields_if_required() { + reqestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); + final Customer sampleCustomer = getSampleCustomer(); final Customer customer = AnyPacker.unpack(messages.get(0)); - assertTrue(customer.getName().equals(sampleCustomer.getName())); + assertEquals(customer.getFamilyList(), sampleCustomer.getFamilyList()); + + assertFalse(customer.hasName()); assertFalse(customer.hasId()); } @@ -452,10 +463,42 @@ private static Customer getSampleCustomer() { .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() .getLeastSignificantBits())) .setName(PersonName.newBuilder().setGivenName("Socrates").build()) + .addFamily(PersonName.newBuilder().setGivenName("Socrates's mother")) + .addFamily(PersonName.newBuilder().setGivenName("Socrates's father")) .build(); } + private static void reqestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + final FieldMask.Builder fieldMask = FieldMask.newBuilder(); + + for (int index : fieldIndexes) { + fieldMask.addPaths(Customer.getDescriptor() + .getFields() + .get(index) + .getFullName()); + } + + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .setFieldMask(fieldMask) + .build(); + + + stand.execute(customerQuery, observer); + } + private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. diff --git a/server/src/test/proto/spine/test/clientservice/customer/customer.proto b/server/src/test/proto/spine/test/clientservice/customer/customer.proto index adcd0866996..2389b93e5e9 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/customer.proto +++ b/server/src/test/proto/spine/test/clientservice/customer/customer.proto @@ -46,4 +46,7 @@ message Customer { // The name of the customer. people.PersonName name = 2; + + // Info about customer's family + repeated people.PersonName family = 3; } From 4b19a1447cf848c303a49b446001fa2ed8fdd6db Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 10:34:42 +0300 Subject: [PATCH 156/361] Add tests for cases: several fields are requested; no fields are requested; field mask is invalid. --- .../org/spine3/server/stand/StandShould.java | 114 +++++++++++++++++- 1 file changed, 111 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index cf51e352058..afb0a8f132f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -72,6 +72,7 @@ import static com.google.common.collect.Maps.newHashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -87,6 +88,7 @@ /** * @author Alex Tymchenko + * @author Dmytro Dashenkov */ //It's OK for a test. @SuppressWarnings({"OverlyCoupledClass", "InstanceMethodNamingConvention", "ClassWithTooManyMethods"}) @@ -408,7 +410,7 @@ public void onCompleted() { @Test public void retrieve_only_selected_param_for_query() { - reqestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -434,7 +436,7 @@ public void onCompleted() { @Test public void retrieve_collection_fields_if_required() { - reqestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -458,6 +460,112 @@ public void onCompleted() { }); } + @Test + public void retrieve_all_requested_fields() { + requestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer sampleCustomer = getSampleCustomer(); + final Customer customer = AnyPacker.unpack(messages.get(0)); + assertEquals(customer.getFamilyList(), sampleCustomer.getFamilyList()); + + assertFalse(customer.hasName()); + assertTrue(customer.hasId()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + + @Test + public void retrieve_whole_entity_if_nothing_is_requested() { + //noinspection ZeroLengthArrayAllocation + requestSampleCustomer(new int[] {}, getDuplicateCostumerStreamObserver()); + } + + @Test + public void handle_mistakes_in_query_silently() { + //noinspection ZeroLengthArrayAllocation + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final TypeUrl customerType = TypeUrl.of(Customer.class); + + final Customer sampleCustomer = getSampleCustomer(); + + stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); + + // FieldMask with invalid type URLs. + final FieldMask.Builder fieldMask = FieldMask.newBuilder() + .addPaths("blablabla") + .addPaths(Project.getDescriptor().getFields().get(2).getFullName()); + final Query customerQuery = Query.newBuilder() + .setTarget( + Target.newBuilder() + .setIncludeAll(true) + .setType(customerType.getTypeName()) + .build()) + .setFieldMask(fieldMask) + .build(); + + + stand.execute(customerQuery, new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + + assertNotEquals(customer, null); + + assertFalse(customer.hasId()); + assertFalse(customer.hasName()); + assertTrue(customer.getFamilyList().isEmpty()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }); + } + + private static StreamObserver getDuplicateCostumerStreamObserver() { + return new StreamObserver() { + @Override + public void onNext(QueryResponse value) { + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer customer = AnyPacker.unpack(messages.get(0)); + final Customer sampleCustomer = getSampleCustomer(); + + assertEquals(sampleCustomer.getName(), customer.getName()); + assertEquals(sampleCustomer.getFamilyList(), customer.getFamilyList()); + assertTrue(customer.hasId()); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onCompleted() { + } + }; + } + private static Customer getSampleCustomer() { return Customer.newBuilder() .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() @@ -469,7 +577,7 @@ private static Customer getSampleCustomer() { } - private static void reqestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { + private static void requestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); final TypeUrl customerType = TypeUrl.of(Customer.class); From 491ced213a89d814809f5ab523fdcd434ef22e8d Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 10:51:28 +0300 Subject: [PATCH 157/361] Merge. --- server/src/main/java/org/spine3/server/stand/Stand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 5a7e49ffbe2..e18856e0c56 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -62,6 +62,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; From e82f6e2fdebdf8b27260782f50616d80e88d0c02 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 15:48:29 +0300 Subject: [PATCH 158/361] Move applying field mask logic to "FieldMasks" util class. --- .../org/spine3/server/entity/FieldMasks.java | 113 ++++++++++++++++++ .../java/org/spine3/server/stand/Stand.java | 62 +--------- 2 files changed, 118 insertions(+), 57 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/entity/FieldMasks.java diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java new file mode 100644 index 00000000000..6755a98a13b --- /dev/null +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.entity; + +import com.google.protobuf.Any; +import com.google.protobuf.Descriptors; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import com.google.protobuf.ProtocolStringList; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.KnownTypes; +import org.spine3.protobuf.TypeUrl; + +import javax.annotation.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * + * // TODO:19-09-16:dmytro.dashenkov: Add javadoc. + * @author Dmytro Dashenkov + */ +public class FieldMasks { + + private FieldMasks() { + } + + /** + * // TODO:19-09-16:dmytro.dashenkov: Add javadoc. + * @param mask + * @param entities + * @param typeUrl + * @param + * @return + */ + @SuppressWarnings("MethodWithMultipleLoops") + public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { + final List filtered = new ArrayList<>(); + final ProtocolStringList filter = mask.getPathsList(); + + final Class builderClass = getBuilderForType(typeUrl); + + if (filter.isEmpty() || builderClass == null) { + return Collections.unmodifiableCollection(entities); + } + + try { + final Constructor builderConstructor = builderClass.getDeclaredConstructor(); + builderConstructor.setAccessible(true); + + for (Any any : entities) { + final Message wholeMessage = AnyPacker.unpack(any); + final B builder = builderConstructor.newInstance(); + + for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { + if (filter.contains(field.getFullName())) { + builder.setField(field, wholeMessage.getField(field)); + } + } + + filtered.add(AnyPacker.pack(builder.build())); + } + + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { + // If any reflection failure happens, return all the data without any mask applied. + return Collections.unmodifiableCollection(entities); + } + + return Collections.unmodifiableList(filtered); + } + + // TODO:19-09-16:dmytro.dashenkov: Add javadoc. + public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask.Builder maskBuilder, Collection entities, TypeUrl typeUrl) { + return applyMask(maskBuilder.build(), entities, typeUrl); + } + + @Nullable + private static Class getBuilderForType(TypeUrl typeUrl) { + Class builderClass; + try { + //noinspection unchecked + builderClass = (Class) Class.forName(KnownTypes.getClassName(typeUrl).value()) + .getClasses()[0]; + } catch (ClassNotFoundException | ClassCastException e) { + builderClass = null; + } + + return builderClass; + + } +} diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index e18856e0c56..d480c7b1333 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -32,9 +32,7 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; -import com.google.protobuf.Descriptors; import com.google.protobuf.Message; -import com.google.protobuf.ProtocolStringList; import io.grpc.stub.StreamObserver; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; @@ -51,6 +49,7 @@ import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.entity.Entity; import org.spine3.server.entity.EntityRepository; +import org.spine3.server.entity.FieldMasks; import org.spine3.server.entity.Repository; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; @@ -58,14 +57,9 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -252,8 +246,11 @@ public ImmutableSet getKnownAggregateTypes() { */ public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); + final Collection queryResult = FieldMasks.applyMask(query.getFieldMask(), + readResult, + TypeUrl.of(query.getTarget().getType())); final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(applyFieldMask(readResult, query)) + .addAllMessages(queryResult) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -289,56 +286,7 @@ private ImmutableCollection internalExecute(Query query) { return result; } - @SuppressWarnings("MethodWithMultipleLoops") // Nested loops: each field in each entity. - private static Iterable applyFieldMask(Collection entities, Query query) { - final List filtered = new ArrayList<>(); - final ProtocolStringList filter = query.getFieldMask().getPathsList(); - final Class builderClass = getBuilderForType(query.getTarget().getType()); - - if (filter.isEmpty() || builderClass == null) { - return Collections.unmodifiableCollection(entities); - } - - try { - final Constructor builderConstructor = builderClass.getDeclaredConstructor(); - builderConstructor.setAccessible(true); - - for (Any any : entities) { - final Message wholeMessage = AnyPacker.unpack(any); - final B builder = builderConstructor.newInstance(); - - for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { - if (filter.contains(field.getFullName())) { - builder.setField(field, wholeMessage.getField(field)); - } - } - - filtered.add(AnyPacker.pack(builder.build())); - } - - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { - // If any reflection failure happens, return all the data without any mask applied. - return Collections.unmodifiableCollection(entities); - } - - return Collections.unmodifiableList(filtered); - } - - @Nullable - private static Class getBuilderForType(String typeUrlString) { - Class builderClass; - try { - //noinspection unchecked - builderClass = (Class) Class.forName(KnownTypes.getClassName(TypeUrl.of(typeUrlString)).value()) - .getClasses()[0]; - } catch (ClassNotFoundException | ClassCastException e) { - builderClass = null; - } - - return builderClass; - - } private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { final ImmutableCollection result; From b52fc7f76e9f34372fa21ddb628b71d131cdff05 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 16:16:58 +0300 Subject: [PATCH 159/361] Add by-entity field mask applier. --- .../org/spine3/server/entity/FieldMasks.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 6755a98a13b..72412edf35a 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -96,6 +96,37 @@ public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") F return applyMask(maskBuilder.build(), entities, typeUrl); } + // TODO:19-09-16:dmytro.dashenkov: Add javadoc. + public static Message applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Message entity, TypeUrl typeUrl) { + final ProtocolStringList filter = mask.getPathsList(); + + final Class builderClass = getBuilderForType(typeUrl); + + if (filter.isEmpty() || builderClass == null) { + return entity; + } + + try { + final Constructor builderConstructor = builderClass.getDeclaredConstructor(); + builderConstructor.setAccessible(true); + + final B builder = builderConstructor.newInstance(); + + for (Descriptors.FieldDescriptor field : entity.getDescriptorForType().getFields()) { + if (filter.contains(field.getFullName())) { + builder.setField(field, entity.getField(field)); + } + } + + return builder.build(); + + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { + return entity; + } + + + } + @Nullable private static Class getBuilderForType(TypeUrl typeUrl) { Class builderClass; From 1f1b32461bc9af44b416b570e36dc3e4340ece0e Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 17:17:13 +0300 Subject: [PATCH 160/361] Apply field mask when fetching from in-stand storage. --- .../org/spine3/server/entity/FieldMasks.java | 10 ++++---- .../java/org/spine3/server/stand/Stand.java | 24 ++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 72412edf35a..0e7a26f0899 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -20,7 +20,6 @@ package org.spine3.server.entity; -import com.google.protobuf.Any; import com.google.protobuf.Descriptors; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; @@ -56,8 +55,8 @@ private FieldMasks() { * @return */ @SuppressWarnings("MethodWithMultipleLoops") - public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { - final List filtered = new ArrayList<>(); + public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { + final List filtered = new ArrayList<>(); final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(typeUrl); @@ -70,8 +69,7 @@ public static Collection applyMask(@SuppressWar final Constructor builderConstructor = builderClass.getDeclaredConstructor(); builderConstructor.setAccessible(true); - for (Any any : entities) { - final Message wholeMessage = AnyPacker.unpack(any); + for (Message wholeMessage : entities) { final B builder = builderConstructor.newInstance(); for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { @@ -92,7 +90,7 @@ public static Collection applyMask(@SuppressWar } // TODO:19-09-16:dmytro.dashenkov: Add javadoc. - public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask.Builder maskBuilder, Collection entities, TypeUrl typeUrl) { + public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask.Builder maskBuilder, Collection entities, TypeUrl typeUrl) { return applyMask(maskBuilder.build(), entities, typeUrl); } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index d480c7b1333..56e9bd0a0b4 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -32,6 +32,7 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.spine3.base.Responses; @@ -246,11 +247,8 @@ public ImmutableSet getKnownAggregateTypes() { */ public void execute(Query query, StreamObserver responseObserver) { final ImmutableCollection readResult = internalExecute(query); - final Collection queryResult = FieldMasks.applyMask(query.getFieldMask(), - readResult, - TypeUrl.of(query.getTarget().getType())); final QueryResponse response = QueryResponse.newBuilder() - .addAllMessages(queryResult) + .addAllMessages(readResult) .setResponse(Responses.ok()) .build(); responseObserver.onNext(response); @@ -270,13 +268,14 @@ private ImmutableCollection internalExecute(Query query) { if (repository != null) { // the target references an entity state + // TODO:19-09-16:dmytro.dashenkov: Apply field mask. ImmutableCollection entities = fetchFromEntityRepository(target, repository); feedEntitiesToBuilder(resultBuilder, entities); } else if (knownAggregateTypes.contains(typeUrl)) { // the target relates to an {@code Aggregate} state - ImmutableCollection stateRecords = fetchFromStandStorage(target, typeUrl); + ImmutableCollection stateRecords = fetchFromStandStorage(query, typeUrl); feedStateRecordsToBuilder(resultBuilder, stateRecords); } @@ -288,8 +287,11 @@ private ImmutableCollection internalExecute(Query query) { - private ImmutableCollection fetchFromStandStorage(Target target, final TypeUrl typeUrl) { - final ImmutableCollection result; + @SuppressWarnings("MethodWithMoreThanThreeNegations") // A lot of small logical conditions is checked. + private ImmutableCollection fetchFromStandStorage(Query query, final TypeUrl typeUrl) { + ImmutableCollection result; + final Target target = query.getTarget(); + final FieldMask fieldMask = query.getFieldMask(); if (target.getIncludeAll()) { result = storage.readAllByType(typeUrl); @@ -319,7 +321,13 @@ private ImmutableCollection fetchFromStandStorage(Target ta } } - return result; + + if (fieldMask != null && !fieldMask.getPathsList().isEmpty()) { + //noinspection unchecked + result = ImmutableList.copyOf((Collection) FieldMasks.applyMask(fieldMask, result, typeUrl)); + } + + return ImmutableList.copyOf(result); } private ImmutableCollection handleBulkRead(Collection stateIds) { From 7a4da067cf76bad3155b492be4c0b09f852e5227 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 18:31:33 +0300 Subject: [PATCH 161/361] Generify "FieldMasks" util. --- .../org/spine3/server/entity/FieldMasks.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 0e7a26f0899..5ef9006a93b 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -24,7 +24,6 @@ import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.ProtocolStringList; -import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; @@ -41,6 +40,7 @@ * // TODO:19-09-16:dmytro.dashenkov: Add javadoc. * @author Dmytro Dashenkov */ +@SuppressWarnings("UtilityClass") public class FieldMasks { private FieldMasks() { @@ -54,9 +54,9 @@ private FieldMasks() { * @param * @return */ - @SuppressWarnings("MethodWithMultipleLoops") - public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { - final List filtered = new ArrayList<>(); + @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { + final List filtered = new ArrayList<>(); final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(typeUrl); @@ -78,7 +78,7 @@ public static Collection applyMas } } - filtered.add(AnyPacker.pack(builder.build())); + filtered.add((M) builder.build()); } } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { @@ -89,13 +89,13 @@ public static Collection applyMas return Collections.unmodifiableList(filtered); } - // TODO:19-09-16:dmytro.dashenkov: Add javadoc. - public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask.Builder maskBuilder, Collection entities, TypeUrl typeUrl) { - return applyMask(maskBuilder.build(), entities, typeUrl); + public static boolean isEffective(@Nullable FieldMask fieldMask) { + return fieldMask != null && !fieldMask.getPathsList().isEmpty(); } // TODO:19-09-16:dmytro.dashenkov: Add javadoc. - public static Message applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Message entity, TypeUrl typeUrl) { + @SuppressWarnings("unchecked") + public static M applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M entity, TypeUrl typeUrl) { final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(typeUrl); @@ -116,7 +116,7 @@ public static Message applyMask(@SuppressWarnings("T } } - return builder.build(); + return (M) builder.build(); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { return entity; From e75ddf28619556d8cd0cab29faf5a34c8c1c79f8 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 19 Sep 2016 18:54:01 +0300 Subject: [PATCH 162/361] Change API to make field mask applying in storage possible. --- .../server/entity/EntityRepository.java | 4 ++- .../java/org/spine3/server/stand/Stand.java | 36 ++++++++++--------- .../spine3/server/storage/RecordStorage.java | 17 ++++++++- .../spine3/server/storage/StandStorage.java | 11 ++++++ .../storage/memory/InMemoryStandStorage.java | 7 ++++ .../org/spine3/server/stand/StandShould.java | 2 +- 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 2a816a9450f..0035f0f95e7 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; import org.spine3.client.EntityFilters; @@ -198,7 +199,8 @@ public E apply(@Nullable Map.Entry input) { * @return all the entities in this repository passed the filters. */ @CheckReturnValue - public ImmutableCollection findAll(EntityFilters filters) { + public ImmutableCollection findAll(EntityFilters filters, @Nullable FieldMask fieldMask) { + // TODO:19-09-16:dmytro.dashenkov: Add support for field mask processing. final List idsList = filters.getIdFilter() .getIdsList(); final Class expectedIdClass = getIdClass(); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 56e9bd0a0b4..cd18449c615 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -268,8 +268,7 @@ private ImmutableCollection internalExecute(Query query) { if (repository != null) { // the target references an entity state - // TODO:19-09-16:dmytro.dashenkov: Apply field mask. - ImmutableCollection entities = fetchFromEntityRepository(target, repository); + ImmutableCollection entities = fetchFromEntityRepository(query, repository); feedEntitiesToBuilder(resultBuilder, entities); } else if (knownAggregateTypes.contains(typeUrl)) { @@ -292,9 +291,12 @@ private ImmutableCollection fetchFromStandStorage(Query que ImmutableCollection result; final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); + final boolean shouldApplyFieldMask = FieldMasks.isEffective(fieldMask); if (target.getIncludeAll()) { - result = storage.readAllByType(typeUrl); + result = shouldApplyFieldMask ? + storage.readAllByType(typeUrl, fieldMask) : + storage.readAllByType(typeUrl); } else { final EntityFilters filters = target.getFilters(); @@ -311,10 +313,12 @@ private ImmutableCollection fetchFromStandStorage(Query que // may be more effective, as bulk reading implies additional time and performance expenses. final AggregateStateId singleId = stateIds.iterator() .next(); - final EntityStorageRecord singleResult = storage.read(singleId); + final EntityStorageRecord singleResult = shouldApplyFieldMask ? + storage.read(singleId, fieldMask) : + storage.read(singleId); result = ImmutableList.of(singleResult); } else { - result = handleBulkRead(stateIds); + result = handleBulkRead(stateIds, fieldMask, shouldApplyFieldMask); } } else { result = ImmutableList.of(); @@ -322,17 +326,14 @@ private ImmutableCollection fetchFromStandStorage(Query que } - if (fieldMask != null && !fieldMask.getPathsList().isEmpty()) { - //noinspection unchecked - result = ImmutableList.copyOf((Collection) FieldMasks.applyMask(fieldMask, result, typeUrl)); - } - - return ImmutableList.copyOf(result); + return result; } - private ImmutableCollection handleBulkRead(Collection stateIds) { + private ImmutableCollection handleBulkRead(Collection stateIds, FieldMask fieldMask, boolean applyFieldMask) { ImmutableCollection result; - final Iterable bulkReadResults = storage.readBulk(stateIds); + final Iterable bulkReadResults = applyFieldMask ? + storage.readBulk(stateIds, fieldMask) : + storage.readBulk(stateIds); result = FluentIterable.from(bulkReadResults) .filter(new Predicate() { @Override @@ -359,13 +360,16 @@ public AggregateStateId apply(@Nullable EntityId input) { }; } - private static ImmutableCollection fetchFromEntityRepository(Target target, EntityRepository repository) { + private static ImmutableCollection fetchFromEntityRepository(Query query, EntityRepository repository) { final ImmutableCollection result; - if (target.getIncludeAll()) { + final Target target = query.getTarget(); + final FieldMask fieldMask = query.getFieldMask(); + + if (target.getIncludeAll() && !FieldMasks.isEffective(fieldMask)) { result = repository.findAll(); } else { final EntityFilters filters = target.getFilters(); - result = repository.findAll(filters); + result = repository.findAll(filters, fieldMask); } return result; } diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index f195163973e..81f15a9a0fb 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -20,10 +20,10 @@ package org.spine3.server.storage; +import com.google.protobuf.FieldMask; import org.spine3.SPI; import org.spine3.server.entity.Entity; -import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import java.util.Map; @@ -47,6 +47,11 @@ protected RecordStorage(boolean multitenant) { @Override public EntityStorageRecord read(I id) { + return read(id, null); + } + + public EntityStorageRecord read(I id, @Nullable FieldMask fieldMask) { + // TODO:19-09-16:dmytro.dashenkov: Add support for field mask processing. checkNotClosed(); checkNotNull(id); @@ -57,6 +62,8 @@ public EntityStorageRecord read(I id) { return record; } + + @Override public void write(I id, EntityStorageRecord record) { checkNotNull(id); @@ -75,6 +82,14 @@ public Iterable readBulk(Iterable ids) { return readBulkInternal(ids); } + public Iterable readBulk(Iterable ids, FieldMask fieldMask) { + // TODO:19-09-16:dmytro.dashenkov: Support field mask processing. + checkNotClosed(); + checkNotNull(ids); + + return readBulkInternal(ids); + } + @Override public Map readAll() { checkNotClosed(); diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index aef39ba950f..af2e9cc18bd 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -23,10 +23,13 @@ import com.google.common.collect.ImmutableCollection; import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; import org.spine3.protobuf.TypeUrl; import org.spine3.server.stand.AggregateStateId; import org.spine3.server.stand.Stand; +import javax.annotation.Nullable; + /** * Contract for {@link Stand} storage. * @@ -50,4 +53,12 @@ protected StandStorage(boolean multitenant) { * @return the state records which {@link Any#getTypeUrl()} equals the argument value */ public abstract ImmutableCollection readAllByType(TypeUrl type); + + /** + * Reads all the state records by the given type. + * + * @param type a {@link TypeUrl} instance + * @return the state records which {@link Any#getTypeUrl()} equals the argument value + */ + public abstract ImmutableCollection readAllByType(TypeUrl type, @Nullable FieldMask fieldMask); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 7ce5714bf5b..e8452119856 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; +import com.google.protobuf.FieldMask; import org.spine3.protobuf.TypeUrl; import org.spine3.server.stand.AggregateStateId; import org.spine3.server.storage.EntityStorageRecord; @@ -59,6 +60,12 @@ public static Builder newBuilder() { @Override public ImmutableCollection readAllByType(final TypeUrl type) { + return readAllByType(type, null); + } + + @Override + public ImmutableCollection readAllByType(final TypeUrl type, @Nullable FieldMask fieldMask) { + // TODO:19-09-16:dmytro.dashenkov: Add support for field mask processing. final Map allRecords = readAll(); final Map resultMap = Maps.filterKeys(allRecords, new Predicate() { @Override diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index afb0a8f132f..03bd0b6229e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -767,7 +767,7 @@ private static void setupExpectedFindAllBehaviour(Map sample when(projectionRepository.findAll()).thenReturn(allResults); final EntityFilters matchingFilter = argThat(entityFilterMatcher(projectIds)); - when(projectionRepository.findAll(matchingFilter)).thenReturn(allResults); + when(projectionRepository.findAll(matchingFilter, null)).thenReturn(allResults); } @SuppressWarnings("OverlyComplexAnonymousInnerClass") From a7f715b7993596f50c2e1deb136d0c45679fbf5f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 20 Sep 2016 11:08:18 +0300 Subject: [PATCH 163/361] Create a small utility class for working with queries. --- .../main/java/org/spine3/base/Queries.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 client/src/main/java/org/spine3/base/Queries.java diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java new file mode 100644 index 00000000000..e9d25aab5b5 --- /dev/null +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -0,0 +1,97 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.base; + +import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; +import org.spine3.client.Query; +import org.spine3.client.Target; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; + +import javax.annotation.Nullable; +import java.util.Set; + +/** + * Client-side utilities for working with queries. + * + * @author Alex Tymchenko + */ +public class Queries { + + private Queries() { + } + + public static Query readByIds(Class entityClass, Set ids) { + final Query result = compose(entityClass, ids, null); + return result; + } + + public static Query readAll(Class entityClass) { + final Query result = compose(entityClass, null, null); + return result; + } + + + // TODO[alex.tymchenko]: think of Optional instead. Consider Java 8 vs Guava Optional. + private static Query compose(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { + final TypeUrl type = TypeUrl.of(entityClass); + + final boolean includeAll = ids == null; + + final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); + + if (!includeAll) { + for (Message rawId : ids) { + final Any packedId = AnyPacker.pack(rawId); + final EntityId entityId = EntityId.newBuilder() + .setId(packedId) + .build(); + idFilterBuilder.addIds(entityId); + } + } + final EntityIdFilter idFilter = idFilterBuilder.build(); + final EntityFilters filters = EntityFilters.newBuilder() + .setIdFilter(idFilter) + .build(); + + final Target target = Target.newBuilder() + .setIncludeAll(includeAll) + .setType(type.getTypeName()) + .setFilters(filters) + .build(); + + + final Query.Builder queryBuilder = Query.newBuilder() + .setTarget(target); + if (fieldMask != null) { + queryBuilder.setFieldMask(fieldMask); + } + final Query result = queryBuilder + .build(); + return result; + } +} From 5261f5b07c943162444c4ca61a3337fd74b85bcb Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 20 Sep 2016 11:12:03 +0300 Subject: [PATCH 164/361] Add Target-related methods. --- .../main/java/org/spine3/base/Queries.java | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index e9d25aab5b5..7ded9ee40ce 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -46,18 +46,42 @@ private Queries() { } public static Query readByIds(Class entityClass, Set ids) { - final Query result = compose(entityClass, ids, null); + final Query result = composeQuery(entityClass, ids, null); return result; } public static Query readAll(Class entityClass) { - final Query result = compose(entityClass, null, null); + final Query result = composeQuery(entityClass, null, null); + return result; + } + + public static Target someOf(Class entityClass, Set ids) { + final Target result = composeTarget(entityClass, ids); + return result; + } + + public static Target allOf(Class entityClass) { + final Target result = composeTarget(entityClass, null); return result; } // TODO[alex.tymchenko]: think of Optional instead. Consider Java 8 vs Guava Optional. - private static Query compose(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { + private static Query composeQuery(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { + final Target target = composeTarget(entityClass, ids); + + + final Query.Builder queryBuilder = Query.newBuilder() + .setTarget(target); + if (fieldMask != null) { + queryBuilder.setFieldMask(fieldMask); + } + final Query result = queryBuilder + .build(); + return result; + } + + private static Target composeTarget(Class entityClass, @Nullable Set ids) { final TypeUrl type = TypeUrl.of(entityClass); final boolean includeAll = ids == null; @@ -78,20 +102,10 @@ private static Query compose(Class entityClass, @Nullable Set .setIdFilter(idFilter) .build(); - final Target target = Target.newBuilder() - .setIncludeAll(includeAll) - .setType(type.getTypeName()) - .setFilters(filters) - .build(); - - - final Query.Builder queryBuilder = Query.newBuilder() - .setTarget(target); - if (fieldMask != null) { - queryBuilder.setFieldMask(fieldMask); - } - final Query result = queryBuilder - .build(); - return result; + return Target.newBuilder() + .setIncludeAll(includeAll) + .setType(type.getTypeName()) + .setFilters(filters) + .build(); } } From a8ae84d4b1febf99d69921278a2f64367d3173c5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 20 Sep 2016 11:36:46 +0300 Subject: [PATCH 165/361] * Improve readability of Target-related methods in the Queries utility. * Refactor Stand tests to use Queries utility class. --- .../main/java/org/spine3/base/Queries.java | 76 +++++---- .../org/spine3/server/stand/StandShould.java | 154 ++++-------------- 2 files changed, 73 insertions(+), 157 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index 7ded9ee40ce..dbf8d89031b 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -35,6 +35,8 @@ import javax.annotation.Nullable; import java.util.Set; +import static org.spine3.base.Queries.Targets.composeTarget; + /** * Client-side utilities for working with queries. * @@ -45,7 +47,7 @@ public class Queries { private Queries() { } - public static Query readByIds(Class entityClass, Set ids) { + public static Query readByIds(Class entityClass, Set ids) { final Query result = composeQuery(entityClass, ids, null); return result; } @@ -55,19 +57,8 @@ public static Query readAll(Class entityClass) { return result; } - public static Target someOf(Class entityClass, Set ids) { - final Target result = composeTarget(entityClass, ids); - return result; - } - - public static Target allOf(Class entityClass) { - final Target result = composeTarget(entityClass, null); - return result; - } - - // TODO[alex.tymchenko]: think of Optional instead. Consider Java 8 vs Guava Optional. - private static Query composeQuery(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { + private static Query composeQuery(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { final Target target = composeTarget(entityClass, ids); @@ -81,31 +72,48 @@ private static Query composeQuery(Class entityClass, @Nullabl return result; } - private static Target composeTarget(Class entityClass, @Nullable Set ids) { - final TypeUrl type = TypeUrl.of(entityClass); - final boolean includeAll = ids == null; + public static class Targets { + + private Targets() { + } + + public static Target someOf(Class entityClass, Set ids) { + final Target result = composeTarget(entityClass, ids); + return result; + } + + public static Target allOf(Class entityClass) { + final Target result = composeTarget(entityClass, null); + return result; + } + + /* package */ static Target composeTarget(Class entityClass, @Nullable Set ids) { + final TypeUrl type = TypeUrl.of(entityClass); + + final boolean includeAll = ids == null; - final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); + final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); - if (!includeAll) { - for (Message rawId : ids) { - final Any packedId = AnyPacker.pack(rawId); - final EntityId entityId = EntityId.newBuilder() - .setId(packedId) - .build(); - idFilterBuilder.addIds(entityId); + if (!includeAll) { + for (Message rawId : ids) { + final Any packedId = AnyPacker.pack(rawId); + final EntityId entityId = EntityId.newBuilder() + .setId(packedId) + .build(); + idFilterBuilder.addIds(entityId); + } } + final EntityIdFilter idFilter = idFilterBuilder.build(); + final EntityFilters filters = EntityFilters.newBuilder() + .setIdFilter(idFilter) + .build(); + + return Target.newBuilder() + .setIncludeAll(includeAll) + .setType(type.getTypeName()) + .setFilters(filters) + .build(); } - final EntityIdFilter idFilter = idFilterBuilder.build(); - final EntityFilters filters = EntityFilters.newBuilder() - .setIdFilter(idFilter) - .build(); - - return Target.newBuilder() - .setIncludeAll(includeAll) - .setType(type.getTypeName()) - .setFilters(filters) - .build(); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index bd845d13d38..e333c28be27 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -34,12 +34,14 @@ import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; +import org.spine3.base.Queries; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; import org.spine3.client.QueryResponse; +import org.spine3.client.Subscription; import org.spine3.client.Target; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; @@ -57,6 +59,7 @@ import javax.annotation.Nullable; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -67,6 +70,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Maps.newHashMap; +import static com.google.common.collect.Sets.newHashSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -91,7 +95,6 @@ public class StandShould { private static final int TOTAL_PROJECTS_FOR_BATCH_READING = 10; - @Test public void initialize_with_empty_builder() { final Stand.Builder builder = Stand.newBuilder(); @@ -158,17 +161,9 @@ public void use_provided_executor_upon_update_of_watched_type() { final StandTestProjectionRepository standTestProjectionRepo = new StandTestProjectionRepository(boundedContext); stand.registerTypeSupplier(standTestProjectionRepo); - final TypeUrl projectProjectionType = TypeUrl.of(Project.class); - final EntityFilters filters = EntityFilters.newBuilder() - .build(); - final Target projectProjectionTarget = Target.newBuilder() - .setIncludeAll(true) - .setType(projectProjectionType.getTypeName()) - .setFilters(filters) - .build(); - - - stand.subscribe(projectProjectionTarget, emptyUpdateCallback()); + final Target projectProjectionTarget = Queries.Targets.allOf(Project.class); + final Subscription subscription = stand.subscribe(projectProjectionTarget, emptyUpdateCallback()); + assertNotNull(subscription); verify(executor, never()).execute(any(Runnable.class)); @@ -193,9 +188,7 @@ public void operate_with_storage_provided_through_builder() { final int numericIdValue = 17; - final CustomerId customerId = CustomerId.newBuilder() - .setNumber(numericIdValue) - .build(); + final CustomerId customerId = customerIdFor(numericIdValue); final CustomerAggregate customerAggregate = customerAggregateRepo.create(customerId); final Customer customerState = customerAggregate.getState(); final Any packedState = AnyPacker.pack(customerState); @@ -214,14 +207,8 @@ public void operate_with_storage_provided_through_builder() { @Test public void return_empty_list_for_aggregate_read_all_on_empty_stand_storage() { - - final TypeUrl customerType = TypeUrl.of(Customer.class); - final Target customerTarget = Target.newBuilder() - .setIncludeAll(true) - .setType(customerType.getTypeName()) - .build(); - - checkEmptyResultForTargetOnEmptyStorage(customerTarget); + final Query readAllCustomers = Queries.readAll(Customer.class); + checkEmptyResultForTargetOnEmptyStorage(readAllCustomers); } @Test @@ -233,14 +220,8 @@ public void return_empty_list_to_unknown_type_reading() { checkTypesEmpty(stand); // Customer type was NOT registered. - final TypeUrl customerType = TypeUrl.of(Customer.class); - final Target customerTarget = Target.newBuilder() - .setIncludeAll(true) - .setType(customerType.getTypeName()) - .build(); - final Query readAllCustomers = Query.newBuilder() - .setTarget(customerTarget) - .build(); + // So create a query for an unknown type. + final Query readAllCustomers = Queries.readAll(Customer.class); final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); stand.execute(readAllCustomers, responseObserver); @@ -253,64 +234,19 @@ public void return_empty_list_to_unknown_type_reading() { @Test public void return_empty_list_for_aggregate_read_by_ids_on_empty_stand_storage() { - final TypeUrl customerType = TypeUrl.of(Customer.class); - final EntityId firstId = wrapCustomerId(1); - final EntityId anotherId = wrapCustomerId(2); - final EntityIdFilter idFilter = EntityIdFilter.newBuilder() - .addIds(firstId) - .addIds(anotherId) - .build(); - final EntityFilters filters = EntityFilters.newBuilder() - .setIdFilter(idFilter) - .build(); - final Target customerTarget = Target.newBuilder() - .setIncludeAll(false) - .setType(customerType.getTypeName()) - .setFilters(filters) - .build(); + final Query readCustomersById = Queries.readByIds(Customer.class, newHashSet( + customerIdFor(1), customerIdFor(2) + )); - checkEmptyResultForTargetOnEmptyStorage(customerTarget); + checkEmptyResultForTargetOnEmptyStorage(readCustomersById); } @Test public void return_empty_list_for_aggregate_reads_with_filters_not_set() { - final TypeUrl customerType = TypeUrl.of(Customer.class); - final Target customerTarget = Target.newBuilder() - .setIncludeAll(false) - .setType(customerType.getTypeName()) - .build(); - checkEmptyResultOnNonEmptyStorageForQueryTarget(customerTarget); - } - - @Test - public void return_empty_list_for_aggregate_reads_with_id_filter_not_set() { - - final TypeUrl customerType = TypeUrl.of(Customer.class); - final EntityFilters filters = EntityFilters.newBuilder() - .build(); - final Target customerTarget = Target.newBuilder() - .setIncludeAll(false) - .setType(customerType.getTypeName()) - .setFilters(filters) - .build(); - checkEmptyResultOnNonEmptyStorageForQueryTarget(customerTarget); - } - - @Test - public void return_empty_list_for_aggregate_reads_with_empty_list_of_ids() { - - final TypeUrl customerType = TypeUrl.of(Customer.class); - final EntityFilters filters = EntityFilters.newBuilder() - .setIdFilter(EntityIdFilter.getDefaultInstance()) - .build(); - final Target customerTarget = Target.newBuilder() - .setIncludeAll(false) - .setType(customerType.getTypeName()) - .setFilters(filters) - .build(); - checkEmptyResultOnNonEmptyStorageForQueryTarget(customerTarget); + final Target noneOfCustomers = Queries.Targets.someOf(Customer.class, Collections.emptySet()); + checkEmptyResultOnNonEmptyStorageForQueryTarget(noneOfCustomers); } @Test @@ -323,12 +259,12 @@ public void return_multiple_results_for_aggregate_state_batch_read_by_ids() { doCheckReadingCustomersById(TOTAL_CUSTOMERS_FOR_BATCH_READING); } - @Test public void return_single_result_for_projection_read_by_id() { doCheckReadingProjectsById(1); } + @Test public void return_multiple_results_for_projection_batch_read_by_ids() { doCheckReadingProjectsById(TOTAL_PROJECTS_FOR_BATCH_READING); @@ -337,18 +273,10 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { @Test public void trigger_callback_upon_change_of_watched_aggregate_state() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); - final TypeUrl customerType = TypeUrl.of(Customer.class); - - final EntityFilters filters = EntityFilters.newBuilder() - .build(); - final Target customerTarget = Target.newBuilder() - .setIncludeAll(true) - .setType(customerType.getTypeName()) - .setFilters(filters) - .build(); + final Target allCustomers = Queries.Targets.allOf(Customer.class); final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); - stand.subscribe(customerTarget, memoizeCallback); + stand.subscribe(allCustomers, memoizeCallback); assertNull(memoizeCallback.newEntityState); final Map.Entry sampleData = fillSampleCustomers(1).entrySet() @@ -362,7 +290,13 @@ public void trigger_callback_upon_change_of_watched_aggregate_state() { assertEquals(packedState, memoizeCallback.newEntityState); } - private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarget) { + private static CustomerId customerIdFor(int numericId) { + return CustomerId.newBuilder() + .setNumber(numericId) + .build(); + } + + private static void checkEmptyResultForTargetOnEmptyStorage(Query readCustomersQuery) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. final ImmutableList emptyResultList = ImmutableList.builder().build(); @@ -370,12 +304,8 @@ private static void checkEmptyResultForTargetOnEmptyStorage(Target customerTarge final Stand stand = prepareStandWithAggregateRepo(standStorageMock); - final Query readAllCustomers = Query.newBuilder() - .setTarget(customerTarget) - .build(); - final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); - stand.execute(readAllCustomers, responseObserver); + stand.execute(readCustomersQuery, responseObserver); final List messageList = checkAndGetMessageList(responseObserver); assertTrue("Query returned a non-empty response message list though the target was empty", messageList.isEmpty()); @@ -393,17 +323,7 @@ private static void doCheckReadingProjectsById(int numberOfProjects) { final Stand stand = prepareStandWithProjectionRepo(projectionRepository); - // Now we are ready to query. - final EntityIdFilter idFilter = idFilterForProjection(sampleProjects.keySet()); - - final Target projectTarget = Target.newBuilder() - .setFilters(EntityFilters.newBuilder() - .setIdFilter(idFilter)) - .setType(projectType.getTypeName()) - .build(); - final Query readMultipleProjects = Query.newBuilder() - .setTarget(projectTarget) - .build(); + final Query readMultipleProjects = Queries.readByIds(Project.class, sampleProjects.keySet()); final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); stand.execute(readMultipleProjects, responseObserver); @@ -562,16 +482,6 @@ public Given.StandTestProjection apply(@Nullable ProjectId input) { return result; } - private static EntityId wrapCustomerId(int number) { - final CustomerId customerId = CustomerId.newBuilder() - .setNumber(number) - .build(); - final Any packedId = AnyPacker.pack(customerId); - return EntityId.newBuilder() - .setId(packedId) - .build(); - } - private static ArgumentMatcher> projectionIdsIterableMatcher(final Set projectIds) { return new ArgumentMatcher>() { @Override @@ -634,9 +544,7 @@ private static Map fillSampleCustomers(int numberOfCustome @SuppressWarnings("UnsecureRandomNumberGeneration") final Random randomizer = new Random(); - final CustomerId customerId = CustomerId.newBuilder() - .setNumber(randomizer.nextInt()) - .build(); + final CustomerId customerId = customerIdFor(randomizer.nextInt()); sampleCustomers.put(customerId, customer); } return sampleCustomers; From 327a436b1187e1a510ac727c6ea2d85c6cf07fdf Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 12:01:10 +0300 Subject: [PATCH 166/361] Add simplified method "applyIfEffective" for applying field mask. --- .../main/java/org/spine3/server/entity/FieldMasks.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 5ef9006a93b..d3c0b14f787 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -125,6 +125,14 @@ public static M applyMask(@Suppr } + public static M applyIfEffective(@SuppressWarnings("TypeMayBeWeakened") @Nullable FieldMask mask, M entity, TypeUrl typeUrl) { + if (isEffective(mask)) { + return applyMask(mask, entity, typeUrl); + } + + return entity; + } + @Nullable private static Class getBuilderForType(TypeUrl typeUrl) { Class builderClass; From c3f8468a0d872a13827b0a5b83b91911a1647ebd Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 12:02:14 +0300 Subject: [PATCH 167/361] Add suport for field masks applying in entity repo. --- .../server/entity/EntityRepository.java | 24 ++++++++++++------- .../org/spine3/server/stand/StandShould.java | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 0035f0f95e7..fe49a4b2fba 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -43,6 +43,7 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -147,23 +148,22 @@ public E find(I id) { * @return all the entities in this repository with the IDs matching the given {@code Iterable} */ @CheckReturnValue - public ImmutableCollection findBulk(Iterable ids) { + public ImmutableCollection findBulk(Iterable ids, @Nullable FieldMask fieldMask) { final RecordStorage storage = recordStorage(); final Iterable entityStorageRecords = storage.readBulk(ids); final Iterator idIterator = ids.iterator(); final Iterator recordIterator = entityStorageRecords.iterator(); - final ImmutableList.Builder builder = ImmutableList.builder(); + final List entities = new ArrayList<>(); while (idIterator.hasNext() && recordIterator.hasNext()) { final I id = idIterator.next(); final EntityStorageRecord record = recordIterator.next(); - final E entity = toEntity(id, record); - builder.add(entity); + final E entity = toEntity(id, record, fieldMask); + entities.add(entity); } - final ImmutableList result = builder.build(); - return result; + return ImmutableList.copyOf(entities); } @CheckReturnValue @@ -200,7 +200,6 @@ public E apply(@Nullable Map.Entry input) { */ @CheckReturnValue public ImmutableCollection findAll(EntityFilters filters, @Nullable FieldMask fieldMask) { - // TODO:19-09-16:dmytro.dashenkov: Add support for field mask processing. final List idsList = filters.getIdFilter() .getIdsList(); final Class expectedIdClass = getIdClass(); @@ -229,17 +228,24 @@ public I apply(@Nullable EntityId input) { } }); - final ImmutableCollection result = findBulk(domainIds); + final ImmutableCollection result = findBulk(domainIds, fieldMask); return result; } private E toEntity(I id, EntityStorageRecord record) { + return toEntity(id, record, null); + } + + private E toEntity(I id, EntityStorageRecord record, @Nullable FieldMask fieldMask) { final E entity = create(id); - final M state = unpack(record.getState()); + @SuppressWarnings("unchecked") + final M state = (M) FieldMasks.applyIfEffective(fieldMask, unpack(record.getState()), getEntityStateType()); entity.setState(state, record.getVersion(), record.getWhenModified()); return entity; } + + private EntityStorageRecord toEntityRecord(E entity) { final M state = entity.getState(); final Any stateAny = AnyPacker.pack(state); diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 03bd0b6229e..3d8fd5a4fa5 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -762,7 +762,7 @@ private static void setupExpectedFindAllBehaviour(Map sample } final Iterable matchingIds = argThat(projectionIdsIterableMatcher(projectIds)); - when(projectionRepository.findBulk(matchingIds)).thenReturn(allResults); + when(projectionRepository.findBulk(matchingIds, null)).thenReturn(allResults); when(projectionRepository.findAll()).thenReturn(allResults); From 1f2270a2000d4c3a4482daece3a5cef37c792b1f Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 14:30:25 +0300 Subject: [PATCH 168/361] Add support for by-FieldMask processing in stand storage. --- .../java/org/spine3/server/stand/Stand.java | 4 +- .../spine3/server/storage/RecordStorage.java | 32 +++++++--- .../memory/InMemoryProjectionStorage.java | 15 ++++- .../storage/memory/InMemoryRecordStorage.java | 60 ++++++++++++++++++- .../storage/memory/InMemoryStandStorage.java | 15 ++++- .../org/spine3/server/stand/StandShould.java | 15 +++-- 6 files changed, 119 insertions(+), 22 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index cd18449c615..f60005f3ca2 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -268,13 +268,13 @@ private ImmutableCollection internalExecute(Query query) { if (repository != null) { // the target references an entity state - ImmutableCollection entities = fetchFromEntityRepository(query, repository); + final ImmutableCollection entities = fetchFromEntityRepository(query, repository); feedEntitiesToBuilder(resultBuilder, entities); } else if (knownAggregateTypes.contains(typeUrl)) { // the target relates to an {@code Aggregate} state - ImmutableCollection stateRecords = fetchFromStandStorage(query, typeUrl); + final ImmutableCollection stateRecords = fetchFromStandStorage(query, typeUrl); feedStateRecordsToBuilder(resultBuilder, stateRecords); } diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index 81f15a9a0fb..09c18eb55cb 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -20,9 +20,13 @@ package org.spine3.server.storage; +import com.google.protobuf.Any; import com.google.protobuf.FieldMask; import org.spine3.SPI; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; import org.spine3.server.entity.Entity; +import org.spine3.server.entity.FieldMasks; import javax.annotation.Nullable; import java.util.Map; @@ -47,11 +51,6 @@ protected RecordStorage(boolean multitenant) { @Override public EntityStorageRecord read(I id) { - return read(id, null); - } - - public EntityStorageRecord read(I id, @Nullable FieldMask fieldMask) { - // TODO:19-09-16:dmytro.dashenkov: Add support for field mask processing. checkNotClosed(); checkNotNull(id); @@ -62,6 +61,13 @@ public EntityStorageRecord read(I id, @Nullable FieldMask fieldMask) { return record; } + // TODO:20-09-16:dmytro.dashenkov: Add javadoc. + public EntityStorageRecord read(I id, FieldMask fieldMask) { + final EntityStorageRecord.Builder builder = EntityStorageRecord.newBuilder(read(id)); + final Any state = builder.getState(); + builder.setState(AnyPacker.pack(FieldMasks.applyIfEffective(fieldMask, AnyPacker.unpack(state), TypeUrl.of(state.getTypeUrl())))); + return builder.build(); + } @Override @@ -73,7 +79,6 @@ public void write(I id, EntityStorageRecord record) { writeInternal(id, record); } - @Override public Iterable readBulk(Iterable ids) { checkNotClosed(); @@ -83,11 +88,10 @@ public Iterable readBulk(Iterable ids) { } public Iterable readBulk(Iterable ids, FieldMask fieldMask) { - // TODO:19-09-16:dmytro.dashenkov: Support field mask processing. checkNotClosed(); checkNotNull(ids); - return readBulkInternal(ids); + return readBulkInternal(ids, fieldMask); } @Override @@ -97,6 +101,12 @@ public Map readAll() { return readAllInternal(); } + public Map readAll(FieldMask fieldMask) { + checkNotClosed(); + + return readAllInternal(fieldMask); + } + // // Internal storage methods @@ -114,10 +124,16 @@ public Map readAll() { /** @see BulkStorageOperationsMixin#readBulk(java.lang.Iterable) */ protected abstract Iterable readBulkInternal(Iterable ids); + /** @see BulkStorageOperationsMixin#readBulk(java.lang.Iterable) */ + protected abstract Iterable readBulkInternal(Iterable ids, FieldMask fieldMask); + /** @see BulkStorageOperationsMixin#readAll() */ protected abstract Map readAllInternal(); + /** @see BulkStorageOperationsMixin#readAll() */ + protected abstract Map readAllInternal(FieldMask fieldMask); + /** * Writes a record into the storage. * diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java index 598c4d9fd1d..99b292f6c08 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java @@ -20,10 +20,11 @@ package org.spine3.server.storage.memory; +import com.google.protobuf.FieldMask; import com.google.protobuf.Timestamp; -import org.spine3.server.storage.RecordStorage; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.ProjectionStorage; +import org.spine3.server.storage.RecordStorage; import java.util.Map; @@ -79,9 +80,21 @@ protected Iterable readBulkInternal(Iterable ids) { return result; } + @Override + protected Iterable readBulkInternal(Iterable ids, FieldMask fieldMask) { + return recordStorage.readBulk(ids, fieldMask); + } + + @Override protected Map readAllInternal() { final Map result = recordStorage.readAll(); return result; } + + @Override + protected Map readAllInternal(FieldMask fieldMask) { + final Map result = recordStorage.readAll(fieldMask); + return result; + } } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 7780bf322c5..e8722eb9221 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -20,13 +20,23 @@ package org.spine3.server.storage.memory; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.entity.FieldMasks; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.RecordStorage; import org.spine3.server.users.CurrentTenant; import org.spine3.users.TenantId; +import javax.annotation.Nullable; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; import java.util.Map; @@ -54,7 +64,7 @@ protected InMemoryRecordStorage(boolean multitenant) { @SuppressWarnings("MethodWithMultipleLoops") /* It's OK for in-memory implementation * as it is used primarily in tests. */ @Override - protected Iterable readBulkInternal(final Iterable givenIds) { + protected Iterable readBulkInternal(final Iterable givenIds, @Nullable FieldMask fieldMask) { final Map storage = getStorage(); // It is not possible to return an immutable collection, since {@code null} may be present in it. @@ -63,8 +73,17 @@ protected Iterable readBulkInternal(final Iterable given for (I recordId : storage.keySet()) { for (I givenId : givenIds) { if (recordId.equals(givenId)) { - final EntityStorageRecord matchingRecord = storage.get(recordId); - result.add(matchingRecord); + final EntityStorageRecord.Builder matchingRecord = storage.get(recordId).toBuilder(); + final Any state = matchingRecord.getState(); + + matchingRecord.setState( + AnyPacker.pack( + FieldMasks.applyIfEffective( + fieldMask, + AnyPacker.unpack(state), + TypeUrl.of(state.getTypeUrl())))); + + result.add(matchingRecord.build()); continue; } result.add(null); @@ -73,6 +92,11 @@ protected Iterable readBulkInternal(final Iterable given return result; } + @Override + protected Iterable readBulkInternal(Iterable ids) { + return readBulkInternal(ids, null); + } + @Override protected Map readAllInternal() { final Map storage = getStorage(); @@ -81,6 +105,36 @@ protected Map readAllInternal() { return result; } + @Override + protected Map readAllInternal(FieldMask fieldMask) { + // TODO:20-09-16:dmytro.dashenkov: Increase efficiency. + if (!FieldMasks.isEffective(fieldMask)) { + return readAllInternal(); + } + + final Map storage = getStorage(); + + if (storage.isEmpty()) { + return newHashMap(); + } + + final Map result = newHashMap(); + + final Collection records = FieldMasks.applyMask(fieldMask, Collections2.transform(storage.values(), new Function() { + @Nullable + @Override + public Message apply(@Nullable EntityStorageRecord input) { + return input == null ? null : AnyPacker.unpack(input.getState()); + } + }), TypeUrl.of(storage.entrySet().iterator().next().getValue().getState().getTypeUrl())); + + final Iterator messageIterator = records.iterator(); + for (I key : storage.keySet()) { + result.put(key, storage.get(key).toBuilder().setState(AnyPacker.pack(messageIterator.next())).build()); + } + + return ImmutableMap.copyOf(result); + } protected static InMemoryRecordStorage newInstance(boolean multitenant) { return new InMemoryRecordStorage<>(multitenant); diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index e8452119856..5fa77e0af7b 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -65,8 +65,7 @@ public ImmutableCollection readAllByType(final TypeUrl type @Override public ImmutableCollection readAllByType(final TypeUrl type, @Nullable FieldMask fieldMask) { - // TODO:19-09-16:dmytro.dashenkov: Add support for field mask processing. - final Map allRecords = readAll(); + final Map allRecords = readAll(fieldMask); final Map resultMap = Maps.filterKeys(allRecords, new Predicate() { @Override public boolean apply(@Nullable AggregateStateId stateId) { @@ -94,12 +93,24 @@ protected Iterable readBulkInternal(Iterable readBulkInternal(Iterable ids, FieldMask fieldMask) { + final Iterable result = recordStorage.readBulk(ids, fieldMask); + return result; + } + @Override protected Map readAllInternal() { final Map result = recordStorage.readAll(); return result; } + @Override + protected Map readAllInternal(FieldMask fieldMask) { + final Map result = recordStorage.readAll(fieldMask); + return result; + } + @Override protected void writeInternal(AggregateStateId id, EntityStorageRecord record) { final TypeUrl recordType = TypeUrl.of(record.getState() diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 3d8fd5a4fa5..eb7f224e03f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -84,6 +84,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.spine3.server.stand.Given.StandTestProjection; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; /** @@ -755,19 +756,21 @@ private static void setupExpectedFindAllBehaviour(Map sample StandTestProjectionRepository projectionRepository) { final Set projectIds = sampleProjects.keySet(); - final ImmutableCollection allResults = toProjectionCollection(projectIds); + final ImmutableCollection allResults = toProjectionCollection(projectIds); for (ProjectId projectId : projectIds) { - when(projectionRepository.find(eq(projectId))).thenReturn(new Given.StandTestProjection(projectId)); + when(projectionRepository.find(eq(projectId))).thenReturn(new StandTestProjection(projectId)); } final Iterable matchingIds = argThat(projectionIdsIterableMatcher(projectIds)); - when(projectionRepository.findBulk(matchingIds, null)).thenReturn(allResults); + + when(projectionRepository.findBulk(matchingIds, any(FieldMask.class))).thenReturn(allResults); when(projectionRepository.findAll()).thenReturn(allResults); final EntityFilters matchingFilter = argThat(entityFilterMatcher(projectIds)); - when(projectionRepository.findAll(matchingFilter, null)).thenReturn(allResults); + //noinspection deprecation + when(projectionRepository.findAll(matchingFilter, any(FieldMask.class))).thenReturn(allResults); } @SuppressWarnings("OverlyComplexAnonymousInnerClass") @@ -798,9 +801,9 @@ private static ImmutableCollection toProjectionCollec final Collection transformed = Collections2.transform(values, new Function() { @Nullable @Override - public Given.StandTestProjection apply(@Nullable ProjectId input) { + public StandTestProjection apply(@Nullable ProjectId input) { checkNotNull(input); - return new Given.StandTestProjection(input); + return new StandTestProjection(input); } }); final ImmutableList result = ImmutableList.copyOf(transformed); From 5531914e9a8dfcfccd58a8dbba44038d55ef5547 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 20 Sep 2016 14:58:14 +0300 Subject: [PATCH 169/361] Rename test method to reflect its nature. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index e333c28be27..f9175bc7455 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -271,7 +271,7 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { } @Test - public void trigger_callback_upon_change_of_watched_aggregate_state() { + public void trigger_callback_upon_subscription_update_of_aggregate() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final Target allCustomers = Queries.Targets.allOf(Customer.class); From f2e79b4d881f9a05bd28256b7c25cc6a4cd3fd83 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 15:06:47 +0300 Subject: [PATCH 170/361] Add required javadoc. --- .../server/entity/EntityRepository.java | 2 +- .../org/spine3/server/entity/FieldMasks.java | 54 +++++++++++++++---- .../java/org/spine3/server/stand/Stand.java | 4 +- .../spine3/server/storage/RecordStorage.java | 11 +++- .../storage/memory/InMemoryRecordStorage.java | 4 +- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index fe49a4b2fba..f2cb3046ea9 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -239,7 +239,7 @@ private E toEntity(I id, EntityStorageRecord record) { private E toEntity(I id, EntityStorageRecord record, @Nullable FieldMask fieldMask) { final E entity = create(id); @SuppressWarnings("unchecked") - final M state = (M) FieldMasks.applyIfEffective(fieldMask, unpack(record.getState()), getEntityStateType()); + final M state = (M) FieldMasks.applyIfValid(fieldMask, unpack(record.getState()), getEntityStateType()); entity.setState(state, record.getVersion(), record.getWhenModified()); return entity; } diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index d3c0b14f787..a162ab50577 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -36,8 +36,9 @@ import java.util.List; /** + *

Util class for filtering fields that are included in an instance of {@link Message} or collection of {@link Message}s. + * Provides basic functionality for processing of e.g. a {@code org.spine3.client.Query} to in-memory storage.

* - * // TODO:19-09-16:dmytro.dashenkov: Add javadoc. * @author Dmytro Dashenkov */ @SuppressWarnings("UtilityClass") @@ -47,12 +48,16 @@ private FieldMasks() { } /** - * // TODO:19-09-16:dmytro.dashenkov: Add javadoc. - * @param mask - * @param entities - * @param typeUrl - * @param - * @return + *

Applies given {@code FieldMask} to givem collection of {@link Message}s. + * Does not change the {@link Collection} itself.

+ * + *

The {@code FieldMask} must be valid for this operation.

+ * + * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. + * @param entities {@link Message}s to filter. + * @param typeUrl Type of the {@link Message}s. + * @return Non-null unmodifiable {@link Collection} of {@link Message}s of the same type that the input had. + * @see #isValid(FieldMask) */ @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { @@ -89,11 +94,28 @@ public static Collection appl return Collections.unmodifiableList(filtered); } - public static boolean isEffective(@Nullable FieldMask fieldMask) { + /** + *

Checks whether the given {@code FieldMask} is valid of not. + * This also includes a null check.

+ * + * @param fieldMask Nullable {@code FieldMask} to check. + * @return {@code true} if the {@code FieldMask} is valid for use, {@code} false otherwise. + */ + public static boolean isValid(@Nullable FieldMask fieldMask) { return fieldMask != null && !fieldMask.getPathsList().isEmpty(); } - // TODO:19-09-16:dmytro.dashenkov: Add javadoc. + /** + *

Applies given {@code FieldMask} to a single {@link Message}.

+ * + *

The {@code FieldMask} must be valid for this operation.

+ * + * @param mask {@code FieldMask} instance to apply. + * @param entity The {@link Message} to apply given {@code FieldMask} to. + * @param typeUrl Type of the {@link Message}. + * @return A {@link Message} of the same type as the given one with only selected fields. + * @see #isValid(FieldMask) + */ @SuppressWarnings("unchecked") public static M applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M entity, TypeUrl typeUrl) { final ProtocolStringList filter = mask.getPathsList(); @@ -125,8 +147,18 @@ public static M applyMask(@Suppr } - public static M applyIfEffective(@SuppressWarnings("TypeMayBeWeakened") @Nullable FieldMask mask, M entity, TypeUrl typeUrl) { - if (isEffective(mask)) { + /** + *

Applies {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid.

+ * + * @param mask The {@code FieldMask} to apply. + * @param entity The {@link Message} to apply given mask to. + * @param typeUrl Type of given {@link Message}. + * @return

A {@link Message} of the same type as the given one with only selected fields + * if the {@code mask} is valid, {@code entity} itself otherwise.

+ * @see #isValid(FieldMask) + */ + public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") @Nullable FieldMask mask, M entity, TypeUrl typeUrl) { + if (isValid(mask)) { return applyMask(mask, entity, typeUrl); } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index f60005f3ca2..2f30a3d8207 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -291,7 +291,7 @@ private ImmutableCollection fetchFromStandStorage(Query que ImmutableCollection result; final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); - final boolean shouldApplyFieldMask = FieldMasks.isEffective(fieldMask); + final boolean shouldApplyFieldMask = FieldMasks.isValid(fieldMask); if (target.getIncludeAll()) { result = shouldApplyFieldMask ? @@ -365,7 +365,7 @@ private static ImmutableCollection fetchFromEntityRepository(Q final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); - if (target.getIncludeAll() && !FieldMasks.isEffective(fieldMask)) { + if (target.getIncludeAll() && !FieldMasks.isValid(fieldMask)) { result = repository.findAll(); } else { final EntityFilters filters = target.getFilters(); diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index 09c18eb55cb..aeed50be83d 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -61,11 +61,18 @@ public EntityStorageRecord read(I id) { return record; } - // TODO:20-09-16:dmytro.dashenkov: Add javadoc. + /** + * Read one specific item form storage. + * + * @param id id of the item to read. + * @param fieldMask Fields to read. Can be {@code null}, but it's more convenient to call {@link #read(Object)} + * in order to get all the fields. + * @return Non-null {@code EntityStorageRecord} instance. + */ public EntityStorageRecord read(I id, FieldMask fieldMask) { final EntityStorageRecord.Builder builder = EntityStorageRecord.newBuilder(read(id)); final Any state = builder.getState(); - builder.setState(AnyPacker.pack(FieldMasks.applyIfEffective(fieldMask, AnyPacker.unpack(state), TypeUrl.of(state.getTypeUrl())))); + builder.setState(AnyPacker.pack(FieldMasks.applyIfValid(fieldMask, AnyPacker.unpack(state), TypeUrl.of(state.getTypeUrl())))); return builder.build(); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index e8722eb9221..6d1c0ffb191 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -78,7 +78,7 @@ protected Iterable readBulkInternal(final Iterable given matchingRecord.setState( AnyPacker.pack( - FieldMasks.applyIfEffective( + FieldMasks.applyIfValid( fieldMask, AnyPacker.unpack(state), TypeUrl.of(state.getTypeUrl())))); @@ -108,7 +108,7 @@ protected Map readAllInternal() { @Override protected Map readAllInternal(FieldMask fieldMask) { // TODO:20-09-16:dmytro.dashenkov: Increase efficiency. - if (!FieldMasks.isEffective(fieldMask)) { + if (!FieldMasks.isValid(fieldMask)) { return readAllInternal(); } From 8d06b0b9ae39e389c5f008aee1084abdb3f9e067 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 15:15:31 +0300 Subject: [PATCH 171/361] Optimize "readAll" operation in InMemoryRecordStorage for case with field mask present. --- .../spine3/server/storage/memory/InMemoryRecordStorage.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 6d1c0ffb191..8394842abba 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -107,7 +107,6 @@ protected Map readAllInternal() { @Override protected Map readAllInternal(FieldMask fieldMask) { - // TODO:20-09-16:dmytro.dashenkov: Increase efficiency. if (!FieldMasks.isValid(fieldMask)) { return readAllInternal(); } @@ -118,7 +117,7 @@ protected Map readAllInternal(FieldMask fieldMask) { return newHashMap(); } - final Map result = newHashMap(); + final ImmutableMap.Builder result = ImmutableMap.builder(); final Collection records = FieldMasks.applyMask(fieldMask, Collections2.transform(storage.values(), new Function() { @Nullable @@ -133,7 +132,7 @@ public Message apply(@Nullable EntityStorageRecord input) { result.put(key, storage.get(key).toBuilder().setState(AnyPacker.pack(messageIterator.next())).build()); } - return ImmutableMap.copyOf(result); + return result.build(); } protected static InMemoryRecordStorage newInstance(boolean multitenant) { From bfb9e96e375a6a040ef95fda359cf3d66fec6527 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 15:38:40 +0300 Subject: [PATCH 172/361] Merge. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 3457481dda9..6fd1de8392f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -303,7 +303,6 @@ private static CustomerId customerIdFor(int numericId) { .build(); } - private static void checkEmptyResultForTargetOnEmptyStorage(Query readCustomersQuery) { @Test public void retrieve_all_data_if_field_mask_is_not_set() { final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); @@ -543,7 +542,7 @@ private static void requestSampleCustomer(int[] fieldIndexes, StreamObserver emptyResultList = ImmutableList.builder().build(); From 9b1d55b2b2213e60097bba35871473f51e761af1 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 20 Sep 2016 15:45:14 +0300 Subject: [PATCH 173/361] * Add more subscription-related tests. * Update Targets#someOf() method signature to allow IDs extending Message. --- .../main/java/org/spine3/base/Queries.java | 2 +- .../org/spine3/server/stand/StandShould.java | 186 +++++++++++++++++- 2 files changed, 181 insertions(+), 7 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index dbf8d89031b..d24e7111d17 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -78,7 +78,7 @@ public static class Targets { private Targets() { } - public static Target someOf(Class entityClass, Set ids) { + public static Target someOf(Class entityClass, Set ids) { final Target result = composeTarget(entityClass, ids); return result; } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index f9175bc7455..5bb534e7afa 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -43,6 +43,7 @@ import org.spine3.client.QueryResponse; import org.spine3.client.Subscription; import org.spine3.client.Target; +import org.spine3.people.PersonName; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.server.BoundedContext; @@ -80,6 +81,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -271,12 +273,13 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { } @Test - public void trigger_callback_upon_subscription_update_of_aggregate() { + public void trigger_subscription_callback_upon_update_of_aggregate() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final Target allCustomers = Queries.Targets.allOf(Customer.class); final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); - stand.subscribe(allCustomers, memoizeCallback); + final Subscription subscription = stand.subscribe(allCustomers, memoizeCallback); + assertNotNull(subscription); assertNull(memoizeCallback.newEntityState); final Map.Entry sampleData = fillSampleCustomers(1).entrySet() @@ -290,12 +293,157 @@ public void trigger_callback_upon_subscription_update_of_aggregate() { assertEquals(packedState, memoizeCallback.newEntityState); } + + @Test + public void trigger_subscription_callback_upon_update_of_projection() { + final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); + final Target allProjects = Queries.Targets.allOf(Project.class); + + final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); + final Subscription subscription = stand.subscribe(allProjects, memoizeCallback); + assertNotNull(subscription); + assertNull(memoizeCallback.newEntityState); + + final Map.Entry sampleData = fillSampleProjects(1).entrySet() + .iterator() + .next(); + final ProjectId projectId = sampleData.getKey(); + final Project project = sampleData.getValue(); + final Any packedState = AnyPacker.pack(project); + stand.update(projectId, packedState); + + assertEquals(packedState, memoizeCallback.newEntityState); + } + + + @Test + public void allow_cancelling_subscriptions() { + final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); + final Target allCustomers = Queries.Targets.allOf(Customer.class); + + final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); + final Subscription subscription = stand.subscribe(allCustomers, memoizeCallback); + assertNull(memoizeCallback.newEntityState); + + stand.cancel(subscription); + + final Map.Entry sampleData = fillSampleCustomers(1).entrySet() + .iterator() + .next(); + final CustomerId customerId = sampleData.getKey(); + final Customer customer = sampleData.getValue(); + final Any packedState = AnyPacker.pack(customer); + stand.update(customerId, packedState); + + assertNull(memoizeCallback.newEntityState); + } + + @Test + public void do_not_fail_if_cancelling_inexistent_subscription() { + final Stand stand = Stand.newBuilder() + .build(); + final Subscription inexistentSubscription = Subscription.newBuilder() + .setId(UUID.randomUUID() + .toString()) + .build(); + stand.cancel(inexistentSubscription); + } + + + @SuppressWarnings("MethodWithMultipleLoops") + @Test + public void trigger_each_subscription_callback_once_for_multiple_subscriptions() { + final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); + final Target allCustomers = Queries.Targets.allOf(Customer.class); + + final Set callbacks = newHashSet(); + final int totalCallbacks = 100; + + for (int callbackIndex = 0; callbackIndex < totalCallbacks; callbackIndex++) { + final MemoizeStandUpdateCallback callback = subscribeWithCallback(stand, allCustomers); + callbacks.add(callback); + } + + + final Map.Entry sampleData = fillSampleCustomers(1).entrySet() + .iterator() + .next(); + final CustomerId customerId = sampleData.getKey(); + final Customer customer = sampleData.getValue(); + final Any packedState = AnyPacker.pack(customer); + stand.update(customerId, packedState); + + + for (MemoizeStandUpdateCallback callback : callbacks) { + assertEquals(packedState, callback.newEntityState); + verify(callback, times(1)).onEntityStateUpdate(any(Any.class)); + } + } + + @Test + public void do_not_trigger_subscription_callbacks_in_case_of_another_type_criterion_mismatch() { + final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); + final Target allProjects = Queries.Targets.allOf(Project.class); + final MemoizeStandUpdateCallback callback = subscribeWithCallback(stand, allProjects); + + final Map.Entry sampleData = fillSampleCustomers(1).entrySet() + .iterator() + .next(); + final CustomerId customerId = sampleData.getKey(); + final Customer customer = sampleData.getValue(); + final Any packedState = AnyPacker.pack(customer); + stand.update(customerId, packedState); + + verify(callback, never()).onEntityStateUpdate(any(Any.class)); + } + + @Test + public void trigger_subscription_callbacks_matching_by_id() { + final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); + + final Map sampleCustomers = fillSampleCustomers(10); + + final Target someCustomers = Queries.Targets.someOf(Customer.class, sampleCustomers.keySet()); + final Set callbackStates = newHashSet(); + final MemoizeStandUpdateCallback callback = new MemoizeStandUpdateCallback() { + @Override + public void onEntityStateUpdate(Any newEntityState) { + super.onEntityStateUpdate(newEntityState); + final Customer customerInCallback = AnyPacker.unpack(newEntityState); + callbackStates.add(customerInCallback); + } + }; + stand.subscribe(someCustomers, callback); + + for (Map.Entry sampleEntry : sampleCustomers.entrySet()) { + final CustomerId customerId = sampleEntry.getKey(); + final Customer customer = sampleEntry.getValue(); + final Any packedState = AnyPacker.pack(customer); + stand.update(customerId, packedState); + } + + assertEquals(newHashSet(sampleCustomers.values()), callbackStates); + } + + private static MemoizeStandUpdateCallback subscribeWithCallback(Stand stand, Target subscriptionTarget) { + final MemoizeStandUpdateCallback callback = spy(new MemoizeStandUpdateCallback()); + stand.subscribe(subscriptionTarget, callback); + assertNull(callback.newEntityState); + return callback; + } + private static CustomerId customerIdFor(int numericId) { return CustomerId.newBuilder() .setNumber(numericId) .build(); } + private static ProjectId projectIdFor(int numericId) { + return ProjectId.newBuilder() + .setId(String.valueOf(numericId)) + .build(); + } + private static void checkEmptyResultForTargetOnEmptyStorage(Query readCustomersQuery) { final StandStorage standStorageMock = mock(StandStorage.class); // Return an empty collection on {@link StandStorage#readAllByType(TypeUrl)} call. @@ -539,17 +687,43 @@ private static void triggerMultipleUpdates(Map sampleCusto private static Map fillSampleCustomers(int numberOfCustomers) { final Map sampleCustomers = newHashMap(); + + @SuppressWarnings("UnsecureRandomNumberGeneration") + final Random randomizer = new Random(Integer.MAX_VALUE); // force non-negative numeric ID values. + for (int customerIndex = 0; customerIndex < numberOfCustomers; customerIndex++) { - final Customer customer = Customer.getDefaultInstance(); - @SuppressWarnings("UnsecureRandomNumberGeneration") - final Random randomizer = new Random(); - final CustomerId customerId = customerIdFor(randomizer.nextInt()); + + final int numericId = randomizer.nextInt(); + final CustomerId customerId = customerIdFor(numericId); + final Customer customer = Customer.newBuilder() + .setName(PersonName.newBuilder() + .setGivenName(String.valueOf(numericId))) + .build(); sampleCustomers.put(customerId, customer); } return sampleCustomers; } + private static Map fillSampleProjects(int numberOfProjects) { + final Map sampleProjects = newHashMap(); + + @SuppressWarnings("UnsecureRandomNumberGeneration") + final Random randomizer = new Random(Integer.MAX_VALUE); // force non-negative numeric ID values. + + for (int projectIndex = 0; projectIndex < numberOfProjects; projectIndex++) { + + final int numericId = randomizer.nextInt(); + final ProjectId customerId = projectIdFor(numericId); + + final Project project = Project.newBuilder() + .setName(String.valueOf(numericId)) + .build(); + sampleProjects.put(customerId, project); + } + return sampleProjects; + } + private static void fillSampleProjects(Map sampleProjects, int numberOfProjects) { for (int projectIndex = 0; projectIndex < numberOfProjects; projectIndex++) { final Project project = Project.getDefaultInstance(); From 604265b3eea0f77fa162c15ee4c35d1c781b2863 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 20 Sep 2016 16:10:41 +0300 Subject: [PATCH 174/361] * Add more subscription-related tests. * Update Targets#someOf() method signature to allow IDs extending Message. --- .../java/org/spine3/server/stand/StandShould.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 5bb534e7afa..0d90b4801a2 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -497,17 +497,7 @@ private static void doCheckReadingCustomersById(int numberOfCustomers) { final Stand stand = prepareStandWithAggregateRepo(standStorageMock); triggerMultipleUpdates(sampleCustomers, stand); - // Now we are ready to query. - final EntityIdFilter idFilter = idFilterForAggregate(sampleCustomers.keySet()); - - final Target customerTarget = Target.newBuilder() - .setFilters(EntityFilters.newBuilder() - .setIdFilter(idFilter)) - .setType(customerType.getTypeName()) - .build(); - final Query readMultipleCustomers = Query.newBuilder() - .setTarget(customerTarget) - .build(); + final Query readMultipleCustomers = Queries.readByIds(Customer.class, sampleCustomers.keySet()); final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); stand.execute(readMultipleCustomers, responseObserver); @@ -554,7 +544,7 @@ private static void setupExpectedBulkReadBehaviour(Map sam final ImmutableList.Builder recordsBuilder = ImmutableList.builder(); for (CustomerId customerId : sampleCustomers.keySet()) { final AggregateStateId stateId = AggregateStateId.of(customerId, customerType); - final Customer customer = Customer.getDefaultInstance(); + final Customer customer = sampleCustomers.get(customerId); final Any customerState = AnyPacker.pack(customer); final EntityStorageRecord entityStorageRecord = EntityStorageRecord.newBuilder() .setState(customerState) From 65b04b06d558d5b2d2be165627cb5cc9c0e45038 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 16:49:03 +0300 Subject: [PATCH 175/361] Resolve some of comments, naming and API issues. --- .../server/entity/EntityRepository.java | 24 +++++++++++++++--- .../org/spine3/server/entity/FieldMasks.java | 25 ++++++++++--------- .../org/spine3/server/stand/StandShould.java | 18 ++++++------- .../clientservice/customer/customer.proto | 4 +-- 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index f2cb3046ea9..183da7b7402 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -126,6 +126,20 @@ public E find(I id) { } + /** + * Finds all the entities in this repository with IDs, contained within the passed {@code Iterable}. + * + *

Same as calling

{@code findBulk(id, FieldMask.getDefaultInstance());}
+ * + * @param ids entity IDs to search for + * @return all the entities in this repository with the IDs matching the given {@code Iterable} + * @see #findBulk(Iterable, FieldMask) + */ + @CheckReturnValue + public ImmutableCollection findBulk(Iterable ids) { + return findBulk(ids, FieldMask.getDefaultInstance()); + } + /** * Finds all the entities in this repository with IDs, contained within the passed {@code Iterable}. * @@ -145,10 +159,11 @@ public E find(I id) { *

NOTE: The storage must be assigned before calling this method. * * @param ids entity IDs to search for + * @param fieldMask mask to apply on entities * @return all the entities in this repository with the IDs matching the given {@code Iterable} */ @CheckReturnValue - public ImmutableCollection findBulk(Iterable ids, @Nullable FieldMask fieldMask) { + public ImmutableCollection findBulk(Iterable ids, FieldMask fieldMask) { final RecordStorage storage = recordStorage(); final Iterable entityStorageRecords = storage.readBulk(ids); @@ -196,10 +211,11 @@ public E apply(@Nullable Map.Entry input) { *

NOTE: The storage must be assigned before calling this method. * * @param filters entity filters + * @param fieldMask mask to apply to the entities * @return all the entities in this repository passed the filters. */ @CheckReturnValue - public ImmutableCollection findAll(EntityFilters filters, @Nullable FieldMask fieldMask) { + public ImmutableCollection findAll(EntityFilters filters, FieldMask fieldMask) { final List idsList = filters.getIdFilter() .getIdsList(); final Class expectedIdClass = getIdClass(); @@ -233,10 +249,10 @@ public I apply(@Nullable EntityId input) { } private E toEntity(I id, EntityStorageRecord record) { - return toEntity(id, record, null); + return toEntity(id, record, FieldMask.getDefaultInstance()); } - private E toEntity(I id, EntityStorageRecord record, @Nullable FieldMask fieldMask) { + private E toEntity(I id, EntityStorageRecord record, FieldMask fieldMask) { final E entity = create(id); @SuppressWarnings("unchecked") final M state = (M) FieldMasks.applyIfValid(fieldMask, unpack(record.getState()), getEntityStateType()); diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index a162ab50577..f1b1d321835 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -36,8 +36,8 @@ import java.util.List; /** - *

Util class for filtering fields that are included in an instance of {@link Message} or collection of {@link Message}s. - * Provides basic functionality for processing of e.g. a {@code org.spine3.client.Query} to in-memory storage.

+ * A utility class for {@code FieldMask} processing against instances of {@link Message}. + *

Provides basic functionality for processing of e.g. a {@code org.spine3.client.Query} to in-memory storage. * * @author Dmytro Dashenkov */ @@ -48,10 +48,10 @@ private FieldMasks() { } /** - *

Applies given {@code FieldMask} to givem collection of {@link Message}s. - * Does not change the {@link Collection} itself.

+ *

Applies given {@code FieldMask} to given collection of {@link Message}s. + * Does not change the {@link Collection} itself. * - *

The {@code FieldMask} must be valid for this operation.

+ *

The {@code FieldMask} must be valid for this operation. * * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. * @param entities {@link Message}s to filter. @@ -96,19 +96,20 @@ public static Collection appl /** *

Checks whether the given {@code FieldMask} is valid of not. - * This also includes a null check.

+ * This also includes a null check. * * @param fieldMask Nullable {@code FieldMask} to check. * @return {@code true} if the {@code FieldMask} is valid for use, {@code} false otherwise. */ - public static boolean isValid(@Nullable FieldMask fieldMask) { + public static boolean isValid(FieldMask fieldMask) { + //noinspection ConstantConditions - redundant null check. return fieldMask != null && !fieldMask.getPathsList().isEmpty(); } /** - *

Applies given {@code FieldMask} to a single {@link Message}.

+ *

Applies given {@code FieldMask} to a single {@link Message}. * - *

The {@code FieldMask} must be valid for this operation.

+ *

The {@code FieldMask} must be valid for this operation. * * @param mask {@code FieldMask} instance to apply. * @param entity The {@link Message} to apply given {@code FieldMask} to. @@ -148,13 +149,13 @@ public static M applyMask(@Suppr } /** - *

Applies {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid.

+ *

Applies {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. * * @param mask The {@code FieldMask} to apply. * @param entity The {@link Message} to apply given mask to. * @param typeUrl Type of given {@link Message}. - * @return

A {@link Message} of the same type as the given one with only selected fields - * if the {@code mask} is valid, {@code entity} itself otherwise.

+ * @return A {@link Message} of the same type as the given one with only selected fields + * if the {@code mask} is valid, {@code entity} itself otherwise. * @see #isValid(FieldMask) */ public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") @Nullable FieldMask mask, M entity, TypeUrl typeUrl) { diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 6fd1de8392f..4da4b169ac2 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -356,7 +356,7 @@ public void onNext(QueryResponse value) { assertTrue(customer.getName() .equals(sampleCustomer.getName())); assertFalse(customer.hasId()); - assertTrue(customer.getFamilyList().isEmpty()); + assertTrue(customer.getFamilyInfoList().isEmpty()); } @Override @@ -371,7 +371,7 @@ public void onCompleted() { @Test public void retrieve_collection_fields_if_required() { - requestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.FAMILYINFO_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -379,7 +379,7 @@ public void onNext(QueryResponse value) { final Customer sampleCustomer = getSampleCustomer(); final Customer customer = AnyPacker.unpack(messages.get(0)); - assertEquals(customer.getFamilyList(), sampleCustomer.getFamilyList()); + assertEquals(customer.getFamilyInfoList(), sampleCustomer.getFamilyInfoList()); assertFalse(customer.hasName()); assertFalse(customer.hasId()); @@ -397,7 +397,7 @@ public void onCompleted() { @Test public void retrieve_all_requested_fields() { - requestSampleCustomer(new int[] {Customer.FAMILY_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.FAMILYINFO_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -405,7 +405,7 @@ public void onNext(QueryResponse value) { final Customer sampleCustomer = getSampleCustomer(); final Customer customer = AnyPacker.unpack(messages.get(0)); - assertEquals(customer.getFamilyList(), sampleCustomer.getFamilyList()); + assertEquals(customer.getFamilyInfoList(), sampleCustomer.getFamilyInfoList()); assertFalse(customer.hasName()); assertTrue(customer.hasId()); @@ -463,7 +463,7 @@ public void onNext(QueryResponse value) { assertFalse(customer.hasId()); assertFalse(customer.hasName()); - assertTrue(customer.getFamilyList().isEmpty()); + assertTrue(customer.getFamilyInfoList().isEmpty()); } @Override @@ -487,7 +487,7 @@ public void onNext(QueryResponse value) { final Customer sampleCustomer = getSampleCustomer(); assertEquals(sampleCustomer.getName(), customer.getName()); - assertEquals(sampleCustomer.getFamilyList(), customer.getFamilyList()); + assertEquals(sampleCustomer.getFamilyInfoList(), customer.getFamilyInfoList()); assertTrue(customer.hasId()); } @@ -506,8 +506,8 @@ private static Customer getSampleCustomer() { .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() .getLeastSignificantBits())) .setName(PersonName.newBuilder().setGivenName("Socrates").build()) - .addFamily(PersonName.newBuilder().setGivenName("Socrates's mother")) - .addFamily(PersonName.newBuilder().setGivenName("Socrates's father")) + .addFamilyInfo(PersonName.newBuilder().setGivenName("Socrates's mother")) + .addFamilyInfo(PersonName.newBuilder().setGivenName("Socrates's father")) .build(); } diff --git a/server/src/test/proto/spine/test/clientservice/customer/customer.proto b/server/src/test/proto/spine/test/clientservice/customer/customer.proto index 2389b93e5e9..fa50232962f 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/customer.proto +++ b/server/src/test/proto/spine/test/clientservice/customer/customer.proto @@ -47,6 +47,6 @@ message Customer { // The name of the customer. people.PersonName name = 2; - // Info about customer's family - repeated people.PersonName family = 3; + // Info about customer's family. + repeated people.PersonName familyInfo = 3; } From ce9c2b4c3918914550c195d6fb357e1a8ed65527 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 17:07:26 +0300 Subject: [PATCH 176/361] Some more javadoc changes. --- server/src/main/java/org/spine3/server/entity/FieldMasks.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index f1b1d321835..43846fd41e9 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -37,7 +37,6 @@ /** * A utility class for {@code FieldMask} processing against instances of {@link Message}. - *

Provides basic functionality for processing of e.g. a {@code org.spine3.client.Query} to in-memory storage. * * @author Dmytro Dashenkov */ @@ -158,7 +157,7 @@ public static M applyMask(@Suppr * if the {@code mask} is valid, {@code entity} itself otherwise. * @see #isValid(FieldMask) */ - public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") @Nullable FieldMask mask, M entity, TypeUrl typeUrl) { + public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M entity, TypeUrl typeUrl) { if (isValid(mask)) { return applyMask(mask, entity, typeUrl); } From 727edd8ae6a343a37d45a226e0fb4d3dff120231 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 17:30:31 +0300 Subject: [PATCH 177/361] Specify failure handling policy for FieldMasks in javadoc. --- .../main/java/org/spine3/server/entity/FieldMasks.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 43846fd41e9..60035ef7822 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -52,6 +52,9 @@ private FieldMasks() { * *

The {@code FieldMask} must be valid for this operation. * + *

In case of failure (e.g. wrong {@code TypeUrl} in the field mask) this method silently handles it + * without any exception or error thrown. + * * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. * @param entities {@link Message}s to filter. * @param typeUrl Type of the {@link Message}s. @@ -110,6 +113,9 @@ public static boolean isValid(FieldMask fieldMask) { * *

The {@code FieldMask} must be valid for this operation. * + *

In case of failure (e.g. wrong {@code TypeUrl} in the field mask) this method silently handles it + * without any exception or error thrown. + * * @param mask {@code FieldMask} instance to apply. * @param entity The {@link Message} to apply given {@code FieldMask} to. * @param typeUrl Type of the {@link Message}. @@ -150,6 +156,9 @@ public static M applyMask(@Suppr /** *

Applies {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. * + *

In case of failure (e.g. wrong {@code TypeUrl} in the field mask) this method silently handles it + * without any exception or error thrown. + * * @param mask The {@code FieldMask} to apply. * @param entity The {@link Message} to apply given mask to. * @param typeUrl Type of given {@link Message}. From 6efcf89a43a205a6bef9e1851e673444eed04f2b Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 18:04:41 +0300 Subject: [PATCH 178/361] Move query composing to "Queries" util. --- .../main/java/org/spine3/base/Queries.java | 48 +++++++++++++++---- .../org/spine3/server/stand/StandShould.java | 47 +++++------------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index dbf8d89031b..a59d84e41d7 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -24,6 +24,7 @@ import com.google.protobuf.Any; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; +import com.sun.tools.javac.util.List; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; import org.spine3.client.EntityIdFilter; @@ -41,22 +42,44 @@ * Client-side utilities for working with queries. * * @author Alex Tymchenko + * @author Dmytro Dashenkov */ public class Queries { private Queries() { } - public static Query readByIds(Class entityClass, Set ids) { - final Query result = composeQuery(entityClass, ids, null); + public static Query readByIds(Class entityClass, Set ids, String... paths) { + //noinspection ConstantConditions + final FieldMask fieldMask = paths != null ? + FieldMask.newBuilder() + .addAllPaths(List.from(paths)) + .build() : + null; + final Query result = composeQuery(entityClass, ids, fieldMask); return result; } - public static Query readAll(Class entityClass) { - final Query result = composeQuery(entityClass, null, null); + public static Query readAll(Class entityClass, String... paths) { + //noinspection ConstantConditions + final FieldMask fieldMask = paths != null ? + FieldMask.newBuilder() + .addAllPaths(List.from(paths)) + .build() : + null; + final Query result = composeQuery(entityClass, null, fieldMask); return result; } + public static Query readByIds(Class entityClass, Set ids) { + //noinspection ConstantConditions + return readByIds(entityClass, ids, (String[]) null); + } + + public static Query readAll(Class entityClass) { + //noinspection ConstantConditions + return readAll(entityClass, (String[]) null); + } private static Query composeQuery(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { final Target target = composeTarget(entityClass, ids); @@ -88,7 +111,8 @@ public static Target allOf(Class entityClass) { return result; } - /* package */ static Target composeTarget(Class entityClass, @Nullable Set ids) { + /* package */ + static Target composeTarget(Class entityClass, @Nullable Set ids) { final TypeUrl type = TypeUrl.of(entityClass); final boolean includeAll = ids == null; @@ -109,11 +133,15 @@ public static Target allOf(Class entityClass) { .setIdFilter(idFilter) .build(); - return Target.newBuilder() - .setIncludeAll(includeAll) - .setType(type.getTypeName()) - .setFilters(filters) - .build(); + final Target.Builder builder = Target.newBuilder().setType(type.getTypeName()); + if (includeAll) { + builder.setIncludeAll(true); + } else { + builder.setFilters(filters); + + } + + return builder.build(); } } } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 4da4b169ac2..9be7d967387 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -306,19 +306,12 @@ private static CustomerId customerIdFor(int numericId) { @Test public void retrieve_all_data_if_field_mask_is_not_set() { final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); - final TypeUrl customerType = TypeUrl.of(Customer.class); final Customer sampleCustomer = getSampleCustomer(); stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); - final Query customerQuery = Query.newBuilder() - .setTarget( - Target.newBuilder().setIncludeAll(true) - .setType(customerType.getTypeName()) - .build()) - .build(); - + final Query customerQuery = Queries.readAll(Customer.class); //noinspection OverlyComplexAnonymousInnerClass stand.execute(customerQuery, new StreamObserver() { @@ -431,25 +424,15 @@ public void retrieve_whole_entity_if_nothing_is_requested() { public void handle_mistakes_in_query_silently() { //noinspection ZeroLengthArrayAllocation final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); - final TypeUrl customerType = TypeUrl.of(Customer.class); final Customer sampleCustomer = getSampleCustomer(); stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); // FieldMask with invalid type URLs. - final FieldMask.Builder fieldMask = FieldMask.newBuilder() - .addPaths("blablabla") - .addPaths(Project.getDescriptor().getFields().get(2).getFullName()); - final Query customerQuery = Query.newBuilder() - .setTarget( - Target.newBuilder() - .setIncludeAll(true) - .setType(customerType.getTypeName()) - .build()) - .setFieldMask(fieldMask) - .build(); + final String[] paths = {"blablabla", Project.getDescriptor().getFields().get(2).getFullName()}; + final Query customerQuery = Queries.readAll(Customer.class, paths); stand.execute(customerQuery, new StreamObserver() { @Override @@ -502,6 +485,7 @@ public void onCompleted() { } private static Customer getSampleCustomer() { + //noinspection NumericCastThatLosesPrecision return Customer.newBuilder() .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() .getLeastSignificantBits())) @@ -514,30 +498,21 @@ private static Customer getSampleCustomer() { private static void requestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); - final TypeUrl customerType = TypeUrl.of(Customer.class); final Customer sampleCustomer = getSampleCustomer(); stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); - final FieldMask.Builder fieldMask = FieldMask.newBuilder(); + final String[] paths = new String[fieldIndexes.length]; - for (int index : fieldIndexes) { - fieldMask.addPaths(Customer.getDescriptor() - .getFields() - .get(index) - .getFullName()); + for (int i = 0; i < fieldIndexes.length; i++) { + paths[i] = Customer.getDescriptor() + .getFields() + .get(fieldIndexes[i]) + .getFullName(); } - final Query customerQuery = Query.newBuilder() - .setTarget( - Target.newBuilder() - .setIncludeAll(true) - .setType(customerType.getTypeName()) - .build()) - .setFieldMask(fieldMask) - .build(); - + final Query customerQuery = Queries.readAll(Customer.class, paths); stand.execute(customerQuery, observer); } From fb8a89d4f3e6664a9b30cedfde0ab29ec553f5cc Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 18:28:42 +0300 Subject: [PATCH 179/361] Fix import issues. --- client/src/main/java/org/spine3/base/Queries.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index d8b0b45b734..716f35dce97 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -24,7 +24,6 @@ import com.google.protobuf.Any; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; -import com.sun.tools.javac.util.List; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; import org.spine3.client.EntityIdFilter; @@ -34,6 +33,7 @@ import org.spine3.protobuf.TypeUrl; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Set; import static org.spine3.base.Queries.Targets.composeTarget; @@ -53,7 +53,7 @@ public static Query readByIds(Class entityClass, Set entityClass, String... path //noinspection ConstantConditions final FieldMask fieldMask = paths != null ? FieldMask.newBuilder() - .addAllPaths(List.from(paths)) + .addAllPaths(Arrays.asList(paths)) .build() : null; final Query result = composeQuery(entityClass, null, fieldMask); From de6e6bd2de374824e588488621f1a9e2aa645cec Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 20 Sep 2016 19:35:46 +0300 Subject: [PATCH 180/361] * Implement the SubscriptionService; * tweak the Subscription to include the Type (required for cancelling the subscription); * minor JavaDoc and code cleanup (eliminate obsolete items). --- .../proto/spine/client/subscription.proto | 7 + .../java/org/spine3/server/QueryService.java | 2 +- .../spine3/server/SubscriptionService.java | 211 ++++++++++++++++++ .../java/org/spine3/server/stand/Stand.java | 5 +- 4 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/SubscriptionService.java diff --git a/client/src/main/proto/spine/client/subscription.proto b/client/src/main/proto/spine/client/subscription.proto index c44443c343f..bff425e31a0 100644 --- a/client/src/main/proto/spine/client/subscription.proto +++ b/client/src/main/proto/spine/client/subscription.proto @@ -81,4 +81,11 @@ message Subscription { // Typically built using Java's UUID.toString() functionality. // Must be unique in scope of a bounded context. string id = 1; + + // Represents TypeUrl of the target entity for this subscription. + string type = 2; + + // Reserved for subscription attributes. + reserved 3 to 10; + } diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index f7ad288814b..8937d0ce872 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -38,7 +38,7 @@ /** * The {@code QueryService} provides a synchronous way to fetch read-side state from the server. * - *

For asynchronous read-side updates please see {@code SubscriptionService}. + *

For asynchronous read-side updates please see {@link SubscriptionService}. * * @author Alex Tymchenko */ diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java new file mode 100644 index 00000000000..26f0657409f --- /dev/null +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -0,0 +1,211 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.protobuf.Any; +import io.grpc.stub.StreamObserver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spine3.base.Response; +import org.spine3.base.Responses; +import org.spine3.client.Subscription; +import org.spine3.client.SubscriptionUpdate; +import org.spine3.client.Target; +import org.spine3.client.Topic; +import org.spine3.client.grpc.SubscriptionServiceGrpc; +import org.spine3.protobuf.KnownTypes; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.stand.Stand; + +import java.util.Set; + +/** + * The {@code SubscriptionService} provides an asynchronous way to fetch read-side state from the server. + * + *

For synchronous read-side updates please see {@link QueryService}. + * + * @author Alex Tymchenko + */ +public class SubscriptionService extends SubscriptionServiceGrpc.SubscriptionServiceImplBase { + private final ImmutableMap typeToContextMap; + + private SubscriptionService(Builder builder) { + this.typeToContextMap = builder.getBoundedContextMap(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. + @Override + public void subscribe(Topic topic, final StreamObserver responseObserver) { + log().debug("Incoming subscription request to topic: {}", topic); + final Target target = topic.getTarget(); + final BoundedContext boundedContext = selectBoundedContext(target); + + try { + final SubscriptionContext context = new SubscriptionContext(); + final Stand.StandUpdateCallback updateCallback = new Stand.StandUpdateCallback() { + @Override + public void onEntityStateUpdate(Any newEntityState) { + final Subscription subscription = context.getSubscription(); + Preconditions.checkNotNull(subscription); + final SubscriptionUpdate update = SubscriptionUpdate.newBuilder() + .setSubscription(subscription) + .setResponse(Responses.ok()) + .setUpdates(0, newEntityState) + .build(); + responseObserver.onNext(update); + } + }; + + + final Subscription subscription = boundedContext.getStand() + .subscribe(target, updateCallback); + context.setSubscription(subscription); + + } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { + log().error("Error processing subscription request", e); + responseObserver.onError(e); + responseObserver.onCompleted(); + } + } + + @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. + @Override + public void cancel(Subscription subscription, StreamObserver responseObserver) { + log().debug("Incoming cancel request for the subscription topic: {}", subscription); + + final BoundedContext boundedContext = selectBoundedContext(subscription); + try { + boundedContext.getStand() + .cancel(subscription); + responseObserver.onNext(Responses.ok()); + } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { + log().error("Error processing cancel subscription request", e); + responseObserver.onError(e); + responseObserver.onCompleted(); + } + } + + private BoundedContext selectBoundedContext(Subscription subscription) { + final String typeAsString = subscription.getType(); + final TypeUrl type = KnownTypes.getTypeUrl(typeAsString); + return typeToContextMap.get(type); + } + + private BoundedContext selectBoundedContext(Target target) { + final String typeAsString = target.getType(); + final TypeUrl type = KnownTypes.getTypeUrl(typeAsString); + return typeToContextMap.get(type); + } + + + public static class Builder { + private final Set boundedContexts = Sets.newHashSet(); + private ImmutableMap typeToContextMap; + + + public Builder addBoundedContext(BoundedContext boundedContext) { + // Save it to a temporary set so that it is easy to remove it if needed. + boundedContexts.add(boundedContext); + return this; + } + + public Builder removeBoundedContext(BoundedContext boundedContext) { + boundedContexts.remove(boundedContext); + return this; + } + + + @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection returned is immutable + public ImmutableMap getBoundedContextMap() { + return typeToContextMap; + } + + /** + * Builds the {@link SubscriptionService}. + * + * @throws IllegalStateException if no bounded contexts were added. + */ + public SubscriptionService build() throws IllegalStateException { + if (boundedContexts.isEmpty()) { + throw new IllegalStateException("Subscription service must have at least one bounded context."); + } + this.typeToContextMap = createBoundedContextMap(); + final SubscriptionService result = new SubscriptionService(this); + return result; + } + + private ImmutableMap createBoundedContextMap() { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + for (BoundedContext boundedContext : boundedContexts) { + addBoundedContext(builder, boundedContext); + } + return builder.build(); + } + + private static void addBoundedContext(ImmutableMap.Builder mapBuilder, + BoundedContext boundedContext) { + + final ImmutableSet availableTypes = boundedContext.getStand() + .getAvailableTypes(); + + for (TypeUrl availableType : availableTypes) { + mapBuilder.put(availableType, boundedContext); + } + } + } + + /** + * The context for the subscription. + * + *

Used as a wrapper around the subscription being created during the {@link #subscribe(Topic, StreamObserver)}. + */ + private static class SubscriptionContext { + private Subscription subscription; + + public Subscription getSubscription() { + return subscription; + } + + public void setSubscription(Subscription subscription) { + this.subscription = subscription; + } + } + + private static Logger log() { + return LogSingleton.INSTANCE.value; + } + + private enum LogSingleton { + INSTANCE; + @SuppressWarnings("NonSerializableFieldInSerializableClass") + private final Logger value = LoggerFactory.getLogger(SubscriptionService.class); + } + +} diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 5205be6cc0c..b340261a304 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -409,7 +409,6 @@ public void deregisterSupplierForType(TypeUrl typeUrl) { * @see #subscribe(Target, StandUpdateCallback) * @see #cancel(Subscription) */ - @SuppressWarnings("InterfaceNeverImplemented") //it's OK, there may be no callbacks in the codebase public interface StandUpdateCallback { void onEntityStateUpdate(Any newEntityState); @@ -492,10 +491,12 @@ private static final class StandSubscriptionRegistry { private synchronized Subscription addSubscription(Target target, StandUpdateCallback callback) { final String subscriptionId = UUID.randomUUID() .toString(); + final String typeAsString = target.getType(); + final TypeUrl type = TypeUrl.of(typeAsString); final Subscription subscription = Subscription.newBuilder() .setId(subscriptionId) + .setType(typeAsString) .build(); - final TypeUrl type = TypeUrl.of(target.getType()); final SubscriptionRecord attributes = new SubscriptionRecord(subscription, target, type, callback); if (!typeToAttrs.containsKey(type)) { From c4a142d78d2cbb7e7b623b32de74569dd93684fe Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 19:43:23 +0300 Subject: [PATCH 181/361] Address issues form PR. --- .../main/java/org/spine3/base/Queries.java | 20 +++++--------- .../server/entity/EntityRepository.java | 4 +-- .../org/spine3/server/entity/FieldMasks.java | 27 +++++++++---------- .../spine3/server/storage/RecordStorage.java | 8 +++--- .../spine3/server/storage/StandStorage.java | 4 +-- .../storage/memory/InMemoryRecordStorage.java | 21 ++++++++++----- .../storage/memory/InMemoryStandStorage.java | 4 +-- .../org/spine3/server/stand/StandShould.java | 18 ++++++------- .../clientservice/customer/customer.proto | 4 +-- 9 files changed, 53 insertions(+), 57 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index 716f35dce97..fdbefcd0530 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -50,35 +50,27 @@ private Queries() { } public static Query readByIds(Class entityClass, Set ids, String... paths) { - //noinspection ConstantConditions - final FieldMask fieldMask = paths != null ? - FieldMask.newBuilder() + final FieldMask fieldMask = FieldMask.newBuilder() .addAllPaths(Arrays.asList(paths)) - .build() : - null; + .build(); final Query result = composeQuery(entityClass, ids, fieldMask); return result; } public static Query readAll(Class entityClass, String... paths) { - //noinspection ConstantConditions - final FieldMask fieldMask = paths != null ? - FieldMask.newBuilder() + final FieldMask fieldMask = FieldMask.newBuilder() .addAllPaths(Arrays.asList(paths)) - .build() : - null; + .build(); final Query result = composeQuery(entityClass, null, fieldMask); return result; } public static Query readByIds(Class entityClass, Set ids) { - //noinspection ConstantConditions - return readByIds(entityClass, ids, (String[]) null); + return composeQuery(entityClass, ids, null); } public static Query readAll(Class entityClass) { - //noinspection ConstantConditions - return readAll(entityClass, (String[]) null); + return composeQuery(entityClass, null, null); } private static Query composeQuery(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 183da7b7402..6bf3b51bba9 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -43,9 +43,9 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -169,7 +169,7 @@ public ImmutableCollection findBulk(Iterable ids, FieldMask fieldMask) { final Iterator idIterator = ids.iterator(); final Iterator recordIterator = entityStorageRecords.iterator(); - final List entities = new ArrayList<>(); + final List entities = new LinkedList<>(); while (idIterator.hasNext() && recordIterator.hasNext()) { final I id = idIterator.next(); diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 60035ef7822..7560fd4fbf2 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -30,9 +30,9 @@ import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedList; import java.util.List; /** @@ -47,13 +47,13 @@ private FieldMasks() { } /** - *

Applies given {@code FieldMask} to given collection of {@link Message}s. + *

Applies the given {@code FieldMask} to given collection of {@link Message}s. * Does not change the {@link Collection} itself. * *

The {@code FieldMask} must be valid for this operation. * - *

In case of failure (e.g. wrong {@code TypeUrl} in the field mask) this method silently handles it - * without any exception or error thrown. + *

n case the {@code FieldMask} instance contains invalid field declarations, they are ignored and + * do not affect the execution result. * * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. * @param entities {@link Message}s to filter. @@ -63,7 +63,7 @@ private FieldMasks() { */ @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { - final List filtered = new ArrayList<>(); + final List filtered = new LinkedList<>(); final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(typeUrl); @@ -100,21 +100,20 @@ public static Collection appl *

Checks whether the given {@code FieldMask} is valid of not. * This also includes a null check. * - * @param fieldMask Nullable {@code FieldMask} to check. + * @param fieldMask {@code FieldMask} to check. * @return {@code true} if the {@code FieldMask} is valid for use, {@code} false otherwise. */ public static boolean isValid(FieldMask fieldMask) { - //noinspection ConstantConditions - redundant null check. - return fieldMask != null && !fieldMask.getPathsList().isEmpty(); + return !fieldMask.getPathsList().isEmpty(); } /** - *

Applies given {@code FieldMask} to a single {@link Message}. + *

Applies the given {@code FieldMask} to a single {@link Message}. * *

The {@code FieldMask} must be valid for this operation. * - *

In case of failure (e.g. wrong {@code TypeUrl} in the field mask) this method silently handles it - * without any exception or error thrown. + *

In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and + * do not affect the execution result. * * @param mask {@code FieldMask} instance to apply. * @param entity The {@link Message} to apply given {@code FieldMask} to. @@ -154,10 +153,10 @@ public static M applyMask(@Suppr } /** - *

Applies {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. + *

Applies the {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. * - *

In case of failure (e.g. wrong {@code TypeUrl} in the field mask) this method silently handles it - * without any exception or error thrown. + *

In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and + * do not affect the execution result. * * @param mask The {@code FieldMask} to apply. * @param entity The {@link Message} to apply given mask to. diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index aeed50be83d..e76a1f33271 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -62,12 +62,12 @@ public EntityStorageRecord read(I id) { } /** - * Read one specific item form storage. + * Read a single item from the storage. * - * @param id id of the item to read. - * @param fieldMask Fields to read. Can be {@code null}, but it's more convenient to call {@link #read(Object)} - * in order to get all the fields. + * @param id ID of the item to read. + * @param fieldMask Fields to read. * @return Non-null {@code EntityStorageRecord} instance. + * @see #read(Object) */ public EntityStorageRecord read(I id, FieldMask fieldMask) { final EntityStorageRecord.Builder builder = EntityStorageRecord.newBuilder(read(id)); diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index af2e9cc18bd..6acc5a0bdb6 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -28,8 +28,6 @@ import org.spine3.server.stand.AggregateStateId; import org.spine3.server.stand.Stand; -import javax.annotation.Nullable; - /** * Contract for {@link Stand} storage. * @@ -60,5 +58,5 @@ protected StandStorage(boolean multitenant) { * @param type a {@link TypeUrl} instance * @return the state records which {@link Any#getTypeUrl()} equals the argument value */ - public abstract ImmutableCollection readAllByType(TypeUrl type, @Nullable FieldMask fieldMask); + public abstract ImmutableCollection readAllByType(TypeUrl type, FieldMask fieldMask); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 8394842abba..908dbf30544 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -64,24 +64,31 @@ protected InMemoryRecordStorage(boolean multitenant) { @SuppressWarnings("MethodWithMultipleLoops") /* It's OK for in-memory implementation * as it is used primarily in tests. */ @Override - protected Iterable readBulkInternal(final Iterable givenIds, @Nullable FieldMask fieldMask) { + protected Iterable readBulkInternal(final Iterable givenIds, FieldMask fieldMask) { final Map storage = getStorage(); // It is not possible to return an immutable collection, since {@code null} may be present in it. final Collection result = new LinkedList<>(); + TypeUrl typeUrl = null; + for (I recordId : storage.keySet()) { for (I givenId : givenIds) { if (recordId.equals(givenId)) { final EntityStorageRecord.Builder matchingRecord = storage.get(recordId).toBuilder(); final Any state = matchingRecord.getState(); - matchingRecord.setState( - AnyPacker.pack( - FieldMasks.applyIfValid( - fieldMask, - AnyPacker.unpack(state), - TypeUrl.of(state.getTypeUrl())))); + if (typeUrl == null) { + typeUrl = TypeUrl.of(state.getTypeUrl()); + } + + final Any processed = AnyPacker.pack( + FieldMasks.applyIfValid( + fieldMask, + AnyPacker.unpack(state), + typeUrl)); + + matchingRecord.setState(processed); result.add(matchingRecord.build()); continue; diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 5fa77e0af7b..3a9dc795480 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -60,11 +60,11 @@ public static Builder newBuilder() { @Override public ImmutableCollection readAllByType(final TypeUrl type) { - return readAllByType(type, null); + return readAllByType(type, FieldMask.getDefaultInstance()); } @Override - public ImmutableCollection readAllByType(final TypeUrl type, @Nullable FieldMask fieldMask) { + public ImmutableCollection readAllByType(final TypeUrl type, FieldMask fieldMask) { final Map allRecords = readAll(fieldMask); final Map resultMap = Maps.filterKeys(allRecords, new Predicate() { @Override diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 179e006a39a..4e39ea96df2 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -496,7 +496,7 @@ public void onNext(QueryResponse value) { assertTrue(customer.getName() .equals(sampleCustomer.getName())); assertFalse(customer.hasId()); - assertTrue(customer.getFamilyInfoList().isEmpty()); + assertTrue(customer.getNicknamesList().isEmpty()); } @Override @@ -511,7 +511,7 @@ public void onCompleted() { @Test public void retrieve_collection_fields_if_required() { - requestSampleCustomer(new int[] {Customer.FAMILYINFO_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -519,7 +519,7 @@ public void onNext(QueryResponse value) { final Customer sampleCustomer = getSampleCustomer(); final Customer customer = AnyPacker.unpack(messages.get(0)); - assertEquals(customer.getFamilyInfoList(), sampleCustomer.getFamilyInfoList()); + assertEquals(customer.getNicknamesList(), sampleCustomer.getNicknamesList()); assertFalse(customer.hasName()); assertFalse(customer.hasId()); @@ -537,7 +537,7 @@ public void onCompleted() { @Test public void retrieve_all_requested_fields() { - requestSampleCustomer(new int[] {Customer.FAMILYINFO_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new StreamObserver() { @Override public void onNext(QueryResponse value) { final List messages = value.getMessagesList(); @@ -545,7 +545,7 @@ public void onNext(QueryResponse value) { final Customer sampleCustomer = getSampleCustomer(); final Customer customer = AnyPacker.unpack(messages.get(0)); - assertEquals(customer.getFamilyInfoList(), sampleCustomer.getFamilyInfoList()); + assertEquals(customer.getNicknamesList(), sampleCustomer.getNicknamesList()); assertFalse(customer.hasName()); assertTrue(customer.hasId()); @@ -593,7 +593,7 @@ public void onNext(QueryResponse value) { assertFalse(customer.hasId()); assertFalse(customer.hasName()); - assertTrue(customer.getFamilyInfoList().isEmpty()); + assertTrue(customer.getNicknamesList().isEmpty()); } @Override @@ -617,7 +617,7 @@ public void onNext(QueryResponse value) { final Customer sampleCustomer = getSampleCustomer(); assertEquals(sampleCustomer.getName(), customer.getName()); - assertEquals(sampleCustomer.getFamilyInfoList(), customer.getFamilyInfoList()); + assertEquals(sampleCustomer.getNicknamesList(), customer.getNicknamesList()); assertTrue(customer.hasId()); } @@ -637,8 +637,8 @@ private static Customer getSampleCustomer() { .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() .getLeastSignificantBits())) .setName(PersonName.newBuilder().setGivenName("Socrates").build()) - .addFamilyInfo(PersonName.newBuilder().setGivenName("Socrates's mother")) - .addFamilyInfo(PersonName.newBuilder().setGivenName("Socrates's father")) + .addNicknames(PersonName.newBuilder().setGivenName("Philosopher")) + .addNicknames(PersonName.newBuilder().setGivenName("Wise guy")) .build(); } diff --git a/server/src/test/proto/spine/test/clientservice/customer/customer.proto b/server/src/test/proto/spine/test/clientservice/customer/customer.proto index fa50232962f..dbb223cabbb 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/customer.proto +++ b/server/src/test/proto/spine/test/clientservice/customer/customer.proto @@ -47,6 +47,6 @@ message Customer { // The name of the customer. people.PersonName name = 2; - // Info about customer's family. - repeated people.PersonName familyInfo = 3; + // This person is also known as + repeated people.PersonName nicknames = 3; } From 062b6f8bd3985ca95f2023d482ea661cf212fdf3 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 20 Sep 2016 20:03:59 +0300 Subject: [PATCH 182/361] Remove 'isValid(FieldMask)' method. --- .../org/spine3/server/entity/FieldMasks.java | 18 ++---------------- .../java/org/spine3/server/stand/Stand.java | 5 ++--- .../storage/memory/InMemoryRecordStorage.java | 2 +- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 7560fd4fbf2..8ade4cc6898 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -50,7 +50,7 @@ private FieldMasks() { *

Applies the given {@code FieldMask} to given collection of {@link Message}s. * Does not change the {@link Collection} itself. * - *

The {@code FieldMask} must be valid for this operation. + *

The {@code FieldMask} must not be empty for this operation. * *

n case the {@code FieldMask} instance contains invalid field declarations, they are ignored and * do not affect the execution result. @@ -59,7 +59,6 @@ private FieldMasks() { * @param entities {@link Message}s to filter. * @param typeUrl Type of the {@link Message}s. * @return Non-null unmodifiable {@link Collection} of {@link Message}s of the same type that the input had. - * @see #isValid(FieldMask) */ @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { @@ -96,17 +95,6 @@ public static Collection appl return Collections.unmodifiableList(filtered); } - /** - *

Checks whether the given {@code FieldMask} is valid of not. - * This also includes a null check. - * - * @param fieldMask {@code FieldMask} to check. - * @return {@code true} if the {@code FieldMask} is valid for use, {@code} false otherwise. - */ - public static boolean isValid(FieldMask fieldMask) { - return !fieldMask.getPathsList().isEmpty(); - } - /** *

Applies the given {@code FieldMask} to a single {@link Message}. * @@ -119,7 +107,6 @@ public static boolean isValid(FieldMask fieldMask) { * @param entity The {@link Message} to apply given {@code FieldMask} to. * @param typeUrl Type of the {@link Message}. * @return A {@link Message} of the same type as the given one with only selected fields. - * @see #isValid(FieldMask) */ @SuppressWarnings("unchecked") public static M applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M entity, TypeUrl typeUrl) { @@ -163,10 +150,9 @@ public static M applyMask(@Suppr * @param typeUrl Type of given {@link Message}. * @return A {@link Message} of the same type as the given one with only selected fields * if the {@code mask} is valid, {@code entity} itself otherwise. - * @see #isValid(FieldMask) */ public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M entity, TypeUrl typeUrl) { - if (isValid(mask)) { + if (!mask.getPathsList().isEmpty()) { return applyMask(mask, entity, typeUrl); } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 2f30a3d8207..fc11fe695dd 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -50,7 +50,6 @@ import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.entity.Entity; import org.spine3.server.entity.EntityRepository; -import org.spine3.server.entity.FieldMasks; import org.spine3.server.entity.Repository; import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; @@ -291,7 +290,7 @@ private ImmutableCollection fetchFromStandStorage(Query que ImmutableCollection result; final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); - final boolean shouldApplyFieldMask = FieldMasks.isValid(fieldMask); + final boolean shouldApplyFieldMask = !fieldMask.getPathsList().isEmpty(); if (target.getIncludeAll()) { result = shouldApplyFieldMask ? @@ -365,7 +364,7 @@ private static ImmutableCollection fetchFromEntityRepository(Q final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); - if (target.getIncludeAll() && !FieldMasks.isValid(fieldMask)) { + if (target.getIncludeAll() && fieldMask.getPathsList().isEmpty()) { result = repository.findAll(); } else { final EntityFilters filters = target.getFilters(); diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 908dbf30544..09988259b0a 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -114,7 +114,7 @@ protected Map readAllInternal() { @Override protected Map readAllInternal(FieldMask fieldMask) { - if (!FieldMasks.isValid(fieldMask)) { + if (fieldMask.getPathsList().isEmpty()) { return readAllInternal(); } From 1598bd6d500afdc41460848f7b18cfc4510c49fb Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 21 Sep 2016 15:28:38 +0300 Subject: [PATCH 183/361] Fix issues from PR. --- .../java/org/spine3/server/entity/FieldMasks.java | 6 +----- .../server/storage/memory/InMemoryRecordStorage.java | 12 +++++------- .../spine/test/clientservice/customer/customer.proto | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 8ade4cc6898..c1f474e352c 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -50,8 +50,6 @@ private FieldMasks() { *

Applies the given {@code FieldMask} to given collection of {@link Message}s. * Does not change the {@link Collection} itself. * - *

The {@code FieldMask} must not be empty for this operation. - * *

n case the {@code FieldMask} instance contains invalid field declarations, they are ignored and * do not affect the execution result. * @@ -97,9 +95,7 @@ public static Collection appl /** *

Applies the given {@code FieldMask} to a single {@link Message}. - * - *

The {@code FieldMask} must be valid for this operation. - * + ** *

In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and * do not affect the execution result. * diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 09988259b0a..b2635d29091 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -82,18 +82,16 @@ protected Iterable readBulkInternal(final Iterable given typeUrl = TypeUrl.of(state.getTypeUrl()); } - final Any processed = AnyPacker.pack( - FieldMasks.applyIfValid( - fieldMask, - AnyPacker.unpack(state), - typeUrl)); + final Message wholeState = AnyPacker.unpack(state); + final Message maskedState = FieldMasks.applyIfValid(fieldMask, wholeState, typeUrl); + final Any processed = AnyPacker.pack(maskedState); matchingRecord.setState(processed); result.add(matchingRecord.build()); - continue; + } else { + result.add(null); } - result.add(null); } } return result; diff --git a/server/src/test/proto/spine/test/clientservice/customer/customer.proto b/server/src/test/proto/spine/test/clientservice/customer/customer.proto index dbb223cabbb..ce625c9e471 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/customer.proto +++ b/server/src/test/proto/spine/test/clientservice/customer/customer.proto @@ -47,6 +47,6 @@ message Customer { // The name of the customer. people.PersonName name = 2; - // This person is also known as + // The list of customer's nicknames. repeated people.PersonName nicknames = 3; } From a37aaa90a6c6c9511643424dba3979a7bf78ef10 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 21 Sep 2016 16:07:29 +0300 Subject: [PATCH 184/361] Check stream observer methods are called. --- .../org/spine3/server/stand/StandShould.java | 101 +++++++----------- 1 file changed, 40 insertions(+), 61 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 4e39ea96df2..4a614319a2b 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -231,9 +231,11 @@ public void return_empty_list_to_unknown_type_reading() { // So create a query for an unknown type. final Query readAllCustomers = Queries.readAll(Customer.class); - final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); + final MemoizeQueryResponseObserver responseObserver = spy(new MemoizeQueryResponseObserver()); stand.execute(readAllCustomers, responseObserver); + verifyObserver(responseObserver); + final List messageList = checkAndGetMessageList(responseObserver); assertTrue("Query returned a non-empty response message list for an unknown type", messageList.isEmpty()); @@ -461,9 +463,10 @@ public void retrieve_all_data_if_field_mask_is_not_set() { final Query customerQuery = Queries.readAll(Customer.class); //noinspection OverlyComplexAnonymousInnerClass - stand.execute(customerQuery, new StreamObserver() { + final MemoizeQueryResponseObserver observer = new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { + super.onNext(value); final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); @@ -472,22 +475,20 @@ public void onNext(QueryResponse value) { assertTrue(customer.getField(field).equals(sampleCustomer.getField(field))); } } + }; - @Override - public void onError(Throwable t) { - } + stand.execute(customerQuery, observer); - @Override - public void onCompleted() { - } - }); + verifyObserver(observer); } @Test public void retrieve_only_selected_param_for_query() { - requestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { + super.onNext(value); + final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); @@ -498,22 +499,16 @@ public void onNext(QueryResponse value) { assertFalse(customer.hasId()); assertTrue(customer.getNicknamesList().isEmpty()); } - - @Override - public void onError(Throwable t) { - } - - @Override - public void onCompleted() { - } }); } @Test public void retrieve_collection_fields_if_required() { - requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { + super.onNext(value); + final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); @@ -524,22 +519,16 @@ public void onNext(QueryResponse value) { assertFalse(customer.hasName()); assertFalse(customer.hasId()); } - - @Override - public void onError(Throwable t) { - } - - @Override - public void onCompleted() { - } }); } @Test public void retrieve_all_requested_fields() { - requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new StreamObserver() { + requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { + super.onNext(value); + final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); @@ -550,14 +539,6 @@ public void onNext(QueryResponse value) { assertFalse(customer.hasName()); assertTrue(customer.hasId()); } - - @Override - public void onError(Throwable t) { - } - - @Override - public void onCompleted() { - } }); } @@ -581,9 +562,10 @@ public void handle_mistakes_in_query_silently() { final Query customerQuery = Queries.readAll(Customer.class, paths); - stand.execute(customerQuery, new StreamObserver() { + final MemoizeQueryResponseObserver observer = new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { + super.onNext(value); final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); @@ -593,23 +575,28 @@ public void onNext(QueryResponse value) { assertFalse(customer.hasId()); assertFalse(customer.hasName()); - assertTrue(customer.getNicknamesList().isEmpty()); + assertTrue(customer.getNicknamesList() + .isEmpty()); } + }; - @Override - public void onError(Throwable t) { - } + stand.execute(customerQuery, observer); - @Override - public void onCompleted() { - } - }); + verifyObserver(observer); } - private static StreamObserver getDuplicateCostumerStreamObserver() { - return new StreamObserver() { + private static void verifyObserver(MemoizeQueryResponseObserver observer) { + assertNotNull(observer.responseHandled); + assertTrue(observer.isCompleted); + assertNull(observer.throwable); + } + + private static MemoizeQueryResponseObserver getDuplicateCostumerStreamObserver() { + return new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { + super.onNext(value); + final List messages = value.getMessagesList(); assertFalse(messages.isEmpty()); @@ -620,14 +607,6 @@ public void onNext(QueryResponse value) { assertEquals(sampleCustomer.getNicknamesList(), customer.getNicknamesList()); assertTrue(customer.hasId()); } - - @Override - public void onError(Throwable t) { - } - - @Override - public void onCompleted() { - } }; } @@ -643,7 +622,7 @@ private static Customer getSampleCustomer() { } - private static void requestSampleCustomer(int[] fieldIndexes, StreamObserver observer) { + private static void requestSampleCustomer(int[] fieldIndexes, final MemoizeQueryResponseObserver observer) { final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); final Customer sampleCustomer = getSampleCustomer(); @@ -662,6 +641,8 @@ private static void requestSampleCustomer(int[] fieldIndexes, StreamObserver messageList = checkAndGetMessageList(responseObserver); assertTrue("Query returned a non-empty response message list though the filter was not set", messageList.isEmpty()); } @@ -1045,10 +1028,6 @@ public void onCompleted() { } - - /** - * A {@link StreamObserver} storing the state of {@link Query} execution. - */ private static class MemoizeStandUpdateCallback implements Stand.StandUpdateCallback { private Any newEntityState; From a0155594b29c58fcc575fd2fda145bca5117ca93 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 21 Sep 2016 16:23:53 +0300 Subject: [PATCH 185/361] Remove redundant spy objects. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 4a614319a2b..58eefc485f3 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -231,7 +231,7 @@ public void return_empty_list_to_unknown_type_reading() { // So create a query for an unknown type. final Query readAllCustomers = Queries.readAll(Customer.class); - final MemoizeQueryResponseObserver responseObserver = spy(new MemoizeQueryResponseObserver()); + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); stand.execute(readAllCustomers, responseObserver); verifyObserver(responseObserver); @@ -730,7 +730,7 @@ private static void checkEmptyResultOnNonEmptyStorageForQueryTarget(Target custo .setTarget(customerTarget) .build(); - final MemoizeQueryResponseObserver responseObserver = spy(new MemoizeQueryResponseObserver()); + final MemoizeQueryResponseObserver responseObserver = new MemoizeQueryResponseObserver(); stand.execute(queryWithNoFilters, responseObserver); verifyObserver(responseObserver); From f8db697bdf93081d85e577cd0638a595dfd859f4 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 21 Sep 2016 18:08:54 +0300 Subject: [PATCH 186/361] Ensure EOF. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 14c6aa59612..263a8583667 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ before_install: after_success: - codecov -script: ./gradlew check --stacktrace \ No newline at end of file +script: ./gradlew check --stacktrace From fde5d757434a7d10e531902ec014ceb48f8fcac2 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 21 Sep 2016 18:27:35 +0300 Subject: [PATCH 187/361] Remove redundant inspection suppress. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 58eefc485f3..7251da96332 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -784,7 +784,6 @@ private static void setupExpectedFindAllBehaviour(Map sample when(projectionRepository.findAll()).thenReturn(allResults); final EntityFilters matchingFilter = argThat(entityFilterMatcher(projectIds)); - //noinspection deprecation when(projectionRepository.findAll(matchingFilter, any(FieldMask.class))).thenReturn(allResults); } From 1e417a92161a728d8fe4d75b5fd4d7b1ed24024f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 22 Sep 2016 15:18:34 +0300 Subject: [PATCH 188/361] Redefine the SubscriptionService typical flow to Subscribe -> Activate -> Cancel, making it more obvious for end-users. --- .../spine/client/subscription_service.proto | 15 ++++- .../spine3/server/SubscriptionService.java | 57 ++++++++-------- .../java/org/spine3/server/stand/Stand.java | 66 ++++++++++++++----- 3 files changed, 88 insertions(+), 50 deletions(-) diff --git a/client/src/main/proto/spine/client/subscription_service.proto b/client/src/main/proto/spine/client/subscription_service.proto index 83a0e16ccdc..5a501f0c22a 100644 --- a/client/src/main/proto/spine/client/subscription_service.proto +++ b/client/src/main/proto/spine/client/subscription_service.proto @@ -35,14 +35,23 @@ import "spine/base/response.proto"; // A service for subscribing to the read-side changes by clients. service SubscriptionService { - // Subscribe to a particular read-side updates. + // Create the subscription to the particular read-side updates. // // Topic defines the target of subscription and other attributes (like field masks). - // The result is a SubscriptionUpdate stream, - rpc Subscribe (Topic) returns (stream SubscriptionUpdate); + // NOTE: this method serves for Topic creation; to start listening for updates use #Activate(Subscription). + rpc Subscribe (Topic) returns (Subscription); + + + // Activate the subscription to the particular read-side updates. + // + // The client will start receiving the subscription updates upon new topic target changes in the read-side. + // Cancelled subscriptions cannot be activated. + rpc Activate(Subscription) returns (stream SubscriptionUpdate); + // Cancel the given subscription. // // Clients will stop receiving the SubscriptionUpdate after this method is called. + // The subscription is destroyed as a result of invocation. rpc Cancel (Subscription) returns (base.Response); } diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index 26f0657409f..f9dd8b69e91 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -60,19 +60,39 @@ public static Builder newBuilder() { return new Builder(); } + @Override @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. + public void subscribe(Topic topic, StreamObserver responseObserver) { + log().debug("Creating the subscription to a topic: {}", topic); + + try { + final Target target = topic.getTarget(); + final BoundedContext boundedContext = selectBoundedContext(target); + final Stand stand = boundedContext.getStand(); + + final Subscription subscription = stand.subscribe(target); + + responseObserver.onNext(subscription); + responseObserver.onCompleted(); + } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { + log().error("Error processing subscription request", e); + responseObserver.onError(e); + responseObserver.onCompleted(); + } + + } + @Override - public void subscribe(Topic topic, final StreamObserver responseObserver) { - log().debug("Incoming subscription request to topic: {}", topic); - final Target target = topic.getTarget(); - final BoundedContext boundedContext = selectBoundedContext(target); + @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. + public void activate(final Subscription subscription, final StreamObserver responseObserver) { + log().debug("Activating the subscription: {}", subscription); try { - final SubscriptionContext context = new SubscriptionContext(); + final BoundedContext boundedContext = selectBoundedContext(subscription); + final Stand.StandUpdateCallback updateCallback = new Stand.StandUpdateCallback() { @Override public void onEntityStateUpdate(Any newEntityState) { - final Subscription subscription = context.getSubscription(); Preconditions.checkNotNull(subscription); final SubscriptionUpdate update = SubscriptionUpdate.newBuilder() .setSubscription(subscription) @@ -82,14 +102,10 @@ public void onEntityStateUpdate(Any newEntityState) { responseObserver.onNext(update); } }; - - - final Subscription subscription = boundedContext.getStand() - .subscribe(target, updateCallback); - context.setSubscription(subscription); + boundedContext.getStand().activate(subscription, updateCallback); } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { - log().error("Error processing subscription request", e); + log().error("Error activating the subscription", e); responseObserver.onError(e); responseObserver.onCompleted(); } @@ -181,23 +197,6 @@ private static void addBoundedContext(ImmutableMap.BuilderUsed as a wrapper around the subscription being created during the {@link #subscribe(Topic, StreamObserver)}. - */ - private static class SubscriptionContext { - private Subscription subscription; - - public Subscription getSubscription() { - return subscription; - } - - public void setSubscription(Subscription subscription) { - this.subscription = subscription; - } - } - private static Logger log() { return LogSingleton.INSTANCE.value; } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index b340261a304..0aff8dcf33a 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -163,7 +163,9 @@ public void update(final Object id, final Any entityState) { if (subscriptionRegistry.hasType(typeUrl)) { final Set allRecords = subscriptionRegistry.byType(typeUrl); for (final SubscriptionRecord subscriptionRecord : allRecords) { - if (subscriptionRecord.matches(typeUrl, id, entityState)) { + + final boolean subscriptionIsActive = subscriptionRecord.isActive(); + if (subscriptionIsActive && subscriptionRecord.matches(typeUrl, id, entityState)) { callbackExecutor.execute(new Runnable() { @Override public void run() { @@ -181,20 +183,33 @@ public void run() { *

Once this instance of {@code Stand} receives an update of an entity with the given {@code TypeUrl}, * all such callbacks are executed. * - * @param target an instance {@link Target}, defining the entity and criteria, - * which changes should be propagated to the {@code callback} - * @param callback an instance of {@link StandUpdateCallback} executed upon entity update. + * @param target an instance {@link Target}, defining the entity and criteria, + * which changes should be propagated to the {@code callback} */ @CheckReturnValue - public Subscription subscribe(Target target, StandUpdateCallback callback) { - final Subscription subscription = subscriptionRegistry.addSubscription(target, callback); + public Subscription subscribe(Target target) { + final Subscription subscription = subscriptionRegistry.addSubscription(target); return subscription; } + /** + * Activate the subscription created via {@link #subscribe(Target)}. + * + *

After the activation, the clients will start receiving the updates via {@code StandUpdateCallback} + * upon the changes in the entities, defined by the {@code Target} attribute used for this subscription. + * + * @param subscription the subscription to activate. + * @param callback an instance of {@link StandUpdateCallback} executed upon entity update. + * @see #subscribe(Target) + */ + public void activate(Subscription subscription, StandUpdateCallback callback) { + subscriptionRegistry.activate(subscription, callback); + } + /** * Cancel the {@link Subscription}. * - *

Typically invoked to cancel the previous {@link #subscribe(Target, StandUpdateCallback)} call. + *

Typically invoked to cancel the previous {@link #activate(Subscription, StandUpdateCallback)} call. *

After this method is called, the subscribers stop receiving the updates, * related to the given {@code Subscription}. * @@ -406,7 +421,7 @@ public void deregisterSupplierForType(TypeUrl typeUrl) { /** * A contract for the callbacks to be executed upon entity state change. * - * @see #subscribe(Target, StandUpdateCallback) + * @see #activate(Subscription, StandUpdateCallback) * @see #cancel(Subscription) */ public interface StandUpdateCallback { @@ -487,8 +502,15 @@ private static final class StandSubscriptionRegistry { private final Map> typeToAttrs = new HashMap<>(); private final Map subscriptionToAttrs = new HashMap<>(); + private synchronized void activate(Subscription subscription, StandUpdateCallback callback) { + if (!subscriptionToAttrs.containsKey(subscription)) { + throw new RuntimeException("Cannot find the subscription in the registry."); + } + final SubscriptionRecord subscriptionRecord = subscriptionToAttrs.get(subscription); + subscriptionRecord.activate(callback); + } - private synchronized Subscription addSubscription(Target target, StandUpdateCallback callback) { + private synchronized Subscription addSubscription(Target target) { final String subscriptionId = UUID.randomUUID() .toString(); final String typeAsString = target.getType(); @@ -497,7 +519,7 @@ private synchronized Subscription addSubscription(Target target, StandUpdateCall .setId(subscriptionId) .setType(typeAsString) .build(); - final SubscriptionRecord attributes = new SubscriptionRecord(subscription, target, type, callback); + final SubscriptionRecord attributes = new SubscriptionRecord(subscription, target, type); if (!typeToAttrs.containsKey(type)) { typeToAttrs.put(type, new HashSet()); @@ -541,15 +563,29 @@ private static final class SubscriptionRecord { private final Subscription subscription; private final Target target; private final TypeUrl type; - private final StandUpdateCallback callback; + private StandUpdateCallback callback = null; - private SubscriptionRecord(Subscription subscription, Target target, TypeUrl type, StandUpdateCallback callback) { + private SubscriptionRecord(Subscription subscription, Target target, TypeUrl type) { this.subscription = subscription; this.target = target; this.type = type; + } + + private void activate(StandUpdateCallback callback) { this.callback = callback; } + private boolean isActive() { + final boolean result = this.callback != null; + return result; + } + + private boolean matches(TypeUrl type, Object id, Any entityState) { + final boolean typeMatches = this.type.equals(type); + // TODO[alex.tymchenko]: use EntityFilter to match ID and state against it + return typeMatches; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -566,11 +602,5 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hashCode(subscription); } - - private boolean matches(TypeUrl type, Object id, Any entityState) { - final boolean typeMatches = this.type.equals(type); - // TODO[alex.tymchenko]: use EntityFilter to match ID and state against it - return typeMatches; - } } } From a12f5a0aeab51061b46bd9ac184b86a26ec3ee39 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 22 Sep 2016 15:33:32 +0300 Subject: [PATCH 189/361] Resolve the test compilation issues. --- .../java/org/spine3/server/stand/Stand.java | 19 ++++++++- .../org/spine3/server/stand/StandShould.java | 41 ++++++------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 0aff8dcf33a..bc4bf6ea15d 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -582,8 +582,25 @@ private boolean isActive() { private boolean matches(TypeUrl type, Object id, Any entityState) { final boolean typeMatches = this.type.equals(type); - // TODO[alex.tymchenko]: use EntityFilter to match ID and state against it + return typeMatches; + // TODO[alex.tymchenko]: complete the implementation. +// final boolean includeAll = target.getIncludeAll(); +// +// if(typeMatches && includeAll) { +// return true; +// } +// +// final EntityIdFilter givenIdFilter = target.getFilters() +// .getIdFilter(); +// final boolean idFilterSet = !EntityIdFilter.getDefaultInstance() +// .equals(givenIdFilter); +// +// if (idFilterSet) { +// +// } +// +// return false; } @Override diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 0d90b4801a2..d283516f483 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -38,7 +38,6 @@ import org.spine3.base.Responses; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; -import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; import org.spine3.client.QueryResponse; import org.spine3.client.Subscription; @@ -164,7 +163,8 @@ public void use_provided_executor_upon_update_of_watched_type() { stand.registerTypeSupplier(standTestProjectionRepo); final Target projectProjectionTarget = Queries.Targets.allOf(Project.class); - final Subscription subscription = stand.subscribe(projectProjectionTarget, emptyUpdateCallback()); + final Subscription subscription = stand.subscribe(projectProjectionTarget); + stand.activate(subscription, emptyUpdateCallback()); assertNotNull(subscription); verify(executor, never()).execute(any(Runnable.class)); @@ -278,7 +278,8 @@ public void trigger_subscription_callback_upon_update_of_aggregate() { final Target allCustomers = Queries.Targets.allOf(Customer.class); final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); - final Subscription subscription = stand.subscribe(allCustomers, memoizeCallback); + final Subscription subscription = stand.subscribe(allCustomers); + stand.activate(subscription, memoizeCallback); assertNotNull(subscription); assertNull(memoizeCallback.newEntityState); @@ -300,7 +301,8 @@ public void trigger_subscription_callback_upon_update_of_projection() { final Target allProjects = Queries.Targets.allOf(Project.class); final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); - final Subscription subscription = stand.subscribe(allProjects, memoizeCallback); + final Subscription subscription = stand.subscribe(allProjects); + stand.activate(subscription, memoizeCallback); assertNotNull(subscription); assertNull(memoizeCallback.newEntityState); @@ -322,7 +324,8 @@ public void allow_cancelling_subscriptions() { final Target allCustomers = Queries.Targets.allOf(Customer.class); final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); - final Subscription subscription = stand.subscribe(allCustomers, memoizeCallback); + final Subscription subscription = stand.subscribe(allCustomers); + stand.activate(subscription, memoizeCallback); assertNull(memoizeCallback.newEntityState); stand.cancel(subscription); @@ -413,7 +416,8 @@ public void onEntityStateUpdate(Any newEntityState) { callbackStates.add(customerInCallback); } }; - stand.subscribe(someCustomers, callback); + final Subscription subscription = stand.subscribe(someCustomers); + stand.activate(subscription, callback); for (Map.Entry sampleEntry : sampleCustomers.entrySet()) { final CustomerId customerId = sampleEntry.getKey(); @@ -427,7 +431,8 @@ public void onEntityStateUpdate(Any newEntityState) { private static MemoizeStandUpdateCallback subscribeWithCallback(Stand stand, Target subscriptionTarget) { final MemoizeStandUpdateCallback callback = spy(new MemoizeStandUpdateCallback()); - stand.subscribe(subscriptionTarget, callback); + final Subscription subscription = stand.subscribe(subscriptionTarget); + stand.activate(subscription, callback); assertNull(callback.newEntityState); return callback; } @@ -584,7 +589,7 @@ private static void setupExpectedFindAllBehaviour(Map sample } @SuppressWarnings("OverlyComplexAnonymousInnerClass") - private static ArgumentMatcher entityFilterMatcher(final Set projectIds) { + private static ArgumentMatcher entityFilterMatcher(final Collection projectIds) { // This argument matcher does NOT mimic the exact repository behavior. // Instead, it only matches the EntityFilters instance in case it has EntityIdFilter with ALL the expected IDs. return new ArgumentMatcher() { @@ -646,26 +651,6 @@ public boolean matches(Iterable argument) { }; } - private static EntityIdFilter idFilterForAggregate(Collection customerIds) { - final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); - for (CustomerId id : customerIds) { - idFilterBuilder - .addIds(EntityId.newBuilder() - .setId(AnyPacker.pack(id))); - } - return idFilterBuilder.build(); - } - - private static EntityIdFilter idFilterForProjection(Collection projectIds) { - final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); - for (ProjectId id : projectIds) { - idFilterBuilder - .addIds(EntityId.newBuilder() - .setId(AnyPacker.pack(id))); - } - return idFilterBuilder.build(); - } - private static void triggerMultipleUpdates(Map sampleCustomers, Stand stand) { // Trigger the aggregate state updates. for (CustomerId id : sampleCustomers.keySet()) { From 5fcdd2f40a5710112d31f79d5e2b2158b1e176c2 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 15:51:10 +0300 Subject: [PATCH 190/361] Rename ClientService to CommandService all over the project. --- ...nt_service.proto => command_service.proto} | 4 ++-- .../spine3/examples/aggregate/ClientApp.java | 10 ++++----- .../examples/aggregate/server/Server.java | 8 +++---- ...ClientService.java => CommandService.java} | 19 ++++++++-------- ...eShould.java => CommandServiceShould.java} | 6 ++--- .../test/java/org/spine3/server/Given.java | 22 +++++++++---------- .../org/spine3/server/stand/StandShould.java | 4 ++-- .../commands.proto | 6 ++--- .../customer/commands.proto | 8 +++---- .../customer/customer.proto | 4 ++-- .../customer/events.proto | 4 ++-- .../events.proto | 6 ++--- .../project.proto | 0 13 files changed, 51 insertions(+), 50 deletions(-) rename client/src/main/proto/spine/client/{client_service.proto => command_service.proto} (95%) rename server/src/main/java/org/spine3/server/{ClientService.java => CommandService.java} (88%) rename server/src/test/java/org/spine3/server/{ClientServiceShould.java => CommandServiceShould.java} (97%) rename server/src/test/proto/spine/test/{clientservice => commandservice}/commands.proto (89%) rename server/src/test/proto/spine/test/{clientservice => commandservice}/customer/commands.proto (91%) rename server/src/test/proto/spine/test/{clientservice => commandservice}/customer/customer.proto (94%) rename server/src/test/proto/spine/test/{clientservice => commandservice}/customer/events.proto (92%) rename server/src/test/proto/spine/test/{clientservice => commandservice}/events.proto (89%) rename server/src/test/proto/spine/test/{clientservice => commandservice}/project.proto (100%) diff --git a/client/src/main/proto/spine/client/client_service.proto b/client/src/main/proto/spine/client/command_service.proto similarity index 95% rename from client/src/main/proto/spine/client/client_service.proto rename to client/src/main/proto/spine/client/command_service.proto index d17e8e03585..8e288d3bd8d 100644 --- a/client/src/main/proto/spine/client/client_service.proto +++ b/client/src/main/proto/spine/client/command_service.proto @@ -26,7 +26,7 @@ package spine.client; option (type_url_prefix) = "type.spine3.org"; option java_package = "org.spine3.client.grpc"; option java_multiple_files = true; -option java_outer_classname = "ClientServiceProto"; +option java_outer_classname = "CommandServiceProto"; option java_generate_equals_and_hash = true; import "spine/annotations.proto"; @@ -35,7 +35,7 @@ import "spine/base/response.proto"; // A service for sending commands from clients. -service ClientService { +service CommandService { // Request to handle a command. rpc Post(base.Command) returns (base.Response); } diff --git a/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java b/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java index 9df51a9117c..8599736a214 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java @@ -30,7 +30,7 @@ import org.spine3.base.Identifiers; import org.spine3.base.Response; import org.spine3.client.CommandFactory; -import org.spine3.client.grpc.ClientServiceGrpc; +import org.spine3.client.grpc.CommandServiceGrpc; import org.spine3.examples.aggregate.command.AddOrderLine; import org.spine3.examples.aggregate.command.CreateOrder; import org.spine3.examples.aggregate.command.PayForOrder; @@ -66,9 +66,9 @@ public class ClientApp { private final CommandFactory commandFactory; private final ManagedChannel channel; - private final ClientServiceGrpc.ClientServiceBlockingStub blockingClient; + private final CommandServiceGrpc.CommandServiceBlockingStub blockingClient; // TODO[alex.tymchenko]: switch to SubscriptionService instead. - private final ClientServiceGrpc.ClientServiceStub nonBlockingClient; + private final CommandServiceGrpc.CommandServiceStub nonBlockingClient; private final StreamObserver observer = new StreamObserver() { @Override @@ -98,8 +98,8 @@ public ClientApp(String host, int port) { .forAddress(host, port) .usePlaintext(true) .build(); - blockingClient = ClientServiceGrpc.newBlockingStub(channel); - nonBlockingClient = ClientServiceGrpc.newStub(channel); + blockingClient = CommandServiceGrpc.newBlockingStub(channel); + nonBlockingClient = CommandServiceGrpc.newStub(channel); } private Command createOrder(OrderId orderId) { diff --git a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java index 5343bfd69db..058adfaca06 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java @@ -22,7 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spine3.server.BoundedContext; -import org.spine3.server.ClientService; +import org.spine3.server.CommandService; import org.spine3.server.event.EventSubscriber; import org.spine3.server.storage.StorageFactory; import org.spine3.server.storage.memory.InMemoryStorageFactory; @@ -58,9 +58,9 @@ public Server(StorageFactory storageFactory) { .subscribe(eventLogger); // Create a client service with this bounded context. - final ClientService clientService = ClientService.newBuilder() - .addBoundedContext(boundedContext) - .build(); + final CommandService clientService = CommandService.newBuilder() + .addBoundedContext(boundedContext) + .build(); // Create a gRPC server and schedule the client service instance for deployment. this.grpcContainer = GrpcContainer.newBuilder() diff --git a/server/src/main/java/org/spine3/server/ClientService.java b/server/src/main/java/org/spine3/server/CommandService.java similarity index 88% rename from server/src/main/java/org/spine3/server/ClientService.java rename to server/src/main/java/org/spine3/server/CommandService.java index 1ab5f73018b..2306b1cdb21 100644 --- a/server/src/main/java/org/spine3/server/ClientService.java +++ b/server/src/main/java/org/spine3/server/CommandService.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import org.spine3.base.Command; import org.spine3.base.Response; +import org.spine3.client.grpc.CommandServiceGrpc; import org.spine3.server.command.error.CommandException; import org.spine3.server.command.error.UnsupportedCommandException; import org.spine3.server.type.CommandClass; @@ -34,13 +35,13 @@ import java.util.Set; /** - * The {@code ClientService} allows client applications to post commands and + * The {@code CommandService} allows client applications to post commands and * receive updates from the application backend. * * @author Alexander Yevsyukov */ -public class ClientService - extends org.spine3.client.grpc.ClientServiceGrpc.ClientServiceImplBase { +public class CommandService + extends CommandServiceGrpc.CommandServiceImplBase { private final ImmutableMap boundedContextMap; @@ -48,7 +49,7 @@ public static Builder newBuilder() { return new Builder(); } - protected ClientService(Builder builder) { + protected CommandService(Builder builder) { this.boundedContextMap = builder.getBoundedContextMap(); } @@ -68,7 +69,7 @@ public void post(Command request, StreamObserver responseObserver) { private static void handleUnsupported(Command request, StreamObserver responseObserver) { final CommandException unsupported = new UnsupportedCommandException(request); - log().error("Unsupported command posted to ClientService", unsupported); + log().error("Unsupported command posted to CommandService", unsupported); responseObserver.onError(Statuses.invalidArgumentWithCause(unsupported)); } @@ -96,11 +97,11 @@ public ImmutableMap getBoundedContextMap() { } /** - * Builds the {@link ClientService}. + * Builds the {@link CommandService}. */ - public ClientService build() { + public CommandService build() { this.boundedContextMap = createBoundedContextMap(); - final ClientService result = new ClientService(this); + final CommandService result = new CommandService(this); return result; } @@ -125,7 +126,7 @@ private static void addBoundedContext(ImmutableMap.Builder boundedContexts = Sets.newHashSet(); private BoundedContext projectsContext; @@ -74,7 +74,7 @@ public void setUp() { boundedContexts.add(customersContext); // Expose two Bounded Contexts via a Client Service. - final ClientService.Builder builder = ClientService.newBuilder(); + final CommandService.Builder builder = CommandService.newBuilder(); for (BoundedContext context : boundedContexts) { builder.addBoundedContext(context); } diff --git a/server/src/test/java/org/spine3/server/Given.java b/server/src/test/java/org/spine3/server/Given.java index 70306ec6613..cd1c4f63cc6 100644 --- a/server/src/test/java/org/spine3/server/Given.java +++ b/server/src/test/java/org/spine3/server/Given.java @@ -32,18 +32,18 @@ import org.spine3.server.aggregate.AggregateRepository; import org.spine3.server.aggregate.Apply; import org.spine3.server.command.Assign; -import org.spine3.test.clientservice.Project; -import org.spine3.test.clientservice.ProjectId; -import org.spine3.test.clientservice.command.AddTask; -import org.spine3.test.clientservice.command.CreateProject; -import org.spine3.test.clientservice.command.StartProject; -import org.spine3.test.clientservice.customer.Customer; -import org.spine3.test.clientservice.customer.CustomerId; -import org.spine3.test.clientservice.customer.command.CreateCustomer; +import org.spine3.test.aggregate.Project; +import org.spine3.test.aggregate.ProjectId; +import org.spine3.test.aggregate.command.AddTask; +import org.spine3.test.aggregate.command.CreateProject; +import org.spine3.test.aggregate.command.StartProject; +import org.spine3.test.aggregate.event.ProjectCreated; +import org.spine3.test.aggregate.event.ProjectStarted; +import org.spine3.test.aggregate.event.TaskAdded; import org.spine3.test.clientservice.customer.event.CustomerCreated; -import org.spine3.test.clientservice.event.ProjectCreated; -import org.spine3.test.clientservice.event.ProjectStarted; -import org.spine3.test.clientservice.event.TaskAdded; +import org.spine3.test.commandservice.customer.Customer; +import org.spine3.test.commandservice.customer.CustomerId; +import org.spine3.test.commandservice.customer.command.CreateCustomer; import org.spine3.time.LocalDate; import org.spine3.time.LocalDates; import org.spine3.users.UserId; diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 7251da96332..cc4b05dcac9 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -55,8 +55,8 @@ import org.spine3.server.storage.EntityStorageRecord; import org.spine3.server.storage.StandStorage; import org.spine3.server.storage.memory.InMemoryStandStorage; -import org.spine3.test.clientservice.customer.Customer; -import org.spine3.test.clientservice.customer.CustomerId; +import org.spine3.test.commandservice.customer.Customer; +import org.spine3.test.commandservice.customer.CustomerId; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; diff --git a/server/src/test/proto/spine/test/clientservice/commands.proto b/server/src/test/proto/spine/test/commandservice/commands.proto similarity index 89% rename from server/src/test/proto/spine/test/clientservice/commands.proto rename to server/src/test/proto/spine/test/commandservice/commands.proto index b4484eefc70..e7790fce2de 100644 --- a/server/src/test/proto/spine/test/clientservice/commands.proto +++ b/server/src/test/proto/spine/test/commandservice/commands.proto @@ -22,12 +22,12 @@ syntax = "proto3"; package spine.test.clientservice; option (type_url_prefix) = "type.spine3.org"; -option java_package="org.spine3.test.clientservice.command"; -option java_outer_classname = "ClientServiceCommandsProto"; +option java_package="org.spine3.test.commandservice.command"; +option java_outer_classname = "CommandServiceCommandsProto"; option java_multiple_files = true; import "spine/annotations.proto"; -import "spine/test/clientservice/project.proto"; +import "spine/test/commandservice/project.proto"; import "spine/base/event.proto"; message CreateProject { diff --git a/server/src/test/proto/spine/test/clientservice/customer/commands.proto b/server/src/test/proto/spine/test/commandservice/customer/commands.proto similarity index 91% rename from server/src/test/proto/spine/test/clientservice/customer/commands.proto rename to server/src/test/proto/spine/test/commandservice/customer/commands.proto index 69017f302b4..5d743667d11 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/commands.proto +++ b/server/src/test/proto/spine/test/commandservice/customer/commands.proto @@ -19,15 +19,15 @@ // syntax = "proto3"; -package spine.test.clientservice.customer; +package spine.test.commandservice.customer; option (type_url_prefix) = "type.spine3.org"; -option java_package="org.spine3.test.clientservice.customer.command"; +option java_package="org.spine3.test.commandservice.customer.command"; option java_outer_classname = "CustomerCommandsProto"; option java_multiple_files = true; import "spine/annotations.proto"; -import "spine/test/clientservice/customer/customer.proto"; +import "spine/test/commandservice/customer/customer.proto"; // A request to create a new customer record. message CreateCustomer { @@ -45,4 +45,4 @@ message CreateCustomer { message ResetCustomerCounter { // This command message does not have field because it's an imperative to the system internals. // The scheduling of the command will be set in `CommandContext` of the corresponding command. -} \ No newline at end of file +} diff --git a/server/src/test/proto/spine/test/clientservice/customer/customer.proto b/server/src/test/proto/spine/test/commandservice/customer/customer.proto similarity index 94% rename from server/src/test/proto/spine/test/clientservice/customer/customer.proto rename to server/src/test/proto/spine/test/commandservice/customer/customer.proto index ce625c9e471..d3731a5ebd9 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/customer.proto +++ b/server/src/test/proto/spine/test/commandservice/customer/customer.proto @@ -19,13 +19,13 @@ // syntax = "proto3"; -package spine.test.clientservice.customer; +package spine.test.commandservice.customer; option (type_url_prefix) = "type.spine3.org"; option java_generate_equals_and_hash = true; option java_multiple_files = true; option java_outer_classname = "CustomerProto"; -option java_package = "org.spine3.test.clientservice.customer"; +option java_package = "org.spine3.test.commandservice.customer"; import "spine/annotations.proto"; import "spine/people/person_name.proto"; diff --git a/server/src/test/proto/spine/test/clientservice/customer/events.proto b/server/src/test/proto/spine/test/commandservice/customer/events.proto similarity index 92% rename from server/src/test/proto/spine/test/clientservice/customer/events.proto rename to server/src/test/proto/spine/test/commandservice/customer/events.proto index c30c70e88f8..bf7813882ba 100644 --- a/server/src/test/proto/spine/test/clientservice/customer/events.proto +++ b/server/src/test/proto/spine/test/commandservice/customer/events.proto @@ -19,7 +19,7 @@ // syntax = "proto3"; -package spine.test.clientservice.customer; +package spine.test.commandservice.customer; option (type_url_prefix) = "type.spine3.org"; option java_package="org.spine3.test.clientservice.customer.event"; @@ -27,7 +27,7 @@ option java_outer_classname = "CustomerEventsProto"; option java_multiple_files = true; import "spine/annotations.proto"; -import "spine/test/clientservice/customer/customer.proto"; +import "spine/test/commandservice/customer/customer.proto"; message CustomerCreated { CustomerId customer_id = 1; diff --git a/server/src/test/proto/spine/test/clientservice/events.proto b/server/src/test/proto/spine/test/commandservice/events.proto similarity index 89% rename from server/src/test/proto/spine/test/clientservice/events.proto rename to server/src/test/proto/spine/test/commandservice/events.proto index 2d41bbc76a1..f7bd5eea006 100644 --- a/server/src/test/proto/spine/test/clientservice/events.proto +++ b/server/src/test/proto/spine/test/commandservice/events.proto @@ -22,12 +22,12 @@ syntax = "proto3"; package spine.test.clientservice; option (type_url_prefix) = "type.spine3.org"; -option java_package="org.spine3.test.clientservice.event"; -option java_outer_classname = "ClientServiceEventsProto"; +option java_package="org.spine3.test.commandservice.event"; +option java_outer_classname = "CommandServiceEventsProto"; option java_multiple_files = true; import "spine/annotations.proto"; -import "spine/test/clientservice/project.proto"; +import "spine/test/commandservice/project.proto"; message ProjectCreated { ProjectId project_id = 1; diff --git a/server/src/test/proto/spine/test/clientservice/project.proto b/server/src/test/proto/spine/test/commandservice/project.proto similarity index 100% rename from server/src/test/proto/spine/test/clientservice/project.proto rename to server/src/test/proto/spine/test/commandservice/project.proto From ede405948b77d04a658c98d7ce7d220be600ea3f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 22 Sep 2016 15:54:23 +0300 Subject: [PATCH 191/361] Fix Target issues (use oneof properly) and use it in Stand entity -> callback matching. --- .../main/java/org/spine3/base/Queries.java | 26 ++++----- .../java/org/spine3/server/stand/Stand.java | 56 ++++++++++++------- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index d24e7111d17..74431c03fae 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -90,12 +90,14 @@ public static Target allOf(Class entityClass) { /* package */ static Target composeTarget(Class entityClass, @Nullable Set ids) { final TypeUrl type = TypeUrl.of(entityClass); - + final Target.Builder builder = Target.newBuilder(); final boolean includeAll = ids == null; - final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); + if (includeAll) { + builder.setIncludeAll(true); + } else { - if (!includeAll) { + final EntityIdFilter.Builder idFilterBuilder = EntityIdFilter.newBuilder(); for (Message rawId : ids) { final Any packedId = AnyPacker.pack(rawId); final EntityId entityId = EntityId.newBuilder() @@ -103,17 +105,15 @@ public static Target allOf(Class entityClass) { .build(); idFilterBuilder.addIds(entityId); } + final EntityIdFilter idFilter = idFilterBuilder.build(); + final EntityFilters filters = EntityFilters.newBuilder() + .setIdFilter(idFilter) + .build(); + builder.setFilters(filters); } - final EntityIdFilter idFilter = idFilterBuilder.build(); - final EntityFilters filters = EntityFilters.newBuilder() - .setIdFilter(idFilter) - .build(); - - return Target.newBuilder() - .setIncludeAll(includeAll) - .setType(type.getTypeName()) - .setFilters(filters) - .build(); + final Target result = builder.setType(type.getTypeName()) + .build(); + return result; } } } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index bc4bf6ea15d..005a2ab5d6b 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -34,6 +34,7 @@ import com.google.protobuf.Any; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; +import org.spine3.base.Identifiers; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; @@ -59,6 +60,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -580,27 +582,43 @@ private boolean isActive() { return result; } - private boolean matches(TypeUrl type, Object id, Any entityState) { + private boolean matches( + TypeUrl type, + Object id, + // entityState will be later used for more advanced filtering + @SuppressWarnings("UnusedParameters") Any entityState + ) { + final boolean result; + final boolean typeMatches = this.type.equals(type); + if (typeMatches) { + final boolean includeAll = target.getIncludeAll(); + final EntityFilters filters = target.getFilters(); + result = includeAll || matchByFilters(id, filters); + } else { + result = false; + } - return typeMatches; - // TODO[alex.tymchenko]: complete the implementation. -// final boolean includeAll = target.getIncludeAll(); -// -// if(typeMatches && includeAll) { -// return true; -// } -// -// final EntityIdFilter givenIdFilter = target.getFilters() -// .getIdFilter(); -// final boolean idFilterSet = !EntityIdFilter.getDefaultInstance() -// .equals(givenIdFilter); -// -// if (idFilterSet) { -// -// } -// -// return false; + return result; + } + + private static boolean matchByFilters(Object id, EntityFilters filters) { + final boolean result; + final EntityIdFilter givenIdFilter = filters + .getIdFilter(); + final boolean idFilterSet = !EntityIdFilter.getDefaultInstance() + .equals(givenIdFilter); + if (idFilterSet) { + final Any idAsAny = Identifiers.idToAny(id); + final EntityId givenEntityId = EntityId.newBuilder() + .setId(idAsAny) + .build(); + final List idsList = givenIdFilter.getIdsList(); + result = idsList.contains(givenEntityId); + } else { + result = false; + } + return result; } @Override From 96df5a0d32a0a731dcc5708beb6502162909f9f7 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 16:10:54 +0300 Subject: [PATCH 192/361] Add senseful invalid type url string. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 7251da96332..ee9165dbf2d 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -558,7 +558,7 @@ public void handle_mistakes_in_query_silently() { stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); // FieldMask with invalid type URLs. - final String[] paths = {"blablabla", Project.getDescriptor().getFields().get(2).getFullName()}; + final String[] paths = {"invalid_type_url_example", Project.getDescriptor().getFields().get(2).getFullName()}; final Query customerQuery = Queries.readAll(Customer.class, paths); From 1989a93d6ec268d98574612857ea6462d0600a70 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 17:00:56 +0300 Subject: [PATCH 193/361] Finish migration. --- server/src/test/java/org/spine3/server/Given.java | 2 +- .../src/test/java/org/spine3/server/QueryServiceShould.java | 2 +- .../src/test/proto/spine/test/commandservice/commands.proto | 2 +- .../proto/spine/test/commandservice/customer/events.proto | 2 +- server/src/test/proto/spine/test/commandservice/events.proto | 2 +- server/src/test/proto/spine/test/commandservice/project.proto | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/spine3/server/Given.java b/server/src/test/java/org/spine3/server/Given.java index cd1c4f63cc6..8568c00c14e 100644 --- a/server/src/test/java/org/spine3/server/Given.java +++ b/server/src/test/java/org/spine3/server/Given.java @@ -40,10 +40,10 @@ import org.spine3.test.aggregate.event.ProjectCreated; import org.spine3.test.aggregate.event.ProjectStarted; import org.spine3.test.aggregate.event.TaskAdded; -import org.spine3.test.clientservice.customer.event.CustomerCreated; import org.spine3.test.commandservice.customer.Customer; import org.spine3.test.commandservice.customer.CustomerId; import org.spine3.test.commandservice.customer.command.CreateCustomer; +import org.spine3.test.commandservice.customer.event.CustomerCreated; import org.spine3.time.LocalDate; import org.spine3.time.LocalDates; import org.spine3.users.UserId; diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index 8dd9844c6a4..884260003fc 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -35,7 +35,7 @@ import org.spine3.server.projection.ProjectionRepository; import org.spine3.server.stand.Stand; import org.spine3.test.bc.event.ProjectCreated; -import org.spine3.test.clientservice.ProjectId; +import org.spine3.test.commandservice.ProjectId; import org.spine3.test.projection.Project; import org.spine3.testdata.TestStandFactory; diff --git a/server/src/test/proto/spine/test/commandservice/commands.proto b/server/src/test/proto/spine/test/commandservice/commands.proto index e7790fce2de..e5bf5c25122 100644 --- a/server/src/test/proto/spine/test/commandservice/commands.proto +++ b/server/src/test/proto/spine/test/commandservice/commands.proto @@ -19,7 +19,7 @@ // syntax = "proto3"; -package spine.test.clientservice; +package spine.test.commandservice; option (type_url_prefix) = "type.spine3.org"; option java_package="org.spine3.test.commandservice.command"; diff --git a/server/src/test/proto/spine/test/commandservice/customer/events.proto b/server/src/test/proto/spine/test/commandservice/customer/events.proto index bf7813882ba..8dc1a7dbc82 100644 --- a/server/src/test/proto/spine/test/commandservice/customer/events.proto +++ b/server/src/test/proto/spine/test/commandservice/customer/events.proto @@ -22,7 +22,7 @@ syntax = "proto3"; package spine.test.commandservice.customer; option (type_url_prefix) = "type.spine3.org"; -option java_package="org.spine3.test.clientservice.customer.event"; +option java_package="org.spine3.test.commandservice.customer.event"; option java_outer_classname = "CustomerEventsProto"; option java_multiple_files = true; diff --git a/server/src/test/proto/spine/test/commandservice/events.proto b/server/src/test/proto/spine/test/commandservice/events.proto index f7bd5eea006..3e567289fdb 100644 --- a/server/src/test/proto/spine/test/commandservice/events.proto +++ b/server/src/test/proto/spine/test/commandservice/events.proto @@ -19,7 +19,7 @@ // syntax = "proto3"; -package spine.test.clientservice; +package spine.test.commandservice; option (type_url_prefix) = "type.spine3.org"; option java_package="org.spine3.test.commandservice.event"; diff --git a/server/src/test/proto/spine/test/commandservice/project.proto b/server/src/test/proto/spine/test/commandservice/project.proto index 608d36631e9..858ef60128b 100644 --- a/server/src/test/proto/spine/test/commandservice/project.proto +++ b/server/src/test/proto/spine/test/commandservice/project.proto @@ -19,10 +19,10 @@ // syntax = "proto3"; -package spine.test.clientservice; +package spine.test.commandservice; option (type_url_prefix) = "type.spine3.org"; -option java_package="org.spine3.test.clientservice"; +option java_package="org.spine3.test.commandservice"; option java_multiple_files = true; import "spine/annotations.proto"; From 8555920de7cf2d35a43e851c238730c75c801857 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 17:06:19 +0300 Subject: [PATCH 194/361] Correct comment. --- .../src/test/java/org/spine3/server/CommandServiceShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/CommandServiceShould.java b/server/src/test/java/org/spine3/server/CommandServiceShould.java index 3988749165a..285b97ce095 100644 --- a/server/src/test/java/org/spine3/server/CommandServiceShould.java +++ b/server/src/test/java/org/spine3/server/CommandServiceShould.java @@ -73,7 +73,7 @@ public void setUp() { customersContext.register(customerRepo); boundedContexts.add(customersContext); - // Expose two Bounded Contexts via a Client Service. + // Expose two Bounded Contexts via an instance of {@code CommandService}. final CommandService.Builder builder = CommandService.newBuilder(); for (BoundedContext context : boundedContexts) { builder.addBoundedContext(context); From c3c046d0a7d33355c3cde5d6d20e51603af607df Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 17:41:16 +0300 Subject: [PATCH 195/361] Fix concurrency issue in the StandFunnel test for executors. --- .../java/org/spine3/server/stand/Given.java | 6 +++++ .../server/stand/StandFunnelShould.java | 23 +++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 2184ac0511d..0dcbd1ba614 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -169,6 +169,12 @@ public void handle(ProjectCreated event, EventContext context) { .build(); } + /*package*/ static BoundedContext boundedContext(Stand stand, Executor standFunnelExecutor) { + return boundedContextBuilder(stand) + .setStandFunnelExecutor(standFunnelExecutor) + .build(); + } + private static BoundedContext.Builder boundedContextBuilder(Stand stand) { return BoundedContext.newBuilder() .setStand(stand) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index d952c30ea84..3943566c9ac 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -21,6 +21,7 @@ */ package org.spine3.server.stand; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import io.netty.util.internal.ConcurrentSet; import org.junit.Assert; @@ -47,7 +48,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -185,9 +185,12 @@ private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAct checkNotNull(dispatchActions); final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand, - isConcurrent ? - Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); + final Executor executor = isConcurrent ? + Executors.newFixedThreadPool(Given.THREADS_COUNT_IN_POOL_EXECUTOR) : + MoreExecutors.directExecutor(); + + final BoundedContext boundedContext = spy(Given.boundedContext(stand, executor)); + for (BoundedContextAction dispatchAction : dispatchActions) { dispatchAction.perform(boundedContext); @@ -197,19 +200,15 @@ private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAct verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); if (isConcurrent) { - await(Given.AWAIT_SECONDS); + try { + ((ExecutorService) executor).awaitTermination(Given.SEVERAL, TimeUnit.SECONDS); + } catch (InterruptedException ignore) { + } } verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); } - private static void await(int seconds) { - try { - Thread.sleep(seconds); - } catch (InterruptedException ignore) { - } - } - private static BoundedContextAction aggregateRepositoryDispatch() { return new BoundedContextAction() { @Override From c196ec5ed360f701951affcf46990fa4dc02d42d Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 17:50:29 +0300 Subject: [PATCH 196/361] Fix throwable naming. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 3943566c9ac..183b5a47c73 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -202,7 +202,7 @@ private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAct if (isConcurrent) { try { ((ExecutorService) executor).awaitTermination(Given.SEVERAL, TimeUnit.SECONDS); - } catch (InterruptedException ignore) { + } catch (InterruptedException ignored) { } } From 3ddc1632454f05ae9e35456400f9f012e399b987 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 17:41:16 +0300 Subject: [PATCH 197/361] Fix concurrency issue in the StandFunnel test for executors. --- .../java/org/spine3/server/stand/Given.java | 6 +++++ .../server/stand/StandFunnelShould.java | 23 +++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/Given.java b/server/src/test/java/org/spine3/server/stand/Given.java index 2184ac0511d..0dcbd1ba614 100644 --- a/server/src/test/java/org/spine3/server/stand/Given.java +++ b/server/src/test/java/org/spine3/server/stand/Given.java @@ -169,6 +169,12 @@ public void handle(ProjectCreated event, EventContext context) { .build(); } + /*package*/ static BoundedContext boundedContext(Stand stand, Executor standFunnelExecutor) { + return boundedContextBuilder(stand) + .setStandFunnelExecutor(standFunnelExecutor) + .build(); + } + private static BoundedContext.Builder boundedContextBuilder(Stand stand) { return BoundedContext.newBuilder() .setStand(stand) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index d952c30ea84..3943566c9ac 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -21,6 +21,7 @@ */ package org.spine3.server.stand; +import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; import io.netty.util.internal.ConcurrentSet; import org.junit.Assert; @@ -47,7 +48,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; /** * @author Alex Tymchenko @@ -185,9 +185,12 @@ private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAct checkNotNull(dispatchActions); final Stand stand = mock(Stand.class); - final BoundedContext boundedContext = spy(Given.boundedContext(stand, - isConcurrent ? - Given.THREADS_COUNT_IN_POOL_EXECUTOR : 0)); + final Executor executor = isConcurrent ? + Executors.newFixedThreadPool(Given.THREADS_COUNT_IN_POOL_EXECUTOR) : + MoreExecutors.directExecutor(); + + final BoundedContext boundedContext = spy(Given.boundedContext(stand, executor)); + for (BoundedContextAction dispatchAction : dispatchActions) { dispatchAction.perform(boundedContext); @@ -197,19 +200,15 @@ private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAct verify(boundedContext, times(dispatchActions.length)).getStandFunnel(); if (isConcurrent) { - await(Given.AWAIT_SECONDS); + try { + ((ExecutorService) executor).awaitTermination(Given.SEVERAL, TimeUnit.SECONDS); + } catch (InterruptedException ignore) { + } } verify(stand, times(dispatchActions.length)).update(ArgumentMatchers.any(), any(Any.class)); } - private static void await(int seconds) { - try { - Thread.sleep(seconds); - } catch (InterruptedException ignore) { - } - } - private static BoundedContextAction aggregateRepositoryDispatch() { return new BoundedContextAction() { @Override From a7ed2f59421a2d1140b6ae9e33c4c44ea53eee3c Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 17:50:29 +0300 Subject: [PATCH 198/361] Fix throwable naming. --- .../test/java/org/spine3/server/stand/StandFunnelShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 3943566c9ac..183b5a47c73 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -202,7 +202,7 @@ private static void checkUpdatesDelivery(boolean isConcurrent, BoundedContextAct if (isConcurrent) { try { ((ExecutorService) executor).awaitTermination(Given.SEVERAL, TimeUnit.SECONDS); - } catch (InterruptedException ignore) { + } catch (InterruptedException ignored) { } } From 9e605569a3be4394f28ab412ffe996ce52c7bfbc Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 18:08:27 +0300 Subject: [PATCH 199/361] Create class for subscription service tests. --- .../server/SubscriptionServiceShould.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 server/src/test/java/org/spine3/server/SubscriptionServiceShould.java diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java new file mode 100644 index 00000000000..2ad84e558db --- /dev/null +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server; + +/** + * @author Dmytro Dashenkov + */ +public class SubscriptionServiceShould { + + +} From 521145d0e95dd46a94e2a35d93ba37f8e7d655cc Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 18:38:35 +0300 Subject: [PATCH 200/361] Add creation tests for subscription service. --- .../server/SubscriptionServiceShould.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index 2ad84e558db..c6b39476b96 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -20,10 +20,81 @@ package org.spine3.server; +import org.junit.Test; +import org.spine3.server.stand.Stand; +import org.spine3.server.storage.memory.InMemoryStandStorage; +import org.spine3.server.storage.memory.InMemoryStorageFactory; + +import static org.junit.Assert.assertNotNull; + /** * @author Dmytro Dashenkov */ public class SubscriptionServiceShould { + /* + * Creation tests + * -------------- + */ + + @Test + public void initialize_properly_with_one_bounded_context() { + final BoundedContext singleBoundedContext = newBoundedContext("Single"); + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(singleBoundedContext) + .build(); + + assertNotNull(subscriptionService); + } + + @Test + public void initialize_properly_with_several_bounded_contexts() { + final BoundedContext firstBoundedContext = newBoundedContext("First"); + final BoundedContext secondBoundedContext = newBoundedContext("Second"); + final BoundedContext thirdBoundedContext = newBoundedContext("Third"); + + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(firstBoundedContext) + .addBoundedContext(secondBoundedContext) + .addBoundedContext(thirdBoundedContext) + .build(); + + assertNotNull(subscriptionService); + } + + @Test + public void be_able_to_remove_bounded_context_from_builder() { + final BoundedContext firstBoundedContext = newBoundedContext("Removed"); + final BoundedContext secondBoundedContext = newBoundedContext("Also removed"); + final BoundedContext thirdBoundedContext = newBoundedContext("The one to stay"); + + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(firstBoundedContext) + .addBoundedContext(secondBoundedContext) + .addBoundedContext(thirdBoundedContext) + .removeBoundedContext(secondBoundedContext) + .removeBoundedContext(firstBoundedContext) + .build(); + + assertNotNull(subscriptionService); + } + + @Test(expected = IllegalStateException.class) + public void fail_to_initialize_from_empty_builder() { + SubscriptionService.newBuilder().build(); + } + + private static BoundedContext newBoundedContext(String name) { + final Stand stand = Stand.newBuilder().setStorage(InMemoryStandStorage.newBuilder().build()).build(); + + return BoundedContext.newBuilder() + .setStand(stand) + .setName(name) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .build(); + } } From ecc76ac9343280396478ab8c225878bc0e6ed8e5 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Thu, 22 Sep 2016 19:19:27 +0300 Subject: [PATCH 201/361] Add subscription test. --- .../server/SubscriptionServiceShould.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index c6b39476b96..43d7724218f 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -20,12 +20,19 @@ package org.spine3.server; +import io.grpc.stub.StreamObserver; import org.junit.Test; +import org.spine3.client.Subscription; +import org.spine3.client.Target; +import org.spine3.client.Topic; import org.spine3.server.stand.Stand; import org.spine3.server.storage.memory.InMemoryStandStorage; import org.spine3.server.storage.memory.InMemoryStorageFactory; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** * @author Dmytro Dashenkov @@ -87,6 +94,48 @@ public void fail_to_initialize_from_empty_builder() { SubscriptionService.newBuilder().build(); } + /* + * Subscription tests + * ------------------ + */ + + @Test + public void subscribe_to_topic() { + final BoundedContext boundedContext = setupBoundedContextForProjectionRepo(); + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + + final String type = boundedContext.getStand() + .getAvailableTypes() + .iterator() + .next() + .getTypeName(); + + final Target target = Target.newBuilder() + .setType(type) + .build(); + + final Topic topic = Topic.newBuilder() + .setTarget(target) + .build(); + + final MemoiseStreamObserver observer = new MemoiseStreamObserver<>(); + + subscriptionService.subscribe(topic, observer); + + assertNotNull(observer.streamFlowValue); + assertTrue(observer.streamFlowValue.isInitialized()); + assertEquals(observer.streamFlowValue.getType(), type); + + + assertNull(observer.throwable); + assertTrue(observer.isCompleted); + } + + + private static BoundedContext newBoundedContext(String name) { final Stand stand = Stand.newBuilder().setStorage(InMemoryStandStorage.newBuilder().build()).build(); @@ -97,4 +146,41 @@ private static BoundedContext newBoundedContext(String name) { .build(); } + + private static BoundedContext setupBoundedContextForProjectionRepo() { + final Stand stand = Stand.newBuilder() + .setStorage(InMemoryStandStorage.newBuilder().build()) + .build(); + + final BoundedContext boundedContext = BoundedContext.newBuilder() + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .build(); + + stand.registerTypeSupplier(new Given.ProjectAggregateRepository(boundedContext)); + + return boundedContext; + } + + private static class MemoiseStreamObserver implements StreamObserver { + + private T streamFlowValue; + private Throwable throwable; + private boolean isCompleted; + + @Override + public void onNext(T value) { + this.streamFlowValue = value; + } + + @Override + public void onError(Throwable t) { + this.throwable = t; + } + + @Override + public void onCompleted() { + this.isCompleted = true; + } + } } From b9971fcec704cece812e950199fb29f5794fe241 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 23 Sep 2016 13:20:51 +0300 Subject: [PATCH 202/361] Add activation test. --- .../spine3/server/SubscriptionService.java | 3 +- .../server/SubscriptionServiceShould.java | 74 +++++++++++++++++-- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index f9dd8b69e91..3c77acdae54 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -97,7 +97,8 @@ public void onEntityStateUpdate(Any newEntityState) { final SubscriptionUpdate update = SubscriptionUpdate.newBuilder() .setSubscription(subscription) .setResponse(Responses.ok()) - .setUpdates(0, newEntityState) + //.setUpdates(0, newEntityState)// TODO:23-09-16:dmytro.dashenkov: resolve. + .addUpdates(newEntityState) .build(); responseObserver.onNext(update); } diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index 43d7724218f..78b6b688d2f 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -20,14 +20,20 @@ package org.spine3.server; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; import org.spine3.client.Subscription; +import org.spine3.client.SubscriptionUpdate; import org.spine3.client.Target; import org.spine3.client.Topic; +import org.spine3.protobuf.AnyPacker; import org.spine3.server.stand.Stand; import org.spine3.server.storage.memory.InMemoryStandStorage; import org.spine3.server.storage.memory.InMemoryStorageFactory; +import org.spine3.test.aggregate.Project; +import org.spine3.test.aggregate.ProjectId; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -101,7 +107,7 @@ public void fail_to_initialize_from_empty_builder() { @Test public void subscribe_to_topic() { - final BoundedContext boundedContext = setupBoundedContextForProjectionRepo(); + final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); final SubscriptionService subscriptionService = SubscriptionService.newBuilder() .addBoundedContext(boundedContext) @@ -121,7 +127,7 @@ public void subscribe_to_topic() { .setTarget(target) .build(); - final MemoiseStreamObserver observer = new MemoiseStreamObserver<>(); + final MemoizeStreamObserver observer = new MemoizeStreamObserver<>(); subscriptionService.subscribe(topic, observer); @@ -134,6 +140,48 @@ public void subscribe_to_topic() { assertTrue(observer.isCompleted); } + @Test + public void activate_subscription() { + final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + + final String type = boundedContext.getStand() + .getAvailableTypes() + .iterator() + .next() + .getTypeName(); + + final Target target = Target.newBuilder() + .setType(type) + .setIncludeAll(true) + .build(); + + final Topic topic = Topic.newBuilder() + .setTarget(target) + .build(); + + final MemoizeStreamObserver subscriptionObserver = new MemoizeStreamObserver<>(); + + subscriptionService.subscribe(topic, subscriptionObserver); + + subscriptionObserver.checkFields(); + assertTrue(subscriptionObserver.streamFlowValue.isInitialized()); + assertEquals(subscriptionObserver.streamFlowValue.getType(), type); + + + final MemoizeStreamObserver activationObserver = new MemoizeStreamObserver<>(); + subscriptionService.activate(subscriptionObserver.streamFlowValue, activationObserver); + + final ProjectId projectId = ProjectId.newBuilder().setId("some-id").build(); + final Message projectState = Project.newBuilder().setId(projectId).build(); + boundedContext.getStand().update(projectId, AnyPacker.pack(projectState)); + + // isCompleted set to false since we don't expect activationObserver::onCompleted to be called. + activationObserver.checkFields(false); + } private static BoundedContext newBoundedContext(String name) { @@ -147,22 +195,23 @@ private static BoundedContext newBoundedContext(String name) { } - private static BoundedContext setupBoundedContextForProjectionRepo() { + private static BoundedContext setupBoundedContextForAggregateRepo() { final Stand stand = Stand.newBuilder() .setStorage(InMemoryStandStorage.newBuilder().build()) + .setCallbackExecutor(MoreExecutors.directExecutor()) .build(); final BoundedContext boundedContext = BoundedContext.newBuilder() - .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .build(); + .setStand(stand) + .setStorageFactory(InMemoryStorageFactory.getInstance()) + .build(); stand.registerTypeSupplier(new Given.ProjectAggregateRepository(boundedContext)); return boundedContext; } - private static class MemoiseStreamObserver implements StreamObserver { + private static class MemoizeStreamObserver implements StreamObserver { private T streamFlowValue; private Throwable throwable; @@ -182,5 +231,16 @@ public void onError(Throwable t) { public void onCompleted() { this.isCompleted = true; } + + private void checkFields() { + checkFields(true); + } + + private void checkFields(boolean isCompleted) { + assertNotNull(streamFlowValue); + assertNull(throwable); + assertEquals(this.isCompleted, isCompleted); + } + } } From 128f50ad63f2234a42fe0a17d5921cb1e170c0de Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 23 Sep 2016 13:37:03 +0300 Subject: [PATCH 203/361] Add cancel subscription test. --- .../server/SubscriptionServiceShould.java | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index 78b6b688d2f..c31a02fe85a 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -24,6 +24,7 @@ import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; +import org.spine3.base.Response; import org.spine3.client.Subscription; import org.spine3.client.SubscriptionUpdate; import org.spine3.client.Target; @@ -39,6 +40,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** * @author Dmytro Dashenkov @@ -163,18 +168,16 @@ public void activate_subscription() { .setTarget(target) .build(); + // Subscribe on the topic final MemoizeStreamObserver subscriptionObserver = new MemoizeStreamObserver<>(); - subscriptionService.subscribe(topic, subscriptionObserver); - subscriptionObserver.checkFields(); - assertTrue(subscriptionObserver.streamFlowValue.isInitialized()); - assertEquals(subscriptionObserver.streamFlowValue.getType(), type); - + // Activate subscription final MemoizeStreamObserver activationObserver = new MemoizeStreamObserver<>(); subscriptionService.activate(subscriptionObserver.streamFlowValue, activationObserver); + // Post update to Stand directly final ProjectId projectId = ProjectId.newBuilder().setId("some-id").build(); final Message projectState = Project.newBuilder().setId(projectId).build(); boundedContext.getStand().update(projectId, AnyPacker.pack(projectState)); @@ -183,6 +186,48 @@ public void activate_subscription() { activationObserver.checkFields(false); } + @Test + public void cancel_subscription_on_topic() { + final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + + final String type = boundedContext.getStand() + .getAvailableTypes() + .iterator() + .next() + .getTypeName(); + + final Target target = Target.newBuilder() + .setType(type) + .build(); + + final Topic topic = Topic.newBuilder() + .setTarget(target) + .build(); + + // Subscribe + final MemoizeStreamObserver subscribeObserver = new MemoizeStreamObserver<>(); + subscriptionService.subscribe(topic, subscribeObserver); + + // Activate subscription + final MemoizeStreamObserver activateSubscription = spy(new MemoizeStreamObserver()); + subscriptionService.activate(subscribeObserver.streamFlowValue, activateSubscription); + + // Cancel subscription + subscriptionService.cancel(subscribeObserver.streamFlowValue, new MemoizeStreamObserver()); + + // Post update to Stand + final ProjectId projectId = ProjectId.newBuilder().setId("some-other-id").build(); + final Message projectState = Project.newBuilder().setId(projectId).build(); + boundedContext.getStand().update(projectId, AnyPacker.pack(projectState)); + + // The update must not be handled by the observer + verify(activateSubscription, never()).onNext(any(SubscriptionUpdate.class)); + verify(activateSubscription, never()).onCompleted(); + } private static BoundedContext newBoundedContext(String name) { final Stand stand = Stand.newBuilder().setStorage(InMemoryStandStorage.newBuilder().build()).build(); From c8abf39c6eda18b06d4207b67761b4c74c9e5c21 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 23 Sep 2016 14:11:27 +0300 Subject: [PATCH 204/361] Resolve 'setUpdates' issue. --- server/src/main/java/org/spine3/server/SubscriptionService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index 3c77acdae54..9494d174837 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -97,7 +97,6 @@ public void onEntityStateUpdate(Any newEntityState) { final SubscriptionUpdate update = SubscriptionUpdate.newBuilder() .setSubscription(subscription) .setResponse(Responses.ok()) - //.setUpdates(0, newEntityState)// TODO:23-09-16:dmytro.dashenkov: resolve. .addUpdates(newEntityState) .build(); responseObserver.onNext(update); From bee4cf4e08d28d9e13432a7717a0e4a8f8763d48 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 23 Sep 2016 18:15:21 +0300 Subject: [PATCH 205/361] Correct some naming and builder check issues. --- .../spine3/server/SubscriptionService.java | 7 ++ .../server/SubscriptionServiceShould.java | 82 ++++++++++--------- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index 9494d174837..6c86b37f0d9 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -22,6 +22,7 @@ package org.spine3.server; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -163,6 +164,12 @@ public ImmutableMap getBoundedContextMap() { return typeToContextMap; } + + @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection returned is immutable + public ImmutableList getBoundedContexts() { + return ImmutableList.copyOf(boundedContexts); + } + /** * Builds the {@link SubscriptionService}. * diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index c31a02fe85a..a5a05729f67 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -24,6 +24,7 @@ import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; +import org.spine3.base.Queries; import org.spine3.base.Response; import org.spine3.client.Subscription; import org.spine3.client.SubscriptionUpdate; @@ -36,7 +37,10 @@ import org.spine3.test.aggregate.Project; import org.spine3.test.aggregate.ProjectId; +import java.util.List; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -44,6 +48,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.spine3.test.Verify.assertSize; /** * @author Dmytro Dashenkov @@ -59,11 +64,15 @@ public class SubscriptionServiceShould { public void initialize_properly_with_one_bounded_context() { final BoundedContext singleBoundedContext = newBoundedContext("Single"); - final SubscriptionService subscriptionService = SubscriptionService.newBuilder() - .addBoundedContext(singleBoundedContext) - .build(); + final SubscriptionService.Builder builder = SubscriptionService.newBuilder() + .addBoundedContext(singleBoundedContext); + final SubscriptionService subscriptionService = builder.build(); assertNotNull(subscriptionService); + + final List boundedContexs = builder.getBoundedContexts(); + assertSize(1, boundedContexs); + assertTrue(boundedContexs.contains(singleBoundedContext)); } @Test @@ -73,13 +82,19 @@ public void initialize_properly_with_several_bounded_contexts() { final BoundedContext thirdBoundedContext = newBoundedContext("Third"); - final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + final SubscriptionService.Builder builder = SubscriptionService.newBuilder() .addBoundedContext(firstBoundedContext) .addBoundedContext(secondBoundedContext) - .addBoundedContext(thirdBoundedContext) - .build(); + .addBoundedContext(thirdBoundedContext); - assertNotNull(subscriptionService); + final SubscriptionService service = builder.build(); + assertNotNull(service); + + final List boundedContexts = builder.getBoundedContexts(); + assertSize(3, boundedContexts); + assertTrue(boundedContexts.contains(firstBoundedContext)); + assertTrue(boundedContexts.contains(secondBoundedContext)); + assertTrue(boundedContexts.contains(thirdBoundedContext)); } @Test @@ -89,15 +104,21 @@ public void be_able_to_remove_bounded_context_from_builder() { final BoundedContext thirdBoundedContext = newBoundedContext("The one to stay"); - final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + final SubscriptionService.Builder builder = SubscriptionService.newBuilder() .addBoundedContext(firstBoundedContext) .addBoundedContext(secondBoundedContext) .addBoundedContext(thirdBoundedContext) .removeBoundedContext(secondBoundedContext) - .removeBoundedContext(firstBoundedContext) - .build(); + .removeBoundedContext(firstBoundedContext); + final SubscriptionService subscriptionService = builder.build(); assertNotNull(subscriptionService); + + final List boundedContexts = builder.getBoundedContexts(); + assertSize(1, boundedContexts); + assertFalse(boundedContexts.contains(firstBoundedContext)); + assertFalse(boundedContexts.contains(secondBoundedContext)); + assertTrue(boundedContexts.contains(thirdBoundedContext)); } @Test(expected = IllegalStateException.class) @@ -124,9 +145,9 @@ public void subscribe_to_topic() { .next() .getTypeName(); - final Target target = Target.newBuilder() - .setType(type) - .build(); + final Target target = getProjectQueryTarget(); + + assertEquals(type, target.getType()); final Topic topic = Topic.newBuilder() .setTarget(target) @@ -153,16 +174,7 @@ public void activate_subscription() { .addBoundedContext(boundedContext) .build(); - final String type = boundedContext.getStand() - .getAvailableTypes() - .iterator() - .next() - .getTypeName(); - - final Target target = Target.newBuilder() - .setType(type) - .setIncludeAll(true) - .build(); + final Target target = getProjectQueryTarget(); final Topic topic = Topic.newBuilder() .setTarget(target) @@ -171,7 +183,7 @@ public void activate_subscription() { // Subscribe on the topic final MemoizeStreamObserver subscriptionObserver = new MemoizeStreamObserver<>(); subscriptionService.subscribe(topic, subscriptionObserver); - subscriptionObserver.checkFields(); + subscriptionObserver.verifyState(); // Activate subscription final MemoizeStreamObserver activationObserver = new MemoizeStreamObserver<>(); @@ -183,7 +195,7 @@ public void activate_subscription() { boundedContext.getStand().update(projectId, AnyPacker.pack(projectState)); // isCompleted set to false since we don't expect activationObserver::onCompleted to be called. - activationObserver.checkFields(false); + activationObserver.verifyState(false); } @Test @@ -194,15 +206,7 @@ public void cancel_subscription_on_topic() { .addBoundedContext(boundedContext) .build(); - final String type = boundedContext.getStand() - .getAvailableTypes() - .iterator() - .next() - .getTypeName(); - - final Target target = Target.newBuilder() - .setType(type) - .build(); + final Target target = getProjectQueryTarget(); final Topic topic = Topic.newBuilder() .setTarget(target) @@ -256,6 +260,10 @@ private static BoundedContext setupBoundedContextForAggregateRepo() { return boundedContext; } + private static Target getProjectQueryTarget() { + return Queries.Targets.allOf(Project.class); + } + private static class MemoizeStreamObserver implements StreamObserver { private T streamFlowValue; @@ -277,11 +285,11 @@ public void onCompleted() { this.isCompleted = true; } - private void checkFields() { - checkFields(true); + private void verifyState() { + verifyState(true); } - private void checkFields(boolean isCompleted) { + private void verifyState(boolean isCompleted) { assertNotNull(streamFlowValue); assertNull(throwable); assertEquals(this.isCompleted, isCompleted); From f0047a6470099923db824a9d9e867c3760d1b659 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 23 Sep 2016 18:28:36 +0300 Subject: [PATCH 206/361] Move bounded context creation into TestBoundedContextFactory. --- .../server/SubscriptionServiceShould.java | 43 ++++++------------- .../testdata/TestBoundedContextFactory.java | 9 ++++ 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index a5a05729f67..e009de810a3 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -20,7 +20,6 @@ package org.spine3.server; -import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.junit.Test; @@ -32,8 +31,6 @@ import org.spine3.client.Topic; import org.spine3.protobuf.AnyPacker; import org.spine3.server.stand.Stand; -import org.spine3.server.storage.memory.InMemoryStandStorage; -import org.spine3.server.storage.memory.InMemoryStorageFactory; import org.spine3.test.aggregate.Project; import org.spine3.test.aggregate.ProjectId; @@ -49,6 +46,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.spine3.test.Verify.assertSize; +import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; /** * @author Dmytro Dashenkov @@ -62,7 +60,7 @@ public class SubscriptionServiceShould { @Test public void initialize_properly_with_one_bounded_context() { - final BoundedContext singleBoundedContext = newBoundedContext("Single"); + final BoundedContext singleBoundedContext = newBoundedContext("Single", newSimpleStand()); final SubscriptionService.Builder builder = SubscriptionService.newBuilder() .addBoundedContext(singleBoundedContext); @@ -77,9 +75,9 @@ public void initialize_properly_with_one_bounded_context() { @Test public void initialize_properly_with_several_bounded_contexts() { - final BoundedContext firstBoundedContext = newBoundedContext("First"); - final BoundedContext secondBoundedContext = newBoundedContext("Second"); - final BoundedContext thirdBoundedContext = newBoundedContext("Third"); + final BoundedContext firstBoundedContext = newBoundedContext("First", newSimpleStand()); + final BoundedContext secondBoundedContext = newBoundedContext("Second", newSimpleStand()); + final BoundedContext thirdBoundedContext = newBoundedContext("Third", newSimpleStand()); final SubscriptionService.Builder builder = SubscriptionService.newBuilder() @@ -99,9 +97,9 @@ public void initialize_properly_with_several_bounded_contexts() { @Test public void be_able_to_remove_bounded_context_from_builder() { - final BoundedContext firstBoundedContext = newBoundedContext("Removed"); - final BoundedContext secondBoundedContext = newBoundedContext("Also removed"); - final BoundedContext thirdBoundedContext = newBoundedContext("The one to stay"); + final BoundedContext firstBoundedContext = newBoundedContext("Removed", newSimpleStand()); + final BoundedContext secondBoundedContext = newBoundedContext("Also removed", newSimpleStand()); + final BoundedContext thirdBoundedContext = newBoundedContext("The one to stay", newSimpleStand()); final SubscriptionService.Builder builder = SubscriptionService.newBuilder() @@ -233,27 +231,10 @@ public void cancel_subscription_on_topic() { verify(activateSubscription, never()).onCompleted(); } - private static BoundedContext newBoundedContext(String name) { - final Stand stand = Stand.newBuilder().setStorage(InMemoryStandStorage.newBuilder().build()).build(); - - return BoundedContext.newBuilder() - .setStand(stand) - .setName(name) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .build(); - - } - private static BoundedContext setupBoundedContextForAggregateRepo() { - final Stand stand = Stand.newBuilder() - .setStorage(InMemoryStandStorage.newBuilder().build()) - .setCallbackExecutor(MoreExecutors.directExecutor()) - .build(); + final Stand stand = Stand.newBuilder().build(); - final BoundedContext boundedContext = BoundedContext.newBuilder() - .setStand(stand) - .setStorageFactory(InMemoryStorageFactory.getInstance()) - .build(); + final BoundedContext boundedContext = newBoundedContext(stand); stand.registerTypeSupplier(new Given.ProjectAggregateRepository(boundedContext)); @@ -264,6 +245,10 @@ private static Target getProjectQueryTarget() { return Queries.Targets.allOf(Project.class); } + private static Stand newSimpleStand() { + return Stand.newBuilder().build(); + } + private static class MemoizeStreamObserver implements StreamObserver { private T streamFlowValue; diff --git a/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java b/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java index 33b21acd2e8..03272f18dba 100644 --- a/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestBoundedContextFactory.java @@ -85,5 +85,14 @@ public static BoundedContext newBoundedContext(CommandBus commandBus, EventBus e return builder.build(); } + public static BoundedContext newBoundedContext(String name, Stand stand) { + final BoundedContext.Builder builder = BoundedContext.newBuilder() + .setStand(stand) + .setName(name) + .setStorageFactory(FACTORY); + + return builder.build(); + } + private TestBoundedContextFactory() {} } From 8854aa0b7a06d9612613471a76a47892309b16bc Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 23 Sep 2016 17:44:35 +0300 Subject: [PATCH 207/361] Male Stand AutoClosable. --- .../main/java/org/spine3/server/BoundedContext.java | 3 ++- .../src/main/java/org/spine3/server/stand/Stand.java | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index 9421985bb36..dd4bb7b1095 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -117,6 +117,7 @@ public static Builder newBuilder() { *

  • Closes {@link EventBus}. *
  • Closes {@link CommandStore}. *
  • Closes {@link EventStore}. + *
  • Closes {@link Stand}. *
  • Shuts down all registered repositories. Each registered repository is: *
      *
    • un-registered from {@link CommandBus} @@ -132,6 +133,7 @@ public void close() throws Exception { storageFactory.close(); commandBus.close(); eventBus.close(); + stand.close(); shutDownRepositories(); @@ -140,7 +142,6 @@ public void close() throws Exception { private void shutDownRepositories() throws Exception { for (Repository repository : repositories) { - stand.deregisterSupplierForType(repository.getEntityStateType()); repository.close(); } repositories.clear(); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index af050b60445..18bca0e5fb7 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -85,7 +85,7 @@ * * @author Alex Tymchenko */ -public class Stand { +public class Stand implements AutoCloseable { /** * Persistent storage for the latest {@link org.spine3.server.aggregate.Aggregate} states. @@ -432,9 +432,12 @@ private static void feedStateRecordsToBuilder(ImmutableList.Builder resultB } - // TODO[alex.tymchenko]: perhaps, we need to close Stand instead of doing this upon repository shutdown (see usages). - public void deregisterSupplierForType(TypeUrl typeUrl) { - typeToRepositoryMap.remove(typeUrl); + /** + * Dumps all {@link TypeUrl}-to-{@link EntityRepository} relations. + */ + @Override + public void close() { + typeToRepositoryMap.clear(); } /** From 2e9857f0100f3287a4a0da1d47c866f18cb1b29a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 23 Sep 2016 17:44:35 +0300 Subject: [PATCH 208/361] Male Stand AutoClosable. --- .../main/java/org/spine3/server/BoundedContext.java | 3 ++- .../src/main/java/org/spine3/server/stand/Stand.java | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/spine3/server/BoundedContext.java b/server/src/main/java/org/spine3/server/BoundedContext.java index 9421985bb36..dd4bb7b1095 100644 --- a/server/src/main/java/org/spine3/server/BoundedContext.java +++ b/server/src/main/java/org/spine3/server/BoundedContext.java @@ -117,6 +117,7 @@ public static Builder newBuilder() { *
    • Closes {@link EventBus}. *
    • Closes {@link CommandStore}. *
    • Closes {@link EventStore}. + *
    • Closes {@link Stand}. *
    • Shuts down all registered repositories. Each registered repository is: *
        *
      • un-registered from {@link CommandBus} @@ -132,6 +133,7 @@ public void close() throws Exception { storageFactory.close(); commandBus.close(); eventBus.close(); + stand.close(); shutDownRepositories(); @@ -140,7 +142,6 @@ public void close() throws Exception { private void shutDownRepositories() throws Exception { for (Repository repository : repositories) { - stand.deregisterSupplierForType(repository.getEntityStateType()); repository.close(); } repositories.clear(); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index af050b60445..18bca0e5fb7 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -85,7 +85,7 @@ * * @author Alex Tymchenko */ -public class Stand { +public class Stand implements AutoCloseable { /** * Persistent storage for the latest {@link org.spine3.server.aggregate.Aggregate} states. @@ -432,9 +432,12 @@ private static void feedStateRecordsToBuilder(ImmutableList.Builder resultB } - // TODO[alex.tymchenko]: perhaps, we need to close Stand instead of doing this upon repository shutdown (see usages). - public void deregisterSupplierForType(TypeUrl typeUrl) { - typeToRepositoryMap.remove(typeUrl); + /** + * Dumps all {@link TypeUrl}-to-{@link EntityRepository} relations. + */ + @Override + public void close() { + typeToRepositoryMap.clear(); } /** From 6d6f3c2dfe410cf22baa9feaae193c20f28a6c62 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 26 Sep 2016 12:18:12 +0300 Subject: [PATCH 209/361] Close StandStorage in Stand::close. --- server/src/main/java/org/spine3/server/stand/Stand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 18bca0e5fb7..ed6455d4d96 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -436,8 +436,9 @@ private static void feedStateRecordsToBuilder(ImmutableList.Builder resultB * Dumps all {@link TypeUrl}-to-{@link EntityRepository} relations. */ @Override - public void close() { + public void close() throws Exception { typeToRepositoryMap.clear(); + storage.close(); } /** From 73e1d1eb421d9fa44211123c39577885cbc53af4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 26 Sep 2016 12:20:36 +0300 Subject: [PATCH 210/361] Use milliseconds resolution for command scheduling; fix command scheduler tests accordingly. --- .../command/ExecutorCommandScheduler.java | 26 ++++++++++++++----- .../ExecutorCommandSchedulerShould.java | 7 +++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/spine3/server/command/ExecutorCommandScheduler.java b/server/src/main/java/org/spine3/server/command/ExecutorCommandScheduler.java index 1ac7c732b93..e7e8bcf7feb 100644 --- a/server/src/main/java/org/spine3/server/command/ExecutorCommandScheduler.java +++ b/server/src/main/java/org/spine3/server/command/ExecutorCommandScheduler.java @@ -20,12 +20,13 @@ package org.spine3.server.command; +import com.google.protobuf.Duration; import org.spine3.base.Command; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.spine3.base.CommandContext.Schedule; /** @@ -40,24 +41,37 @@ public class ExecutorCommandScheduler extends CommandScheduler { private static final int MIN_THREAD_POOL_SIZE = 5; + private static final int NANOS_IN_MILLISECOND = 1_000_000; + private static final int MILLIS_IN_SECOND = 1_000; private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(MIN_THREAD_POOL_SIZE); @Override protected void doSchedule(final Command command) { - final long delaySec = getDelaySeconds(command); + final long delaySec = getDelayMilliseconds(command); executorService.schedule(new Runnable() { @Override public void run() { post(command); } - }, delaySec, SECONDS); + }, delaySec, MILLISECONDS); } - private static long getDelaySeconds(Command command) { + private static long getDelayMilliseconds(Command command) { final Schedule schedule = command.getContext().getSchedule(); - final long delaySec = schedule.getDelay().getSeconds(); - return delaySec; + final Duration delay = schedule.getDelay(); + final long delaySec = delay.getSeconds(); + final long delayMillisFraction = delay.getNanos() / NANOS_IN_MILLISECOND; + + /** + * Maximum value of {@link Duration#getSeconds()} is + * . + * + * {@link Long.MAX_VALUE} is +9,223,372,036,854,775,807. That's why it is safe to multiply + * {@code delaySec * MILLIS_IN_SECOND}. + */ + final long absoluteMillis = delaySec * MILLIS_IN_SECOND + delayMillisFraction; + return absoluteMillis; } @Override diff --git a/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java b/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java index c4e9f52dc27..c30e3573b60 100644 --- a/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java +++ b/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java @@ -51,6 +51,9 @@ public class ExecutorCommandSchedulerShould { private static final Duration DELAY = milliseconds(DELAY_MS); + // Wait a bit longer in the verifier to ensure the commmand was processed. + private static final int TINY_DELAY_GAP_MS = 50; + private CommandScheduler scheduler; private CommandContext context; @@ -73,7 +76,7 @@ public void schedule_command_if_delay_is_set() { scheduler.schedule(cmdPrimary); verify(scheduler, never()).post(any(Command.class)); - verify(scheduler, after(DELAY_MS)).post(commandCaptor.capture()); + verify(scheduler, after(DELAY_MS + TINY_DELAY_GAP_MS)).post(commandCaptor.capture()); final Command actualCmd = commandCaptor.getValue(); final Command expectedCmd = Commands.setSchedulingTime(cmdPrimary, getSchedulingTime(actualCmd)); assertEquals(expectedCmd, actualCmd); @@ -88,7 +91,7 @@ public void not_schedule_command_with_same_id_twice() { scheduler.schedule(expectedCmd); scheduler.schedule(extraCmd); - verify(scheduler, after(DELAY_MS)).post(any(Command.class)); + verify(scheduler, after(DELAY_MS + TINY_DELAY_GAP_MS)).post(any(Command.class)); verify(scheduler, never()).post(extraCmd); } From f0dcda549555cb726fd798069efa235ef3cc469f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 26 Sep 2016 17:54:19 +0300 Subject: [PATCH 211/361] Try to ensure correct test execution for async-based command scheduler. --- .../server/command/ExecutorCommandSchedulerShould.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java b/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java index c30e3573b60..8c9ee147d53 100644 --- a/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java +++ b/server/src/test/java/org/spine3/server/command/ExecutorCommandSchedulerShould.java @@ -32,10 +32,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static org.mockito.Mockito.after; import static org.mockito.Mockito.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.spine3.base.Identifiers.newUuid; import static org.spine3.protobuf.Durations.milliseconds; @@ -51,8 +51,8 @@ public class ExecutorCommandSchedulerShould { private static final Duration DELAY = milliseconds(DELAY_MS); - // Wait a bit longer in the verifier to ensure the commmand was processed. - private static final int TINY_DELAY_GAP_MS = 50; + // Wait a bit longer in the verifier to ensure the command was processed. + private static final int WAIT_FOR_PROPAGATION_MS = 300; private CommandScheduler scheduler; private CommandContext context; @@ -76,7 +76,7 @@ public void schedule_command_if_delay_is_set() { scheduler.schedule(cmdPrimary); verify(scheduler, never()).post(any(Command.class)); - verify(scheduler, after(DELAY_MS + TINY_DELAY_GAP_MS)).post(commandCaptor.capture()); + verify(scheduler, timeout(DELAY_MS + WAIT_FOR_PROPAGATION_MS)).post(commandCaptor.capture()); final Command actualCmd = commandCaptor.getValue(); final Command expectedCmd = Commands.setSchedulingTime(cmdPrimary, getSchedulingTime(actualCmd)); assertEquals(expectedCmd, actualCmd); @@ -91,7 +91,7 @@ public void not_schedule_command_with_same_id_twice() { scheduler.schedule(expectedCmd); scheduler.schedule(extraCmd); - verify(scheduler, after(DELAY_MS + TINY_DELAY_GAP_MS)).post(any(Command.class)); + verify(scheduler, timeout(DELAY_MS + WAIT_FOR_PROPAGATION_MS)).post(any(Command.class)); verify(scheduler, never()).post(extraCmd); } From 2b765fe4ba60353e705cbbaad08467356cee8fcb Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 26 Sep 2016 18:57:59 +0300 Subject: [PATCH 212/361] Update the Client -> Server example with the non-blocking SubscriptionService showcase. --- .../spine3/examples/aggregate/ClientApp.java | 101 +++++++++++++----- .../examples/aggregate/server/Server.java | 14 ++- 2 files changed, 85 insertions(+), 30 deletions(-) diff --git a/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java b/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java index 8599736a214..7a1d2d53335 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/ClientApp.java @@ -26,11 +26,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spine3.base.Command; -import org.spine3.base.Event; import org.spine3.base.Identifiers; +import org.spine3.base.Queries; import org.spine3.base.Response; import org.spine3.client.CommandFactory; +import org.spine3.client.Subscription; +import org.spine3.client.SubscriptionUpdate; +import org.spine3.client.Target; +import org.spine3.client.Topic; import org.spine3.client.grpc.CommandServiceGrpc; +import org.spine3.client.grpc.SubscriptionServiceGrpc; import org.spine3.examples.aggregate.command.AddOrderLine; import org.spine3.examples.aggregate.command.CreateOrder; import org.spine3.examples.aggregate.command.PayForOrder; @@ -56,6 +61,7 @@ * @author Mikhail Mikhaylov * @author Alexander Litus */ +@SuppressWarnings("OverlyCoupledClass") // OK for a self-contained all-in-one example. public class ClientApp { @SuppressWarnings("DuplicateStringLiteralInspection") @@ -67,39 +73,39 @@ public class ClientApp { private final CommandFactory commandFactory; private final ManagedChannel channel; private final CommandServiceGrpc.CommandServiceBlockingStub blockingClient; - // TODO[alex.tymchenko]: switch to SubscriptionService instead. - private final CommandServiceGrpc.CommandServiceStub nonBlockingClient; + private final SubscriptionServiceGrpc.SubscriptionServiceStub subscriptionClient; - private final StreamObserver observer = new StreamObserver() { + private final StreamObserver orderUpdateObserver = new StreamObserver() { @Override - public void onNext(Event event) { - final String eventText = Messages.toText(event.getMessage()); - log().info(eventText); + public void onNext(SubscriptionUpdate subscriptionUpdate) { + final String updateText = Messages.toText(subscriptionUpdate); + log().info(" + Order updated: {}", updateText); } @Override public void onError(Throwable throwable) { - log().error("Streaming error occurred", throwable); + log().error("Subscription update delivery error occurred", throwable); } @Override public void onCompleted() { - log().info("Stream completed."); + log().info("Subscription update delivery completed."); } }; + /** Construct the client connecting to server at {@code host:port}. */ public ClientApp(String host, int port) { commandFactory = CommandFactory.newBuilder() - .setActor(newUserId(Identifiers.newUuid())) - .setZoneOffset(ZoneOffsets.UTC) - .build(); + .setActor(newUserId(Identifiers.newUuid())) + .setZoneOffset(ZoneOffsets.UTC) + .build(); channel = ManagedChannelBuilder .forAddress(host, port) .usePlaintext(true) .build(); blockingClient = CommandServiceGrpc.newBlockingStub(channel); - nonBlockingClient = CommandServiceGrpc.newStub(channel); + subscriptionClient = SubscriptionServiceGrpc.newStub(channel); } private Command createOrder(OrderId orderId) { @@ -112,26 +118,31 @@ private Command createOrder(OrderId orderId) { private Command addOrderLine(OrderId orderId) { final int bookPriceUsd = 52; final Book book = Book.newBuilder() - .setBookId(BookId.newBuilder().setISBN("978-0321125217").build()) - .setAuthor("Eric Evans") - .setTitle("Domain Driven Design.") - .setPrice(newMoney(bookPriceUsd, USD)) - .build(); + .setBookId(BookId.newBuilder() + .setISBN("978-0321125217") + .build()) + .setAuthor("Eric Evans") + .setTitle("Domain Driven Design.") + .setPrice(newMoney(bookPriceUsd, USD)) + .build(); final int quantity = 1; final Money totalPrice = newMoney(bookPriceUsd * quantity, USD); final OrderLine orderLine = OrderLine.newBuilder() - .setProductId(AnyPacker.pack(book.getBookId())) - .setQuantity(quantity) - .setPrice(totalPrice) - .build(); + .setProductId(AnyPacker.pack(book.getBookId())) + .setQuantity(quantity) + .setPrice(totalPrice) + .build(); final AddOrderLine msg = AddOrderLine.newBuilder() .setOrderId(orderId) - .setOrderLine(orderLine).build(); + .setOrderLine(orderLine) + .build(); return commandFactory.create(msg); } private Command payForOrder(OrderId orderId) { - final BillingInfo billingInfo = BillingInfo.newBuilder().setInfo("Payment info is here.").build(); + final BillingInfo billingInfo = BillingInfo.newBuilder() + .setInfo("Payment info is here.") + .build(); final PayForOrder msg = PayForOrder.newBuilder() .setOrderId(orderId) .setBillingInfo(billingInfo) @@ -140,14 +151,47 @@ private Command payForOrder(OrderId orderId) { } + private void subscribe() { + final Target allOrders = Queries.Targets.allOf(Order.class); + final Topic topic = Topic.newBuilder() + .setTarget(allOrders) + .build(); + subscriptionClient.subscribe(topic, new StreamObserver() { + + private Subscription latestSubscription; + + @Override + public void onNext(Subscription subscription) { + this.latestSubscription = subscription; + final String eventText = Messages.toText(subscription); + log().info(eventText); + } + + @Override + public void onError(Throwable throwable) { + log().error("Subscription error occurred", throwable); + } + + @Override + public void onCompleted() { + log().info("Subscription request completed."); + subscriptionClient.activate(latestSubscription, orderUpdateObserver); + } + + }); + } + + /** Sends requests to the server. */ public static void main(String[] args) throws InterruptedException { final ClientApp client = new ClientApp(SERVICE_HOST, DEFAULT_CLIENT_SERVICE_PORT); + client.subscribe(); final List requests = client.generateRequests(); for (Command request : requests) { - log().info("Sending a request: " + request.getMessage().getTypeUrl() + "..."); + log().info("Sending a request: " + request.getMessage() + .getTypeUrl() + "..."); final Response result = client.post(request); log().info("Result: " + toText(result)); } @@ -159,7 +203,9 @@ public static void main(String[] args) throws InterruptedException { private List generateRequests() { final List result = newLinkedList(); for (int i = 0; i < 10; i++) { - final OrderId orderId = OrderId.newBuilder().setValue(String.valueOf(i)).build(); + final OrderId orderId = OrderId.newBuilder() + .setValue(String.valueOf(i)) + .build(); result.add(createOrder(orderId)); result.add(addOrderLine(orderId)); result.add(payForOrder(orderId)); @@ -173,7 +219,8 @@ private List generateRequests() { * @throws InterruptedException if waiting is interrupted. */ private void shutdown() throws InterruptedException { - channel.shutdown().awaitTermination(SHUTDOWN_TIMEOUT_SEC, SECONDS); + channel.shutdown() + .awaitTermination(SHUTDOWN_TIMEOUT_SEC, SECONDS); } /** Sends a request to the server. */ diff --git a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java index 058adfaca06..85412371d97 100644 --- a/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java +++ b/examples/src/main/java/org/spine3/examples/aggregate/server/Server.java @@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory; import org.spine3.server.BoundedContext; import org.spine3.server.CommandService; +import org.spine3.server.SubscriptionService; import org.spine3.server.event.EventSubscriber; import org.spine3.server.storage.StorageFactory; import org.spine3.server.storage.memory.InMemoryStorageFactory; @@ -57,15 +58,22 @@ public Server(StorageFactory storageFactory) { boundedContext.getEventBus() .subscribe(eventLogger); - // Create a client service with this bounded context. - final CommandService clientService = CommandService.newBuilder() + // Create a command service with this bounded context, + final CommandService commandService = CommandService.newBuilder() .addBoundedContext(boundedContext) .build(); + // and a subscription service for the same bounded context. + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + + // Create a gRPC server and schedule the client service instance for deployment. this.grpcContainer = GrpcContainer.newBuilder() .setPort(DEFAULT_CLIENT_SERVICE_PORT) - .addService(clientService) + .addService(commandService) + .addService(subscriptionService) .build(); } From 50de0a952f066fc93f5b16c79b44f60d36f323cc Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 26 Sep 2016 18:58:17 +0300 Subject: [PATCH 213/361] Remove resolved todo item. --- .../src/main/java/org/spine3/server/entity/EntityRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 6bf3b51bba9..62ae6c69e29 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -121,7 +121,6 @@ public E load(I id) { @CheckReturnValue @Nullable public E find(I id) { - // TODO[alex.tymchenko]: check whether using #load(id) straightaway is a good idea; return this.load(id); } From af7fc02532e8de31312416c8659c8b8b9882443a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 26 Sep 2016 19:00:51 +0300 Subject: [PATCH 214/361] Add a short description for the StandStorage. --- .../src/main/java/org/spine3/server/storage/StandStorage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index 6acc5a0bdb6..54f4841d7a8 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -29,9 +29,9 @@ import org.spine3.server.stand.Stand; /** - * Contract for {@link Stand} storage. + * Serves as a storage for the latest {@link org.spine3.server.aggregate.Aggregate} states. * - * // TODO[alex.tymchenko]: describe + *

        Used by an instance of {@link Stand} to optimize the {@code Aggregate} state fetch performance. * * @author Alex Tymchenko * @see Any#getTypeUrl() From a4e8fd98a573282515f437bfe00bc785af68ab36 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 26 Sep 2016 19:04:14 +0300 Subject: [PATCH 215/361] Omit extra `null` checks for EntityFilters and address a related todo item. --- server/src/main/java/org/spine3/server/stand/Stand.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index ed6455d4d96..b3a82158ad0 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -317,10 +317,10 @@ private ImmutableCollection fetchFromStandStorage(Query que } else { final EntityFilters filters = target.getFilters(); - // TODO[alex.tymchenko]: do we need to check for null at all? How about, say, Python gRPC client? - if (filters != null && filters.getIdFilter() != null && !filters.getIdFilter() - .getIdsList() - .isEmpty()) { + final boolean idsAreDefined = !filters.getIdFilter() + .getIdsList() + .isEmpty(); + if (idsAreDefined) { final EntityIdFilter idFilter = filters.getIdFilter(); final Collection stateIds = Collections2.transform(idFilter.getIdsList(), aggregateStateIdTransformer(typeUrl)); From 1f3c4a0ff9df203ae3aed8dfee96f4a28bcdb3ca Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 26 Sep 2016 19:17:31 +0300 Subject: [PATCH 216/361] Bump Spine version to 0.6.0-SNAPSHOT. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cd39e03385a..1059b75f4fa 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { apply plugin: 'idea' group = 'org.spine3' - version = '0.5.8-SNAPSHOT' + version = '0.6.0-SNAPSHOT' } ext { From 855f8203dd36759d95ca133392a8dbe047b2c38a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 16:55:40 +0300 Subject: [PATCH 217/361] Add JavaDocs for Queries. --- .../main/java/org/spine3/base/Queries.java | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index fdbefcd0530..54387c2eee6 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -49,6 +49,25 @@ public class Queries { private Queries() { } + /** + * Create a {@link Query} to read certain entity states by IDs with the {@link FieldMask} + * applied to each of the results. + * + *

        Allows to specify a set of identifiers to be used during the {@code Query} processing. The processing + * results will contain only the entities, which IDs are present among the {@code ids}. + * + *

        Allows to set property paths for a {@link FieldMask}, applied to each of the query results. + * This processing is performed according to the + * FieldMask specs. + * + *

        In case the {@code paths} array contains entries inapplicable to the resulting entity, such invalid paths + * are silently ignored. + * + * @param entityClass the class of a target entity + * @param paths the property paths for the {@code FieldMask} applied to each of results + * @return an instance of {@code Query} formed according to the passed parameters + */ public static Query readAll(Class entityClass, String... paths) { final FieldMask fieldMask = FieldMask.newBuilder() - .addAllPaths(Arrays.asList(paths)) - .build(); + .addAllPaths(Arrays.asList(paths)) + .build(); final Query result = composeQuery(entityClass, null, fieldMask); return result; } + + /** + * Create a {@link Query} to read certain entity states by IDs. + * + *

        Allows to specify a set of identifiers to be used during the {@code Query} processing. The processing + * results will contain only the entities, which IDs are present among the {@code ids}. + * + *

        Unlike {@link Queries#readByIds(Class, Set, String...)}, the {@code Query} processing will not change + * the resulting entities. + * + * @param entityClass the class of a target entity + * @param ids the entity IDs of interest + * @return an instance of {@code Query} formed according to the passed parameters + */ public static Query readByIds(Class entityClass, Set ids) { return composeQuery(entityClass, ids, null); } + /** + * Create a {@link Query} to read all states of a certain entity. + * + *

        Unlike {@link Queries#readAll(Class, String...)}, the {@code Query} processing will not change + * the resulting entities. + * + * @param entityClass the class of a target entity + * @return an instance of {@code Query} formed according to the passed parameters + */ public static Query readAll(Class entityClass) { return composeQuery(entityClass, null, null); } @@ -125,7 +182,8 @@ static Target composeTarget(Class entityClass, @Nullable Set< .setIdFilter(idFilter) .build(); - final Target.Builder builder = Target.newBuilder().setType(type.getTypeName()); + final Target.Builder builder = Target.newBuilder() + .setType(type.getTypeName()); if (includeAll) { builder.setIncludeAll(true); } else { From 1cea1406bbb1d63b0625acdc81b602b74306b70c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 17:33:04 +0300 Subject: [PATCH 218/361] Add JavaDocs for Queries.Targets. --- .../main/java/org/spine3/base/Queries.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index 54387c2eee6..8546e21bb8b 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -145,16 +145,35 @@ private static Query composeQuery(Class entityClass, @Nullabl } + /** + * Client-side utilities for working with {@link Query} and {@link org.spine3.client.Subscription} targets. + * + * @author Alex Tymchenko + * @author Dmytro Dashenkov + */ public static class Targets { private Targets() { } + /** + * Create a {@link Target} for a subset of the entity states by specifying their IDs. + * + * @param entityClass the class of a target entity + * @param ids the IDs of interest + * @return the instance of {@code Target} assembled according to the parameters. + */ public static Target someOf(Class entityClass, Set ids) { final Target result = composeTarget(entityClass, ids); return result; } + /** + * Create a {@link Target} for all of the specified entity states. + * + * @param entityClass the class of a target entity + * @return the instance of {@code Target} assembled according to the parameters. + */ public static Target allOf(Class entityClass) { final Target result = composeTarget(entityClass, null); return result; From 80b710f7ac4262658ec830761c9f1a50fbe6565b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 17:35:54 +0300 Subject: [PATCH 219/361] Fix typos in the JavaDocs for Queries. --- client/src/main/java/org/spine3/base/Queries.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index 8546e21bb8b..d29cad401b8 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -60,8 +60,8 @@ private Queries() { * This processing is performed according to the * FieldMask specs. * - *

        In case the {@code paths} array contains entries inapplicable to the resulting entity, such invalid paths - * are silently ignored. + *

        In case the {@code paths} array contains entries inapplicable to the resulting entity + * (e.g. a {@code path} references a missing field), such invalid paths are silently ignored. * * @param entityClass the class of a target entity * @param paths the property paths for the {@code FieldMask} applied to each of results From fb2004919e7f34b612aa2c4208bbe01e05ff8b9f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 17:44:57 +0300 Subject: [PATCH 220/361] Remove the extra lines. --- client/src/main/java/org/spine3/base/Queries.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index d29cad401b8..46887f2afeb 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -133,7 +133,6 @@ public static Query readAll(Class entityClass) { private static Query composeQuery(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { final Target target = composeTarget(entityClass, ids); - final Query.Builder queryBuilder = Query.newBuilder() .setTarget(target); if (fieldMask != null) { @@ -200,14 +199,12 @@ static Target composeTarget(Class entityClass, @Nullable Set< final EntityFilters filters = EntityFilters.newBuilder() .setIdFilter(idFilter) .build(); - final Target.Builder builder = Target.newBuilder() .setType(type.getTypeName()); if (includeAll) { builder.setIncludeAll(true); } else { builder.setFilters(filters); - } return builder.build(); From 8bf368f908977590204c22b318631c88d10c3cb8 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 17:45:45 +0300 Subject: [PATCH 221/361] Improve readability of CommandService definition. --- client/src/main/proto/spine/client/command_service.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/main/proto/spine/client/command_service.proto b/client/src/main/proto/spine/client/command_service.proto index 8e288d3bd8d..c8e1b2a3ea3 100644 --- a/client/src/main/proto/spine/client/command_service.proto +++ b/client/src/main/proto/spine/client/command_service.proto @@ -36,6 +36,7 @@ import "spine/base/response.proto"; // A service for sending commands from clients. service CommandService { + // Request to handle a command. rpc Post(base.Command) returns (base.Response); } From 7525ce08f3cade909169cd2cde51a2f5cd1a8836 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 17:57:13 +0300 Subject: [PATCH 222/361] Document the QueryResponse proto. --- client/src/main/proto/spine/client/query.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/main/proto/spine/client/query.proto b/client/src/main/proto/spine/client/query.proto index 1a48b6ef51f..59fd56e0ae5 100644 --- a/client/src/main/proto/spine/client/query.proto +++ b/client/src/main/proto/spine/client/query.proto @@ -59,6 +59,10 @@ message Query { reserved 3 to 6; } +// The result of Query processing in the read-side implementation. +// +// Contains the actual processing results and other response attributes. +// Used as a result of {@code QueryService#Read(Query)} gRPC method call. message QueryResponse { // Represents the base part of the response. I.e. whether the Query has been acked or not. @@ -67,5 +71,6 @@ message QueryResponse { // Reserved for more query response attributes, e.g. to describe paginated response etc. reserved 2 to 4; + // Entity states (each packed as Any) returned to the API user as a result of Query execution. repeated google.protobuf.Any messages = 5; } From 021490c2cb1d7307018351b0d1cdcc7ca231a284 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 30 Sep 2016 18:20:21 +0300 Subject: [PATCH 223/361] Add test for searching by ids with field mask. --- .../org/spine3/server/stand/StandShould.java | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index f5c310f7e06..92e980aba8c 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -60,8 +60,10 @@ import org.spine3.test.projection.ProjectId; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -89,6 +91,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.spine3.server.stand.Given.StandTestProjection; +import static org.spine3.test.Verify.assertSize; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; /** @@ -280,6 +283,14 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { doCheckReadingProjectsById(TOTAL_PROJECTS_FOR_BATCH_READING); } + @Test + public void return_multiple_results_for_projection_batch_read_by_ids_with_field_mask() { + final List projectFields = Project.getDescriptor().getFields(); + doCheckReadingProjectsByIdAndFieldMask( + projectFields.get(0).getFullName(), // Id + projectFields.get(1).getFullName()); // Name + } + @Test public void trigger_subscription_callback_upon_update_of_aggregate() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); @@ -691,6 +702,50 @@ private static void doCheckReadingProjectsById(int numberOfProjects) { } } + @SuppressWarnings("MethodWithMultipleLoops") + private static void doCheckReadingProjectsByIdAndFieldMask(String... paths) { + //noinspection ZeroLengthArrayAllocation + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + + final Set ids = new HashSet<>(2); + + for (int i = 0; i < ids.size(); i++) { + final Customer customer = getSampleCustomer().toBuilder() + .setId(CustomerId.newBuilder().setNumber(i)) + .build(); + + stand.update(customer.getId(), AnyPacker.pack(customer)); + + ids.add(customer.getId()); + } + + final Query customerQuery = Queries.readByIds(Customer.class, ids, paths); + + final FieldMask fieldMask = FieldMask.newBuilder().addAllPaths(Arrays.asList(paths)).build(); + + final MemoizeQueryResponseObserver observer = new MemoizeQueryResponseObserver() { + @Override + public void onNext(QueryResponse value) { + super.onNext(value); + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + assertSize(ids.size(), messages); + + for (Any message : messages) { + final Customer customer = AnyPacker.unpack(message); + + assertNotEquals(customer, null); + + assertMatches(customer, fieldMask); + } + } + }; + + stand.execute(customerQuery, observer); + + verifyObserver(observer); + } + private static void doCheckReadingCustomersById(int numberOfCustomers) { // Define the types and values used as a test data. final TypeUrl customerType = TypeUrl.of(Customer.class); @@ -770,7 +825,6 @@ private static void setupExpectedBulkReadBehaviour(Map sam when(standStorageMock.readBulk(matchingIds)).thenReturn(records); } - @SuppressWarnings("ConstantConditions") private static void setupExpectedFindAllBehaviour(Map sampleProjects, StandTestProjectionRepository projectionRepository) { @@ -914,6 +968,23 @@ private static void fillSampleProjects(Map sampleProjects, i } } + private static void fillRichSampleProjects(Map sampleProjects, int numberOfProjects) { + for (int projectIndex = 0; projectIndex < numberOfProjects; projectIndex++) { + final ProjectId projectId = ProjectId.newBuilder() + .setId(UUID.randomUUID() + .toString()) + .build(); + + final Project project = Project.newBuilder() + .setId(projectId) + .setName(String.valueOf(projectIndex)) + .setStatus(Project.Status.CREATED) + .build(); + + sampleProjects.put(projectId, project); + } + } + private static List checkAndGetMessageList(MemoizeQueryResponseObserver responseObserver) { assertTrue("Query has not completed successfully", responseObserver.isCompleted); assertNull("Throwable has been caught upon query execution", responseObserver.throwable); @@ -983,6 +1054,19 @@ public void onEntityStateUpdate(Any newEntityState) { }; } + private static void assertMatches(Message message, FieldMask fieldMask) { + final List paths = fieldMask.getPathsList(); + for (Descriptors.FieldDescriptor field : message.getDescriptorForType().getFields()) { + + // Protobuf limitation, has no effect on the test. + if (field.isRepeated()) { + continue; + } + + assertEquals(message.hasField(field), paths.contains(field.getFullName())); + } + } + // ***** Inner classes used for tests. ***** From 0f1a1b0680db70d3c88f81afc6700c2eb98fcc04 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 18:35:27 +0300 Subject: [PATCH 224/361] Remove redundant line. --- client/src/main/proto/spine/client/subscription.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/main/proto/spine/client/subscription.proto b/client/src/main/proto/spine/client/subscription.proto index bff425e31a0..09e1553e8aa 100644 --- a/client/src/main/proto/spine/client/subscription.proto +++ b/client/src/main/proto/spine/client/subscription.proto @@ -87,5 +87,4 @@ message Subscription { // Reserved for subscription attributes. reserved 3 to 10; - } From b450c9bd45b9d497dba93632bfaa19b01830586d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 18:52:46 +0300 Subject: [PATCH 225/361] Improve readability of CommandService and QueryService. --- server/src/main/java/org/spine3/server/CommandService.java | 3 --- server/src/main/java/org/spine3/server/QueryService.java | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/server/src/main/java/org/spine3/server/CommandService.java b/server/src/main/java/org/spine3/server/CommandService.java index 2306b1cdb21..07b523a00a8 100644 --- a/server/src/main/java/org/spine3/server/CommandService.java +++ b/server/src/main/java/org/spine3/server/CommandService.java @@ -53,7 +53,6 @@ protected CommandService(Builder builder) { this.boundedContextMap = builder.getBoundedContextMap(); } - @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. @Override public void post(Command request, StreamObserver responseObserver) { @@ -78,7 +77,6 @@ public static class Builder { private final Set boundedContexts = Sets.newHashSet(); private ImmutableMap boundedContextMap; - public Builder addBoundedContext(BoundedContext boundedContext) { // Save it to a temporary set so that it is easy to remove it if needed. boundedContexts.add(boundedContext); @@ -90,7 +88,6 @@ public Builder removeBoundedContext(BoundedContext boundedContext) { return this; } - @SuppressWarnings("ReturnOfCollectionOrArrayField") // is immutable public ImmutableMap getBoundedContextMap() { return boundedContextMap; diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index 8937d0ce872..20f7ee403ac 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -42,8 +42,7 @@ * * @author Alex Tymchenko */ -public class QueryService - extends QueryServiceGrpc.QueryServiceImplBase { +public class QueryService extends QueryServiceGrpc.QueryServiceImplBase { private final ImmutableMap typeToContextMap; @@ -81,7 +80,6 @@ public static class Builder { private final Set boundedContexts = Sets.newHashSet(); private ImmutableMap typeToContextMap; - public Builder addBoundedContext(BoundedContext boundedContext) { // Save it to a temporary set so that it is easy to remove it if needed. boundedContexts.add(boundedContext); From 179767e3426e5fdb272a2ce549bff90379337158 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 19:05:05 +0300 Subject: [PATCH 226/361] Improve readability of SubscriptionService. --- .../java/org/spine3/server/SubscriptionService.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index 6c86b37f0d9..d71169d744c 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -80,7 +80,6 @@ public void subscribe(Topic topic, StreamObserver responseObserver responseObserver.onError(e); responseObserver.onCompleted(); } - } @Override @@ -103,7 +102,8 @@ public void onEntityStateUpdate(Any newEntityState) { responseObserver.onNext(update); } }; - boundedContext.getStand().activate(subscription, updateCallback); + final Stand targetStand = boundedContext.getStand(); + targetStand.activate(subscription, updateCallback); } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { log().error("Error activating the subscription", e); @@ -141,12 +141,10 @@ private BoundedContext selectBoundedContext(Target target) { return typeToContextMap.get(type); } - public static class Builder { private final Set boundedContexts = Sets.newHashSet(); private ImmutableMap typeToContextMap; - public Builder addBoundedContext(BoundedContext boundedContext) { // Save it to a temporary set so that it is easy to remove it if needed. boundedContexts.add(boundedContext); @@ -158,13 +156,11 @@ public Builder removeBoundedContext(BoundedContext boundedContext) { return this; } - @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection returned is immutable public ImmutableMap getBoundedContextMap() { return typeToContextMap; } - @SuppressWarnings("ReturnOfCollectionOrArrayField") // the collection returned is immutable public ImmutableList getBoundedContexts() { return ImmutableList.copyOf(boundedContexts); @@ -193,11 +189,10 @@ private ImmutableMap createBoundedContextMap() { } private static void addBoundedContext(ImmutableMap.Builder mapBuilder, - BoundedContext boundedContext) { + BoundedContext boundedContext) { final ImmutableSet availableTypes = boundedContext.getStand() .getAvailableTypes(); - for (TypeUrl availableType : availableTypes) { mapBuilder.put(availableType, boundedContext); } From 8840361831bb09b64f80d9adb5e26f9036a89b24 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Fri, 30 Sep 2016 19:05:54 +0300 Subject: [PATCH 227/361] Turn on "Align multiline parameters" in the method declaration to improve the readability. --- .idea/codeStyleSettings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 13da8a541b7..691bf49884f 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -12,7 +12,6 @@

        Same as calling

        {@code findBulk(id, FieldMask.getDefaultInstance());}
        * * @param ids entity IDs to search for - * @return all the entities in this repository with the IDs matching the given {@code Iterable} + * @return all the entities in this repository with the IDs contained in the given {@code ids} * @see #findBulk(Iterable, FieldMask) */ @CheckReturnValue @@ -157,7 +156,7 @@ public ImmutableCollection findBulk(Iterable ids) { * *

        NOTE: The storage must be assigned before calling this method. * - * @param ids entity IDs to search for + * @param ids entity IDs to search for * @param fieldMask mask to apply on entities * @return all the entities in this repository with the IDs matching the given {@code Iterable} */ @@ -198,7 +197,6 @@ public E apply(@Nullable Map.Entry input) { }) .toList(); - return entities; } @@ -209,7 +207,7 @@ public E apply(@Nullable Map.Entry input) { * *

        NOTE: The storage must be assigned before calling this method. * - * @param filters entity filters + * @param filters entity filters * @param fieldMask mask to apply to the entities * @return all the entities in this repository passed the filters. */ @@ -239,7 +237,6 @@ public I apply(@Nullable EntityId input) { @SuppressWarnings("unchecked") final I id = (I) idAsMessage; return id; - } }); @@ -259,8 +256,6 @@ private E toEntity(I id, EntityStorageRecord record, FieldMask fieldMask) { return entity; } - - private EntityStorageRecord toEntityRecord(E entity) { final M state = entity.getState(); final Any stateAny = AnyPacker.pack(state); From 1960f7f6e54a86713aa1f70fbfccc7d32381edc9 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Sat, 1 Oct 2016 13:14:40 +0300 Subject: [PATCH 229/361] Fix test for searching by ids with field mask. --- .../java/org/spine3/server/stand/StandShould.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 92e980aba8c..833c1db7e35 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -286,7 +286,7 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { @Test public void return_multiple_results_for_projection_batch_read_by_ids_with_field_mask() { final List projectFields = Project.getDescriptor().getFields(); - doCheckReadingProjectsByIdAndFieldMask( + doCheckReadingCustomersByIdAndFieldMask( projectFields.get(0).getFullName(), // Id projectFields.get(1).getFullName()); // Name } @@ -703,13 +703,15 @@ private static void doCheckReadingProjectsById(int numberOfProjects) { } @SuppressWarnings("MethodWithMultipleLoops") - private static void doCheckReadingProjectsByIdAndFieldMask(String... paths) { + private static void doCheckReadingCustomersByIdAndFieldMask(String... paths) { //noinspection ZeroLengthArrayAllocation final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); - final Set ids = new HashSet<>(2); + final int querySize = 2; - for (int i = 0; i < ids.size(); i++) { + final Set ids = new HashSet<>(); + + for (int i = 0; i < querySize; i++) { final Customer customer = getSampleCustomer().toBuilder() .setId(CustomerId.newBuilder().setNumber(i)) .build(); @@ -728,7 +730,6 @@ private static void doCheckReadingProjectsByIdAndFieldMask(String... paths) { public void onNext(QueryResponse value) { super.onNext(value); final List messages = value.getMessagesList(); - assertFalse(messages.isEmpty()); assertSize(ids.size(), messages); for (Any message : messages) { From 1c45f5c32e24d834b8f72a3c718214081485c05b Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Sat, 1 Oct 2016 14:00:18 +0300 Subject: [PATCH 230/361] Cover CommandService.Builder::removeBoundedContext with a test. --- .../org/spine3/server/CommandServiceShould.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/server/src/test/java/org/spine3/server/CommandServiceShould.java b/server/src/test/java/org/spine3/server/CommandServiceShould.java index 285b97ce095..b2312512d9e 100644 --- a/server/src/test/java/org/spine3/server/CommandServiceShould.java +++ b/server/src/test/java/org/spine3/server/CommandServiceShould.java @@ -41,6 +41,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; @@ -94,6 +95,20 @@ public void post_commands_to_appropriate_bounded_context() { verifyPostsCommand(Given.Command.createCustomer(), customersContext.getCommandBus()); } + @Test + public void never_retrieve_removed_bounded_contexts_from_builder() { + final CommandService.Builder builder = CommandService.newBuilder() + .addBoundedContext(projectsContext) + .addBoundedContext(customersContext) + .removeBoundedContext(projectsContext); + + final CommandService service = builder.build(); // Creates BoundedContext map + assertNotNull(service); + + assertTrue(builder.getBoundedContextMap().containsValue(customersContext)); + assertFalse(builder.getBoundedContextMap().containsValue(projectsContext)); + } + private void verifyPostsCommand(Command cmd, CommandBus commandBus) { service.post(cmd, responseObserver); From 76b15893cfac79877527636b8a33017e4b110745 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 3 Oct 2016 19:13:15 +0300 Subject: [PATCH 231/361] Fix naming and suppression issues. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 833c1db7e35..5540a1b4a52 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -287,7 +287,7 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { public void return_multiple_results_for_projection_batch_read_by_ids_with_field_mask() { final List projectFields = Project.getDescriptor().getFields(); doCheckReadingCustomersByIdAndFieldMask( - projectFields.get(0).getFullName(), // Id + projectFields.get(0).getFullName(), // ID projectFields.get(1).getFullName()); // Name } @@ -704,7 +704,6 @@ private static void doCheckReadingProjectsById(int numberOfProjects) { @SuppressWarnings("MethodWithMultipleLoops") private static void doCheckReadingCustomersByIdAndFieldMask(String... paths) { - //noinspection ZeroLengthArrayAllocation final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); final int querySize = 2; From d804eca3e0a86f186b410992380eaeeb7b57c35d Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 11:27:53 +0300 Subject: [PATCH 232/361] Add common entity repository tests. --- .../storage/memory/InMemoryRecordStorage.java | 10 +-- .../AbstractEntityRepositoryShould.java | 83 +++++++++++++++++++ .../ProcessManagerRepositoryShould.java | 38 ++++++++- 3 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index b2635d29091..624a67f2d6d 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -88,10 +88,10 @@ protected Iterable readBulkInternal(final Iterable given matchingRecord.setState(processed); - result.add(matchingRecord.build()); - } else { - result.add(null); - } + result.add(matchingRecord.build()); } +// } else { +// result.add(null); +// } } } return result; @@ -99,7 +99,7 @@ protected Iterable readBulkInternal(final Iterable given @Override protected Iterable readBulkInternal(Iterable ids) { - return readBulkInternal(ids, null); + return readBulkInternal(ids, FieldMask.getDefaultInstance()); } @Override diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java new file mode 100644 index 00000000000..0bc312f8bb1 --- /dev/null +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.entity; + +import org.junit.Test; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.spine3.test.Verify.assertContains; +import static org.spine3.test.Verify.assertSize; + +/** + * @author Dmytro Dashenkov + */ +public abstract class AbstractEntityRepositoryShould> { + + @SuppressWarnings("unchecked") + @Test + public void find_single_entity_by_id() { + final EntityRepository repo = repository(); + + final E entity = entity(); + + repo.store(entity); + + final Entity found = repo.find(entity.getId()); + + assertEquals(found, entity); + } + + @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + @Test + public void find_multiple_entities_by_ids() { + final EntityRepository repo = repository(); + + final int count = 10; + final List entities = entities(count); + + for (E entity : entities) { + repo.store(entity); + } + + final List ids = new LinkedList<>(); + for (int i = 0; i < count / 2; i++) { + ids.add(entities.get(i).getId()); + } + + final Collection found = repo.findBulk(ids); + + assertSize(count / 2, found); + + for (E entity : found) { + assertContains(entity, entities); + } + } + + protected abstract EntityRepository repository(); + + protected abstract E entity(); + + protected abstract List entities(int count); +} diff --git a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java index 0e803b066cf..c388fc1ad59 100644 --- a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java @@ -38,6 +38,8 @@ import org.spine3.server.BoundedContext; import org.spine3.server.command.Assign; import org.spine3.server.command.CommandDispatcher; +import org.spine3.server.entity.AbstractEntityRepositoryShould; +import org.spine3.server.entity.EntityRepository; import org.spine3.server.entity.IdFunction; import org.spine3.server.event.EventBus; import org.spine3.server.event.GetProducerIdFromEvent; @@ -59,6 +61,8 @@ import javax.annotation.Nonnull; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -74,7 +78,7 @@ * @author Alexander Litus */ @SuppressWarnings("InstanceMethodNamingConvention") -public class ProcessManagerRepositoryShould { +public class ProcessManagerRepositoryShould extends AbstractEntityRepositoryShould { private static final ProjectId ID = Given.AggregateId.newProjectId(); @@ -205,6 +209,36 @@ public void return_event_classes() { assertTrue(eventClasses.contains(EventClass.of(ProjectStarted.class))); } + @Override + protected EntityRepository repository() { + final TestProcessManagerRepository repo = new TestProcessManagerRepository( + TestBoundedContextFactory.newBoundedContext()); + + repo.initStorage(InMemoryStorageFactory.getInstance()); + + return repo; + } + + @Override + protected TestProcessManager entity() { + final ProjectId id = ProjectId.newBuilder().setId("123-id").build(); + return new TestProcessManager(id); + } + + @Override + protected List entities(int count) { + final List procmans = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + final ProjectId id = ProjectId.newBuilder().setId(String.format("procman-number-%s", i)).build(); + + procmans.add(new TestProcessManager(id)); + } + + return procmans; + } + + private static class TestProcessManagerRepository extends ProcessManagerRepository { @@ -218,7 +252,7 @@ private TestProcessManagerRepository(BoundedContext boundedContext) { } } - private static class TestProcessManager extends ProcessManager { + /* package */ static class TestProcessManager extends ProcessManager { /** The event message we store for inspecting in delivery tests. */ private static final Multimap messagesDelivered = HashMultimap.create(); From 73c6cbf349d8269d043f85523b06abac7bcfbfa8 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 11:39:48 +0300 Subject: [PATCH 233/361] Add implementation for ProjectionRepository. --- .../ProjectionRepositoryShould.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java index 5cdf02044dc..09e26780e39 100644 --- a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java @@ -30,6 +30,8 @@ import org.spine3.base.EventContext; import org.spine3.base.Events; import org.spine3.server.BoundedContext; +import org.spine3.server.entity.AbstractEntityRepositoryShould; +import org.spine3.server.entity.EntityRepository; import org.spine3.server.event.EventStore; import org.spine3.server.event.Subscribe; import org.spine3.server.storage.RecordStorage; @@ -41,6 +43,8 @@ import org.spine3.test.projection.event.ProjectStarted; import org.spine3.test.projection.event.TaskAdded; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -59,7 +63,7 @@ * @author Alexander Litus */ @SuppressWarnings("InstanceMethodNamingConvention") -public class ProjectionRepositoryShould { +public class ProjectionRepositoryShould extends AbstractEntityRepositoryShould { private static final ProjectId ID = Given.AggregateId.newProjectId(); @@ -207,8 +211,33 @@ public void catches_up_from_EventStorage() { assertTrue(TestProjection.processed(Events.getMessage(projectStartedEvent))); } + @Override + protected EntityRepository repository() { + return repository; + } + + @Override + protected TestProjection entity() { + final TestProjection projection = new TestProjection(ProjectId.newBuilder().setId("single-test-projection").build()); + return projection; + } + + @Override + protected List entities(int count) { + final List projections = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final TestProjection projection = new TestProjection( + ProjectId.newBuilder().setId(String.format("test-projection-%s", i)).build()); + + projections.add(projection); + } + + return projections; + } + /** The projection stub used in tests. */ - private static class TestProjection extends Projection { + /* package */ static class TestProjection extends Projection { /** The event message history we store for inspecting in delivery tests. */ private static final Multimap eventMessagesDelivered = HashMultimap.create(); From 6f8bd40b121a99b29015deec407070b90a3e4c62 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 12:24:22 +0300 Subject: [PATCH 234/361] Fix nullable collection values issue. --- .../server/entity/EntityRepository.java | 5 +++ .../storage/memory/InMemoryRecordStorage.java | 20 ++++++++---- .../AbstractEntityRepositoryShould.java | 32 ++++++++++++++++++- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index de7dcf9d971..2c720e0449b 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -172,6 +172,11 @@ public ImmutableCollection findBulk(Iterable ids, FieldMask fieldMask) { while (idIterator.hasNext() && recordIterator.hasNext()) { final I id = idIterator.next(); final EntityStorageRecord record = recordIterator.next(); + + if (record == null) { // Record is nullable here since RecordStorage::findBulk returns an Iterable that may contain nulls. + continue; + } + final E entity = toEntity(id, record, fieldMask); entities.add(entity); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 624a67f2d6d..c25f381851e 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -72,8 +72,12 @@ protected Iterable readBulkInternal(final Iterable given TypeUrl typeUrl = null; - for (I recordId : storage.keySet()) { - for (I givenId : givenIds) { + int resultExpectedSize = 0; + + for (I givenId : givenIds) { + resultExpectedSize++; + + for (I recordId : storage.keySet()) { if (recordId.equals(givenId)) { final EntityStorageRecord.Builder matchingRecord = storage.get(recordId).toBuilder(); final Any state = matchingRecord.getState(); @@ -88,10 +92,14 @@ protected Iterable readBulkInternal(final Iterable given matchingRecord.setState(processed); - result.add(matchingRecord.build()); } -// } else { -// result.add(null); -// } + result.add(matchingRecord.build()); + } + } + + // If no record was found + if (result.size() < resultExpectedSize) { + result.add(null); + resultExpectedSize++; } } return result; diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 0bc312f8bb1..116e4dcf0ff 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -68,7 +68,37 @@ public void find_multiple_entities_by_ids() { final Collection found = repo.findBulk(ids); - assertSize(count / 2, found); + assertSize(ids.size(), found); + + for (E entity : found) { + assertContains(entity, entities); + } + } + + @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + @Test + public void handle_wrong_passed_ids() { + final EntityRepository repo = repository(); + + final int count = 10; + final List entities = entities(count); + + for (E entity : entities) { + repo.store(entity); + } + + final List ids = new LinkedList<>(); + for (int i = 0; i < count; i++) { + ids.add(entities.get(i).getId()); + } + + final Entity sideEntity = entity(); + + ids.add(sideEntity.getId()); + + final Collection found = repo.findBulk(ids); + + assertSize(ids.size() - 1, found); // Check we've found all existing items for (E entity : found) { assertContains(entity, entities); From 3f059a2ee1ba5726c42290cf0ebc0499a0a0dbbc Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 13:08:12 +0300 Subject: [PATCH 235/361] Add test for EntityRepository::findAll(EntityFilters, FieldMask). --- .../AbstractEntityRepositoryShould.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 116e4dcf0ff..915c8242312 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -20,7 +20,14 @@ package org.spine3.server.entity; +import com.google.protobuf.Descriptors; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; import org.junit.Test; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; +import org.spine3.protobuf.AnyPacker; import java.util.Collection; import java.util.LinkedList; @@ -105,6 +112,58 @@ public void handle_wrong_passed_ids() { } } + + @SuppressWarnings({ "MethodWithMultipleLoops", "unchecked"}) + @Test + public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { + final EntityRepository repo = repository(); + + final int count = 10; + final List entities = entities(count); + + for (E entity : entities) { + repo.store(entity); + } + + final List ids = new LinkedList<>(); + for (int i = 0; i < count / 2; i++) { + final EntityId id = EntityId.newBuilder() + .setId(AnyPacker.pack((Message) entities.get(i).getId())) + .build(); + ids.add(id); + } + + final EntityIdFilter filter = EntityIdFilter.newBuilder().addAllIds(ids).build(); + final EntityFilters filters = EntityFilters.newBuilder() + .setIdFilter(filter) + .build(); + + final String firstFieldName = entities.get(0).getState().getDescriptorForType().getFields().get(0).getFullName(); + + final FieldMask firstFieldOnly = FieldMask.newBuilder() + .addPaths(firstFieldName).build(); + + final Iterable readEntities = repo.findAll(filters, firstFieldOnly); + + assertSize(ids.size(), readEntities); + + for (E entity : readEntities) { + assertMatches(entity, firstFieldOnly); + } + } + + + private void assertMatches(E entity, FieldMask fieldMask) { + final Message state = entity.getState(); + final List paths = fieldMask.getPathsList(); + + for (Descriptors.FieldDescriptor field : state.getDescriptorForType().getFields()) { + if (field.isRepeated()) continue; + + assertEquals(state.hasField(field), paths.contains(field.getFullName())); + } + } + protected abstract EntityRepository repository(); protected abstract E entity(); From 9d7c432eb74251bfd60d0e833ec315b7e782f686 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 15:09:26 +0300 Subject: [PATCH 236/361] Clarify the behaviour of "readBulk(...)" method in the JavaDoc section. --- .../server/storage/BulkStorageOperationsMixin.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java index b8c5e6690b5..510fa76743b 100644 --- a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java +++ b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java @@ -42,15 +42,19 @@ * *

        The size of {@link Iterable} returned is always the same as the size of given IDs. * - *

        In case there is no record for a particular ID, {@code null} will be present in the result. + *

        In case there is no record for a particular ID, {@code null} will be present in the result instead. + * In this way {@code readBulk()} callees are able to track the absenсe of a certain element by comparing + * the input IDs and resulting {@code Iterable}. + * + *

        E.g. {@code readBulk( Lists.newArrayList(idPresentInStorage, idNonPresentInStorage) )} will return + * an {@code Iterable} with two elements, first of which is non-null and the second is null. * * @param ids record IDs of interest * @return the {@link Iterable} containing the records matching the given IDs * @throws IllegalStateException if the storage was closed before */ @CheckReturnValue - Iterable - readBulk(Iterable ids); + Iterable readBulk(Iterable ids); /** From 8a10babd1ab74baad04b38286978a307864eff04 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 17:16:35 +0300 Subject: [PATCH 237/361] Cleanup the QueryResponse documentation. --- client/src/main/proto/spine/client/query.proto | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/main/proto/spine/client/query.proto b/client/src/main/proto/spine/client/query.proto index 59fd56e0ae5..3b8cb2c0a0c 100644 --- a/client/src/main/proto/spine/client/query.proto +++ b/client/src/main/proto/spine/client/query.proto @@ -45,8 +45,8 @@ message QueryContext { // The main abstraction over the read-side API. // -// Allows clients to form the requests to the read-side through the QueryService. -// Query execution typically results in a QueryResponse object. +// Allows clients to form the requests to the read-side through the `QueryService`. +// `Query` execution typically results in a QueryResponse object. message Query { // Defines the entity of interest, e.g. entity type URL and a set of fetch criteria. @@ -59,18 +59,18 @@ message Query { reserved 3 to 6; } -// The result of Query processing in the read-side implementation. +// The result of `Query` processing. // // Contains the actual processing results and other response attributes. // Used as a result of {@code QueryService#Read(Query)} gRPC method call. message QueryResponse { - // Represents the base part of the response. I.e. whether the Query has been acked or not. + // Represents the base part of the response. I.e. whether the `Query` has been acked or not. base.Response response = 1; // Reserved for more query response attributes, e.g. to describe paginated response etc. reserved 2 to 4; - // Entity states (each packed as Any) returned to the API user as a result of Query execution. + // Entity states (each packed as `Any`) returned to the API user as a result of Query execution. repeated google.protobuf.Any messages = 5; } From dc4dbaaffa5403a7330e950a7443dab35d7a44ea Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 17:23:08 +0300 Subject: [PATCH 238/361] [Code style]: Adjust IDEA settings to keep 1 blank line max (in code and before the closing curly brace). --- .idea/codeStyleSettings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 691bf49884f..a321dd64d78 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -11,6 +11,8 @@

        Same as calling

        {@code findBulk(id, FieldMask.getDefaultInstance());}
        + *

        Provides a convenience wrapper around multiple invocations of {@link #find(Object)}. Descendants may + * optimize the execution of this method, choosing the most suitable way for the particular storage engine used. + * + *

        The result only contains those entities which IDs are contained inside the passed {@code ids}. + * The resulting collection is always returned with no {@code null} values. + * + *

        The order of objects in the result is not guaranteed to be the same as the order of IDs passed as argument. + * + *

        In case IDs contain duplicates, the result may also contain duplicates, depending on particular + * implementation. + * + *

        Similar to {@link #find(Object)}, the invocation of this method never leads to creation of new objects. + * + *

        NOTE: The storage must be assigned before calling this method. * * @param ids entity IDs to search for - * @return all the entities in this repository with the IDs contained in the given {@code ids} - * @see #findBulk(Iterable, FieldMask) + * @return all the entities in this repository with the IDs matching the given {@code Iterable} */ @CheckReturnValue public ImmutableCollection findBulk(Iterable ids) { @@ -139,26 +151,19 @@ public ImmutableCollection findBulk(Iterable ids) { } /** - * Finds all the entities in this repository with IDs, contained within the passed {@code Iterable}. - * - *

        Provides a convenience wrapper around multiple invocations of {@link #find(Object)}. Descendants may - * optimize the execution of this method, choosing the most suitable way for the particular storage engine used. - * - *

        The order of resulting objects in the {@link Iterable} is not guaranteed to be the same as the order - * of IDs passed as argument. + * Finds all the entities in this repository by their IDs and applies the {@link FieldMask} to each of them. * - *

        The instance of {@code Iterable} is always returned with no {@code null} values. + *

        Acts in the same way as {@link #findBulk(Iterable)}, with the {@code FieldMask} applied to the results. * - *

        In case IDs contain duplicates, the resulting {@code Iterable} may also contain duplicates, depending - * on particular implementation. - * - *

        Similar to {@link #find(Object)}, the invocation of this method never leads to creation of new objects. + *

        Field mask must be applied according to + * FieldMask specs. * *

        NOTE: The storage must be assigned before calling this method. @@ -206,7 +206,10 @@ public E apply(@Nullable Map.Entry input) { } /** - * Find all the entities passing the given filters. + * Finds all the entities passing the given filters and applies the given {@link FieldMask} to the results. + * + *

        Field mask is applied according to + * FieldMask specs. @@ -145,10 +145,10 @@ public ImmutableCollection findBulk(Iterable ids) { * @param ids entity IDs to search for * @param fieldMask mask to apply on entities * @return all the entities in this repository with the IDs contained in the given {@code ids} - * @see #findBulk(Iterable) + * @see #loadAll(Iterable) */ @CheckReturnValue - public ImmutableCollection findBulk(Iterable ids, FieldMask fieldMask) { + public ImmutableCollection loadAll(Iterable ids, FieldMask fieldMask) { final RecordStorage storage = recordStorage(); final Iterable entityStorageRecords = storage.readBulk(ids); @@ -230,7 +230,7 @@ public I apply(@Nullable EntityId input) { } }); - final ImmutableCollection result = findBulk(domainIds, fieldMask); + final ImmutableCollection result = loadAll(domainIds, fieldMask); return result; } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 26eb78d02f3..6520ccd484a 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -838,7 +838,7 @@ private static void setupExpectedFindAllBehaviour(Map sample final Iterable matchingIds = argThat(projectionIdsIterableMatcher(projectIds)); - when(projectionRepository.findBulk(matchingIds, any(FieldMask.class))).thenReturn(allResults); + when(projectionRepository.loadAll(matchingIds, any(FieldMask.class))).thenReturn(allResults); when(projectionRepository.findAll()).thenReturn(allResults); From 447dbcd55a135ac602502d9b0558c78361ed14b5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 19:41:41 +0300 Subject: [PATCH 247/361] Rename `EntityRepository#findAll()` to `#loadAll()` in order to make API uniform; migrate calls accordingly. --- .../main/java/org/spine3/server/entity/EntityRepository.java | 2 +- server/src/main/java/org/spine3/server/stand/Stand.java | 2 +- server/src/test/java/org/spine3/server/QueryServiceShould.java | 2 +- server/src/test/java/org/spine3/server/stand/StandShould.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 98a91774780..3edbcf0bcae 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -167,7 +167,7 @@ public ImmutableCollection loadAll(Iterable ids, FieldMask fieldMask) { } @CheckReturnValue - public ImmutableCollection findAll() { + public ImmutableCollection loadAll() { final RecordStorage storage = recordStorage(); final Map recordMap = storage.readAll(); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index b3a82158ad0..a72b585b265 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -382,7 +382,7 @@ private static ImmutableCollection fetchFromEntityRepository(Q final FieldMask fieldMask = query.getFieldMask(); if (target.getIncludeAll() && fieldMask.getPathsList().isEmpty()) { - result = repository.findAll(); + result = repository.loadAll(); } else { final EntityFilters filters = target.getFilters(); result = repository.findAll(filters, fieldMask); diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index 884260003fc..8caba5e909b 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -142,7 +142,7 @@ public void fail_to_create_with_no_bounded_context() { @Test public void return_error_if_query_failed_to_execute() { - when(projectDetailsRepository.findAll()).thenThrow(RuntimeException.class); + when(projectDetailsRepository.loadAll()).thenThrow(RuntimeException.class); final Query query = Given.Query.readAllProjects(); service.read(query, responseObserver); checkFailureResponse(responseObserver); diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 6520ccd484a..268006f68bb 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -840,7 +840,7 @@ private static void setupExpectedFindAllBehaviour(Map sample when(projectionRepository.loadAll(matchingIds, any(FieldMask.class))).thenReturn(allResults); - when(projectionRepository.findAll()).thenReturn(allResults); + when(projectionRepository.loadAll()).thenReturn(allResults); final EntityFilters matchingFilter = argThat(entityFilterMatcher(projectIds)); when(projectionRepository.findAll(matchingFilter, any(FieldMask.class))).thenReturn(allResults); From 40c9a08e0599f2f8451f933718081491702c2550 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 19:43:27 +0300 Subject: [PATCH 248/361] Update JavaDocs according to the renamed methods meaning. --- .../org/spine3/server/entity/EntityRepository.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 3edbcf0bcae..0dd9657f351 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -109,7 +109,7 @@ public E load(I id) { } /** - * Finds all the entities in this repository with IDs, contained within the passed {@code ids} values. + * Loads all the entities in this repository with IDs, contained within the passed {@code ids} values. * *

        Provides a convenience wrapper around multiple invocations of {@link #load(Object)}. Descendants may * optimize the execution of this method, choosing the most suitable way for the particular storage engine used. @@ -133,7 +133,7 @@ public ImmutableCollection loadAll(Iterable ids) { } /** - * Finds all the entities in this repository by their IDs and applies the {@link FieldMask} to each of them. + * Loads all the entities in this repository by their IDs and applies the {@link FieldMask} to each of them. * *

        Acts in the same way as {@link #loadAll(Iterable)}, with the {@code FieldMask} applied to the results. * @@ -166,6 +166,14 @@ public ImmutableCollection loadAll(Iterable ids, FieldMask fieldMask) { return ImmutableList.copyOf(entities); } + /** + * Loads all the entities in this repository. + * + *

        NOTE: The storage must be assigned before calling this method. + * + * @return all the entities in this repository + * @see #loadAll(Iterable) + */ @CheckReturnValue public ImmutableCollection loadAll() { final RecordStorage storage = recordStorage(); From d134c2635800f42b98ad8df4f01b62bedf33d4d3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 19:48:31 +0300 Subject: [PATCH 249/361] * Rename `EntityRepository#findAll(EntityFilters, FieldMask)` to `find(EntityFilters, FieldMask)` to distinguish from `loadAll(...)` methods. * Update method usages according to the new naming. * Describe the `EntityIdFilter` behaviour for the `find(...)` method and relate it to `loadAll(ids, ...)`. --- .../main/java/org/spine3/server/entity/EntityRepository.java | 4 +++- server/src/main/java/org/spine3/server/stand/Stand.java | 2 +- server/src/test/java/org/spine3/server/stand/StandShould.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 0dd9657f351..72ce25448af 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -203,6 +203,8 @@ public E apply(@Nullable Map.Entry input) { * *

        At this point only {@link org.spine3.client.EntityIdFilter} is supported. All other filters are ignored. * + *

        Filtering by IDs set via {@code EntityIdFilter} is performed in the same way as by {@link #loadAll(Iterable)}. + * *

        NOTE: The storage must be assigned before calling this method. * * @param filters entity filters @@ -210,7 +212,7 @@ public E apply(@Nullable Map.Entry input) { * @return all the entities in this repository passed the filters. */ @CheckReturnValue - public ImmutableCollection findAll(EntityFilters filters, FieldMask fieldMask) { + public ImmutableCollection find(EntityFilters filters, FieldMask fieldMask) { final List idsList = filters.getIdFilter() .getIdsList(); final Class expectedIdClass = getIdClass(); diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index a72b585b265..647bded2655 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -385,7 +385,7 @@ private static ImmutableCollection fetchFromEntityRepository(Q result = repository.loadAll(); } else { final EntityFilters filters = target.getFilters(); - result = repository.findAll(filters, fieldMask); + result = repository.find(filters, fieldMask); } return result; } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 268006f68bb..5294e4b6e79 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -843,7 +843,7 @@ private static void setupExpectedFindAllBehaviour(Map sample when(projectionRepository.loadAll()).thenReturn(allResults); final EntityFilters matchingFilter = argThat(entityFilterMatcher(projectIds)); - when(projectionRepository.findAll(matchingFilter, any(FieldMask.class))).thenReturn(allResults); + when(projectionRepository.find(matchingFilter, any(FieldMask.class))).thenReturn(allResults); } @SuppressWarnings("OverlyComplexAnonymousInnerClass") From 1f17a8d18fa5a302c790ce35805b961f35c0033c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 19:59:39 +0300 Subject: [PATCH 250/361] Create a test for `Queries` utility class. --- .../java/org/spine3/base/QueriesShould.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 client/src/test/java/org/spine3/base/QueriesShould.java diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java new file mode 100644 index 00000000000..338352ca170 --- /dev/null +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -0,0 +1,39 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.base; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.spine3.test.Tests.hasPrivateUtilityConstructor; + +/** + * @author Alex Tymchenko + */ +public class QueriesShould { + + @Test + public void have_private_constructor() { + assertTrue(hasPrivateUtilityConstructor(Queries.class)); + } + +} From ffb4ce0a4d0a5b33c5ed8b3bf28c5adcb9d07b34 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 20:19:29 +0300 Subject: [PATCH 251/361] Use Guava's `Maps#newHashMap()` in favour of `new HashMap()` to keep the codebase uniform. --- server/src/main/java/org/spine3/server/stand/Stand.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 647bded2655..b12f52ab82b 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -70,6 +70,7 @@ import java.util.concurrent.Executor; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Maps.newHashMap; /** * A container for storing the lastest {@link org.spine3.server.aggregate.Aggregate} states. @@ -522,8 +523,8 @@ public Stand build() { *

        Responsible for {@link Subscription} object instantiation. */ private static final class StandSubscriptionRegistry { - private final Map> typeToAttrs = new HashMap<>(); - private final Map subscriptionToAttrs = new HashMap<>(); + private final Map> typeToAttrs = newHashMap(); + private final Map subscriptionToAttrs = newHashMap(); private synchronized void activate(Subscription subscription, StandUpdateCallback callback) { if (!subscriptionToAttrs.containsKey(subscription)) { From ac7eefcba6114be395e295dc7c73a6c9275e3fb1 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 20:22:06 +0300 Subject: [PATCH 252/361] Cover `Queries#readAll()` with tests. --- .../java/org/spine3/base/QueriesShould.java | 76 +++++++++++++++++++ .../proto/spine/test/queries_should.proto | 50 ++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 client/src/test/proto/spine/test/queries_should.proto diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java index 338352ca170..f759bca477e 100644 --- a/client/src/test/java/org/spine3/base/QueriesShould.java +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -21,14 +21,24 @@ */ package org.spine3.base; +import com.google.protobuf.FieldMask; +import com.google.protobuf.ProtocolStringList; import org.junit.Test; +import org.spine3.client.EntityFilters; +import org.spine3.client.Query; +import org.spine3.client.Target; +import org.spine3.protobuf.TypeUrl; +import org.spine3.test.queries.TestEntity; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.spine3.test.Tests.hasPrivateUtilityConstructor; /** * @author Alex Tymchenko */ +@SuppressWarnings("LocalVariableNamingConvention") public class QueriesShould { @Test @@ -36,4 +46,70 @@ public void have_private_constructor() { assertTrue(hasPrivateUtilityConstructor(Queries.class)); } + @Test + public void compose_proper_read_all_query() { + final Class targetEntityClass = TestEntity.class; + final Query readAllQuery = Queries.readAll(targetEntityClass); + assertNotNull(readAllQuery); + + checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllQuery); + + // `FieldMask` must be default as `paths` were not set. + final FieldMask fieldMask = readAllQuery.getFieldMask(); + assertNotNull(fieldMask); + assertEquals(FieldMask.getDefaultInstance(), fieldMask); + } + + @Test + public void compose_proper_read_all_query_with_single_path() { + final Class targetEntityClass = TestEntity.class; + final String firstPropertyFieldName = TestEntity.getDescriptor() + .getFields() + .get(1) + .getFullName(); + final Query readAllWithPathFilteringQuery = Queries.readAll(targetEntityClass, firstPropertyFieldName); + assertNotNull(readAllWithPathFilteringQuery); + + checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllWithPathFilteringQuery); + + final FieldMask fieldMask = readAllWithPathFilteringQuery.getFieldMask(); + assertEquals(1, fieldMask.getPathsCount()); // as we set the only path value. + + final String firstPath = fieldMask.getPaths(0); + assertEquals(firstPropertyFieldName, firstPath); + } + + @Test + public void compose_proper_read_all_query_with_multiple_paths() { + final Class targetEntityClass = TestEntity.class; + + final String[] paths = {"some", "random", "paths"}; + final Query readAllWithPathFilteringQuery = Queries.readAll(targetEntityClass, paths); + assertNotNull(readAllWithPathFilteringQuery); + + checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllWithPathFilteringQuery); + + final FieldMask fieldMask = readAllWithPathFilteringQuery.getFieldMask(); + assertEquals(paths.length, fieldMask.getPathsCount()); + final ProtocolStringList pathsList = fieldMask.getPathsList(); + for (String expectedPath : paths) { + assertTrue(pathsList.contains(expectedPath)); + } + } + + + private static void checkTypeCorrectAndFiltersEmpty(Class targetEntityClass, Query readAllQuery) { + final Target entityTarget = readAllQuery.getTarget(); + assertNotNull(entityTarget); + + final String expectedTypeName = TypeUrl.of(targetEntityClass) + .getTypeName(); + assertEquals(expectedTypeName, entityTarget.getType()); + + // `EntityFilters` must be default as this value was not set. + final EntityFilters filters = entityTarget.getFilters(); + assertNotNull(filters); + assertEquals(EntityFilters.getDefaultInstance(), filters); + } + } diff --git a/client/src/test/proto/spine/test/queries_should.proto b/client/src/test/proto/spine/test/queries_should.proto new file mode 100644 index 00000000000..ec38a46decd --- /dev/null +++ b/client/src/test/proto/spine/test/queries_should.proto @@ -0,0 +1,50 @@ +// +// Copyright 2016, TeamDev Ltd. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +syntax = "proto3"; + +package spine.test.queries; + +option (type_url_prefix) = "type.spine3.org"; +option java_package="org.spine3.test.queries"; +option java_multiple_files = true; +option java_outer_classname = "QueriesShouldProto"; + + +import "spine/annotations.proto"; + +// Only for usage in `QueriesShould` class. + +// Simple ID for tests. +message TestEntityId { + + // Numeric value wrapped into this ID. + int32 value = 1; +} + +// Test entity used as a `Target` for queries. +message TestEntity { + + // Entity ID. + TestEntityId id = 1; + + string first_field = 2; + + bool second_field = 3; +} From 158ede1c5eee2a6b5f387eb2422b70a810c0c7f2 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 4 Oct 2016 21:17:11 +0300 Subject: [PATCH 253/361] Cover one of `Queries#readByIds()` cases with tests. --- .../java/org/spine3/base/QueriesShould.java | 71 ++++++++++++++++--- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java index f759bca477e..54766ca1245 100644 --- a/client/src/test/java/org/spine3/base/QueriesShould.java +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -25,11 +25,19 @@ import com.google.protobuf.ProtocolStringList; import org.junit.Test; import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; import org.spine3.client.Target; +import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; import org.spine3.test.queries.TestEntity; +import org.spine3.test.queries.TestEntityId; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -38,7 +46,7 @@ /** * @author Alex Tymchenko */ -@SuppressWarnings("LocalVariableNamingConvention") +@SuppressWarnings({"LocalVariableNamingConvention", "MagicNumber"}) public class QueriesShould { @Test @@ -52,12 +60,11 @@ public void compose_proper_read_all_query() { final Query readAllQuery = Queries.readAll(targetEntityClass); assertNotNull(readAllQuery); + // `EntityFilters` must be default as this value was not set. checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllQuery); // `FieldMask` must be default as `paths` were not set. - final FieldMask fieldMask = readAllQuery.getFieldMask(); - assertNotNull(fieldMask); - assertEquals(FieldMask.getDefaultInstance(), fieldMask); + checkFieldMaskEmpty(readAllQuery); } @Test @@ -97,19 +104,61 @@ public void compose_proper_read_all_query_with_multiple_paths() { } } + @Test + public void compose_proper_read_by_ids_query() { + final Class targetEntityClass = TestEntity.class; - private static void checkTypeCorrectAndFiltersEmpty(Class targetEntityClass, Query readAllQuery) { - final Target entityTarget = readAllQuery.getTarget(); - assertNotNull(entityTarget); + final Set testEntityIds = newHashSet(TestEntityId.newBuilder() + .setValue(1) + .build(), + TestEntityId.newBuilder() + .setValue(7) + .build(), + TestEntityId.newBuilder() + .setValue(15) + .build() + ); + final Query readByIdsQuery = Queries.readByIds(targetEntityClass, testEntityIds); + assertNotNull(readByIdsQuery); + + checkFieldMaskEmpty(readByIdsQuery); + + final Target target = checkTarget(targetEntityClass, readByIdsQuery); + final EntityFilters filters = target.getFilters(); + assertNotNull(filters); + final EntityIdFilter idFilter = filters.getIdFilter(); + assertNotNull(idFilter); + final List actualListOfIds = idFilter.getIdsList(); + for (TestEntityId testEntityId : testEntityIds) { + final EntityId expectedEntityId = EntityId.newBuilder() + .setId(AnyPacker.pack(testEntityId)) + .build(); + assertTrue(actualListOfIds.contains(expectedEntityId)); + } + } - final String expectedTypeName = TypeUrl.of(targetEntityClass) - .getTypeName(); - assertEquals(expectedTypeName, entityTarget.getType()); + private static void checkFieldMaskEmpty(Query query) { + final FieldMask fieldMask = query.getFieldMask(); + assertNotNull(fieldMask); + assertEquals(FieldMask.getDefaultInstance(), fieldMask); + } + + private static void checkTypeCorrectAndFiltersEmpty(Class expectedTargetClass, Query query) { + final Target entityTarget = checkTarget(expectedTargetClass, query); - // `EntityFilters` must be default as this value was not set. final EntityFilters filters = entityTarget.getFilters(); assertNotNull(filters); assertEquals(EntityFilters.getDefaultInstance(), filters); } + private static Target checkTarget(Class targetEntityClass, Query query) { + final Target entityTarget = query.getTarget(); + assertNotNull(entityTarget); + + final String expectedTypeName = TypeUrl.of(targetEntityClass) + .getTypeName(); + assertEquals(expectedTypeName, entityTarget.getType()); + return entityTarget; + } + } From 8956f6f0b63e25efff59225c52c9b073cfd3c59c Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 11:27:53 +0300 Subject: [PATCH 254/361] Add common entity repository tests. --- .../storage/memory/InMemoryRecordStorage.java | 10 +-- .../AbstractEntityRepositoryShould.java | 83 +++++++++++++++++++ .../ProcessManagerRepositoryShould.java | 38 ++++++++- 3 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index b2635d29091..624a67f2d6d 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -88,10 +88,10 @@ protected Iterable readBulkInternal(final Iterable given matchingRecord.setState(processed); - result.add(matchingRecord.build()); - } else { - result.add(null); - } + result.add(matchingRecord.build()); } +// } else { +// result.add(null); +// } } } return result; @@ -99,7 +99,7 @@ protected Iterable readBulkInternal(final Iterable given @Override protected Iterable readBulkInternal(Iterable ids) { - return readBulkInternal(ids, null); + return readBulkInternal(ids, FieldMask.getDefaultInstance()); } @Override diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java new file mode 100644 index 00000000000..0bc312f8bb1 --- /dev/null +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.entity; + +import org.junit.Test; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.spine3.test.Verify.assertContains; +import static org.spine3.test.Verify.assertSize; + +/** + * @author Dmytro Dashenkov + */ +public abstract class AbstractEntityRepositoryShould> { + + @SuppressWarnings("unchecked") + @Test + public void find_single_entity_by_id() { + final EntityRepository repo = repository(); + + final E entity = entity(); + + repo.store(entity); + + final Entity found = repo.find(entity.getId()); + + assertEquals(found, entity); + } + + @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + @Test + public void find_multiple_entities_by_ids() { + final EntityRepository repo = repository(); + + final int count = 10; + final List entities = entities(count); + + for (E entity : entities) { + repo.store(entity); + } + + final List ids = new LinkedList<>(); + for (int i = 0; i < count / 2; i++) { + ids.add(entities.get(i).getId()); + } + + final Collection found = repo.findBulk(ids); + + assertSize(count / 2, found); + + for (E entity : found) { + assertContains(entity, entities); + } + } + + protected abstract EntityRepository repository(); + + protected abstract E entity(); + + protected abstract List entities(int count); +} diff --git a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java index 0e803b066cf..c388fc1ad59 100644 --- a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java @@ -38,6 +38,8 @@ import org.spine3.server.BoundedContext; import org.spine3.server.command.Assign; import org.spine3.server.command.CommandDispatcher; +import org.spine3.server.entity.AbstractEntityRepositoryShould; +import org.spine3.server.entity.EntityRepository; import org.spine3.server.entity.IdFunction; import org.spine3.server.event.EventBus; import org.spine3.server.event.GetProducerIdFromEvent; @@ -59,6 +61,8 @@ import javax.annotation.Nonnull; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -74,7 +78,7 @@ * @author Alexander Litus */ @SuppressWarnings("InstanceMethodNamingConvention") -public class ProcessManagerRepositoryShould { +public class ProcessManagerRepositoryShould extends AbstractEntityRepositoryShould { private static final ProjectId ID = Given.AggregateId.newProjectId(); @@ -205,6 +209,36 @@ public void return_event_classes() { assertTrue(eventClasses.contains(EventClass.of(ProjectStarted.class))); } + @Override + protected EntityRepository repository() { + final TestProcessManagerRepository repo = new TestProcessManagerRepository( + TestBoundedContextFactory.newBoundedContext()); + + repo.initStorage(InMemoryStorageFactory.getInstance()); + + return repo; + } + + @Override + protected TestProcessManager entity() { + final ProjectId id = ProjectId.newBuilder().setId("123-id").build(); + return new TestProcessManager(id); + } + + @Override + protected List entities(int count) { + final List procmans = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + final ProjectId id = ProjectId.newBuilder().setId(String.format("procman-number-%s", i)).build(); + + procmans.add(new TestProcessManager(id)); + } + + return procmans; + } + + private static class TestProcessManagerRepository extends ProcessManagerRepository { @@ -218,7 +252,7 @@ private TestProcessManagerRepository(BoundedContext boundedContext) { } } - private static class TestProcessManager extends ProcessManager { + /* package */ static class TestProcessManager extends ProcessManager { /** The event message we store for inspecting in delivery tests. */ private static final Multimap messagesDelivered = HashMultimap.create(); From 3a1a29b658a0fad804aa86fc6238e08cf9f8c735 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 11:39:48 +0300 Subject: [PATCH 255/361] Add implementation for ProjectionRepository. --- .../ProjectionRepositoryShould.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java index 5cdf02044dc..09e26780e39 100644 --- a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java @@ -30,6 +30,8 @@ import org.spine3.base.EventContext; import org.spine3.base.Events; import org.spine3.server.BoundedContext; +import org.spine3.server.entity.AbstractEntityRepositoryShould; +import org.spine3.server.entity.EntityRepository; import org.spine3.server.event.EventStore; import org.spine3.server.event.Subscribe; import org.spine3.server.storage.RecordStorage; @@ -41,6 +43,8 @@ import org.spine3.test.projection.event.ProjectStarted; import org.spine3.test.projection.event.TaskAdded; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -59,7 +63,7 @@ * @author Alexander Litus */ @SuppressWarnings("InstanceMethodNamingConvention") -public class ProjectionRepositoryShould { +public class ProjectionRepositoryShould extends AbstractEntityRepositoryShould { private static final ProjectId ID = Given.AggregateId.newProjectId(); @@ -207,8 +211,33 @@ public void catches_up_from_EventStorage() { assertTrue(TestProjection.processed(Events.getMessage(projectStartedEvent))); } + @Override + protected EntityRepository repository() { + return repository; + } + + @Override + protected TestProjection entity() { + final TestProjection projection = new TestProjection(ProjectId.newBuilder().setId("single-test-projection").build()); + return projection; + } + + @Override + protected List entities(int count) { + final List projections = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final TestProjection projection = new TestProjection( + ProjectId.newBuilder().setId(String.format("test-projection-%s", i)).build()); + + projections.add(projection); + } + + return projections; + } + /** The projection stub used in tests. */ - private static class TestProjection extends Projection { + /* package */ static class TestProjection extends Projection { /** The event message history we store for inspecting in delivery tests. */ private static final Multimap eventMessagesDelivered = HashMultimap.create(); From 6514e041cf63a1a87174dcb0c948c2ee9d637a7a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 12:24:22 +0300 Subject: [PATCH 256/361] Fix nullable collection values issue. --- .../server/entity/EntityRepository.java | 5 +++ .../storage/memory/InMemoryRecordStorage.java | 20 ++++++++---- .../AbstractEntityRepositoryShould.java | 32 ++++++++++++++++++- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 72ce25448af..44fe3a6a604 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -159,6 +159,11 @@ public ImmutableCollection loadAll(Iterable ids, FieldMask fieldMask) { while (idIterator.hasNext() && recordIterator.hasNext()) { final I id = idIterator.next(); final EntityStorageRecord record = recordIterator.next(); + + if (record == null) { // Record is nullable here since RecordStorage::findBulk returns an Iterable that may contain nulls. + continue; + } + final E entity = toEntity(id, record, fieldMask); entities.add(entity); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 624a67f2d6d..c25f381851e 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -72,8 +72,12 @@ protected Iterable readBulkInternal(final Iterable given TypeUrl typeUrl = null; - for (I recordId : storage.keySet()) { - for (I givenId : givenIds) { + int resultExpectedSize = 0; + + for (I givenId : givenIds) { + resultExpectedSize++; + + for (I recordId : storage.keySet()) { if (recordId.equals(givenId)) { final EntityStorageRecord.Builder matchingRecord = storage.get(recordId).toBuilder(); final Any state = matchingRecord.getState(); @@ -88,10 +92,14 @@ protected Iterable readBulkInternal(final Iterable given matchingRecord.setState(processed); - result.add(matchingRecord.build()); } -// } else { -// result.add(null); -// } + result.add(matchingRecord.build()); + } + } + + // If no record was found + if (result.size() < resultExpectedSize) { + result.add(null); + resultExpectedSize++; } } return result; diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 0bc312f8bb1..116e4dcf0ff 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -68,7 +68,37 @@ public void find_multiple_entities_by_ids() { final Collection found = repo.findBulk(ids); - assertSize(count / 2, found); + assertSize(ids.size(), found); + + for (E entity : found) { + assertContains(entity, entities); + } + } + + @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + @Test + public void handle_wrong_passed_ids() { + final EntityRepository repo = repository(); + + final int count = 10; + final List entities = entities(count); + + for (E entity : entities) { + repo.store(entity); + } + + final List ids = new LinkedList<>(); + for (int i = 0; i < count; i++) { + ids.add(entities.get(i).getId()); + } + + final Entity sideEntity = entity(); + + ids.add(sideEntity.getId()); + + final Collection found = repo.findBulk(ids); + + assertSize(ids.size() - 1, found); // Check we've found all existing items for (E entity : found) { assertContains(entity, entities); From 9a1225cd1242f3f01c9e1d50fcf55aaece505e45 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 4 Oct 2016 13:08:12 +0300 Subject: [PATCH 257/361] Add test for EntityRepository::findAll(EntityFilters, FieldMask). --- .../AbstractEntityRepositoryShould.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 116e4dcf0ff..915c8242312 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -20,7 +20,14 @@ package org.spine3.server.entity; +import com.google.protobuf.Descriptors; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; import org.junit.Test; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; +import org.spine3.protobuf.AnyPacker; import java.util.Collection; import java.util.LinkedList; @@ -105,6 +112,58 @@ public void handle_wrong_passed_ids() { } } + + @SuppressWarnings({ "MethodWithMultipleLoops", "unchecked"}) + @Test + public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { + final EntityRepository repo = repository(); + + final int count = 10; + final List entities = entities(count); + + for (E entity : entities) { + repo.store(entity); + } + + final List ids = new LinkedList<>(); + for (int i = 0; i < count / 2; i++) { + final EntityId id = EntityId.newBuilder() + .setId(AnyPacker.pack((Message) entities.get(i).getId())) + .build(); + ids.add(id); + } + + final EntityIdFilter filter = EntityIdFilter.newBuilder().addAllIds(ids).build(); + final EntityFilters filters = EntityFilters.newBuilder() + .setIdFilter(filter) + .build(); + + final String firstFieldName = entities.get(0).getState().getDescriptorForType().getFields().get(0).getFullName(); + + final FieldMask firstFieldOnly = FieldMask.newBuilder() + .addPaths(firstFieldName).build(); + + final Iterable readEntities = repo.findAll(filters, firstFieldOnly); + + assertSize(ids.size(), readEntities); + + for (E entity : readEntities) { + assertMatches(entity, firstFieldOnly); + } + } + + + private void assertMatches(E entity, FieldMask fieldMask) { + final Message state = entity.getState(); + final List paths = fieldMask.getPathsList(); + + for (Descriptors.FieldDescriptor field : state.getDescriptorForType().getFields()) { + if (field.isRepeated()) continue; + + assertEquals(state.hasField(field), paths.contains(field.getFullName())); + } + } + protected abstract EntityRepository repository(); protected abstract E entity(); From ffb572a5140d35b75ff62e63633410810a7ebd04 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 11:01:36 +0300 Subject: [PATCH 258/361] Rename methods according to last changes in the base branch. --- .../AbstractEntityRepositoryShould.java | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 915c8242312..fba357407ca 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -51,7 +51,7 @@ public void find_single_entity_by_id() { repo.store(entity); - final Entity found = repo.find(entity.getId()); + final Entity found = repo.load(entity.getId()); assertEquals(found, entity); } @@ -73,7 +73,7 @@ public void find_multiple_entities_by_ids() { ids.add(entities.get(i).getId()); } - final Collection found = repo.findBulk(ids); + final Collection found = repo.loadAll(ids); assertSize(ids.size(), found); @@ -103,7 +103,7 @@ public void handle_wrong_passed_ids() { ids.add(sideEntity.getId()); - final Collection found = repo.findBulk(ids); + final Collection found = repo.loadAll(ids); assertSize(ids.size() - 1, found); // Check we've found all existing items @@ -112,8 +112,7 @@ public void handle_wrong_passed_ids() { } } - - @SuppressWarnings({ "MethodWithMultipleLoops", "unchecked"}) + @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) @Test public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { final EntityRepository repo = repository(); @@ -128,22 +127,31 @@ public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { final List ids = new LinkedList<>(); for (int i = 0; i < count / 2; i++) { final EntityId id = EntityId.newBuilder() - .setId(AnyPacker.pack((Message) entities.get(i).getId())) + .setId(AnyPacker.pack((Message) entities.get(i) + .getId())) .build(); ids.add(id); } - final EntityIdFilter filter = EntityIdFilter.newBuilder().addAllIds(ids).build(); + final EntityIdFilter filter = EntityIdFilter.newBuilder() + .addAllIds(ids) + .build(); final EntityFilters filters = EntityFilters.newBuilder() - .setIdFilter(filter) - .build(); + .setIdFilter(filter) + .build(); - final String firstFieldName = entities.get(0).getState().getDescriptorForType().getFields().get(0).getFullName(); + final String firstFieldName = entities.get(0) + .getState() + .getDescriptorForType() + .getFields() + .get(0) + .getFullName(); final FieldMask firstFieldOnly = FieldMask.newBuilder() - .addPaths(firstFieldName).build(); + .addPaths(firstFieldName) + .build(); - final Iterable readEntities = repo.findAll(filters, firstFieldOnly); + final Iterable readEntities = repo.find(filters, firstFieldOnly); assertSize(ids.size(), readEntities); @@ -152,13 +160,15 @@ public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { } } - private void assertMatches(E entity, FieldMask fieldMask) { final Message state = entity.getState(); final List paths = fieldMask.getPathsList(); - for (Descriptors.FieldDescriptor field : state.getDescriptorForType().getFields()) { - if (field.isRepeated()) continue; + for (Descriptors.FieldDescriptor field : state.getDescriptorForType() + .getFields()) { + if (field.isRepeated()) { + continue; + } assertEquals(state.hasField(field), paths.contains(field.getFullName())); } From a3b01f47b6be89a864affadad217747d2d5d0053 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 11:09:46 +0300 Subject: [PATCH 259/361] Add clarifying comment and make assertion method static. --- .../server/entity/AbstractEntityRepositoryShould.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index fba357407ca..4292a951b3d 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -69,6 +69,8 @@ public void find_multiple_entities_by_ids() { } final List ids = new LinkedList<>(); + + // Find some of the records (half of them in this case) for (int i = 0; i < count / 2; i++) { ids.add(entities.get(i).getId()); } @@ -125,6 +127,8 @@ public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { } final List ids = new LinkedList<>(); + + // Find some of the records (half of them in this case) for (int i = 0; i < count / 2; i++) { final EntityId id = EntityId.newBuilder() .setId(AnyPacker.pack((Message) entities.get(i) @@ -160,7 +164,7 @@ public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { } } - private void assertMatches(E entity, FieldMask fieldMask) { + private static > void assertMatches(E entity, FieldMask fieldMask) { final Message state = entity.getState(); final List paths = fieldMask.getPathsList(); From a8f55d01aec5699884a3177f6935333989a20839 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 11:35:07 +0300 Subject: [PATCH 260/361] Extract common check method. --- .../entity/AbstractEntityRepositoryShould.java | 13 ++----------- .../src/main/java/org/spine3/test/Tests.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 4292a951b3d..64cf1773e6e 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -20,7 +20,6 @@ package org.spine3.server.entity; -import com.google.protobuf.Descriptors; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import org.junit.Test; @@ -28,6 +27,7 @@ import org.spine3.client.EntityId; import org.spine3.client.EntityIdFilter; import org.spine3.protobuf.AnyPacker; +import org.spine3.test.Tests; import java.util.Collection; import java.util.LinkedList; @@ -166,16 +166,7 @@ public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { private static > void assertMatches(E entity, FieldMask fieldMask) { final Message state = entity.getState(); - final List paths = fieldMask.getPathsList(); - - for (Descriptors.FieldDescriptor field : state.getDescriptorForType() - .getFields()) { - if (field.isRepeated()) { - continue; - } - - assertEquals(state.hasField(field), paths.contains(field.getFullName())); - } + Tests.assertMatchesMask(state, fieldMask); } protected abstract EntityRepository repository(); diff --git a/testutil/src/main/java/org/spine3/test/Tests.java b/testutil/src/main/java/org/spine3/test/Tests.java index 6f690ffae09..a89bd4b26d9 100644 --- a/testutil/src/main/java/org/spine3/test/Tests.java +++ b/testutil/src/main/java/org/spine3/test/Tests.java @@ -20,12 +20,17 @@ package org.spine3.test; +import com.google.protobuf.Descriptors; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; import com.google.protobuf.Timestamp; +import org.junit.Assert; import org.spine3.protobuf.Timestamps; import org.spine3.users.UserId; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; +import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; @@ -114,6 +119,19 @@ public static UserId newUserId(String value) { .build(); } + public static void assertMatchesMask(Message message, FieldMask fieldMask) { + final List paths = fieldMask.getPathsList(); + + for (Descriptors.FieldDescriptor field : message.getDescriptorForType() + .getFields()) { + if (field.isRepeated()) { + continue; + } + + Assert.assertEquals(message.hasField(field), paths.contains(field.getFullName())); + } + } + /** The provider of current time, which is always the same. */ public static class FrozenMadHatterParty implements Timestamps.Provider { private final Timestamp frozenTime; From 48714b58cefea15d1cda4955b2b125e118c33e66 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 11:35:34 +0300 Subject: [PATCH 261/361] Create FieldMasks test class. --- .../server/entity/FieldMasksShould.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 server/src/test/java/org/spine3/server/entity/FieldMasksShould.java diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java new file mode 100644 index 00000000000..8ebe6e0966c --- /dev/null +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.entity; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.spine3.test.Tests.hasPrivateUtilityConstructor; + +/** + * @author Dmytro Dashenkov + */ +public class FieldMasksShould { + + @Test + public void have_private_constructor() { + assertTrue(hasPrivateUtilityConstructor(FieldMasks.class)); + } + + public void apply_mask_to_single_entity() { + + } +} From cae736f892e69958a4a4e59a94d24bf8fc61d67c Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 11:38:17 +0300 Subject: [PATCH 262/361] Clarify naming. --- .../org/spine3/server/entity/FieldMasks.java | 32 +++++++++---------- .../server/entity/FieldMasksShould.java | 3 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index c1f474e352c..ee9783c0d22 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -54,26 +54,26 @@ private FieldMasks() { * do not affect the execution result. * * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. - * @param entities {@link Message}s to filter. + * @param messages {@link Message}s to filter. * @param typeUrl Type of the {@link Message}s. * @return Non-null unmodifiable {@link Collection} of {@link Message}s of the same type that the input had. */ @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) - public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection entities, TypeUrl typeUrl) { + public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection messages, TypeUrl typeUrl) { final List filtered = new LinkedList<>(); final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(typeUrl); if (filter.isEmpty() || builderClass == null) { - return Collections.unmodifiableCollection(entities); + return Collections.unmodifiableCollection(messages); } try { final Constructor builderConstructor = builderClass.getDeclaredConstructor(); builderConstructor.setAccessible(true); - for (Message wholeMessage : entities) { + for (Message wholeMessage : messages) { final B builder = builderConstructor.newInstance(); for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { @@ -87,7 +87,7 @@ public static Collection appl } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { // If any reflection failure happens, return all the data without any mask applied. - return Collections.unmodifiableCollection(entities); + return Collections.unmodifiableCollection(messages); } return Collections.unmodifiableList(filtered); @@ -100,18 +100,18 @@ public static Collection appl * do not affect the execution result. * * @param mask {@code FieldMask} instance to apply. - * @param entity The {@link Message} to apply given {@code FieldMask} to. + * @param message The {@link Message} to apply given {@code FieldMask} to. * @param typeUrl Type of the {@link Message}. * @return A {@link Message} of the same type as the given one with only selected fields. */ @SuppressWarnings("unchecked") - public static M applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M entity, TypeUrl typeUrl) { + public static M applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M message, TypeUrl typeUrl) { final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(typeUrl); if (filter.isEmpty() || builderClass == null) { - return entity; + return message; } try { @@ -120,16 +120,16 @@ public static M applyMask(@Suppr final B builder = builderConstructor.newInstance(); - for (Descriptors.FieldDescriptor field : entity.getDescriptorForType().getFields()) { + for (Descriptors.FieldDescriptor field : message.getDescriptorForType().getFields()) { if (filter.contains(field.getFullName())) { - builder.setField(field, entity.getField(field)); + builder.setField(field, message.getField(field)); } } return (M) builder.build(); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { - return entity; + return message; } @@ -142,17 +142,17 @@ public static M applyMask(@Suppr * do not affect the execution result. * * @param mask The {@code FieldMask} to apply. - * @param entity The {@link Message} to apply given mask to. + * @param message The {@link Message} to apply given mask to. * @param typeUrl Type of given {@link Message}. * @return A {@link Message} of the same type as the given one with only selected fields - * if the {@code mask} is valid, {@code entity} itself otherwise. + * if the {@code mask} is valid, {@code message} itself otherwise. */ - public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M entity, TypeUrl typeUrl) { + public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M message, TypeUrl typeUrl) { if (!mask.getPathsList().isEmpty()) { - return applyMask(mask, entity, typeUrl); + return applyMask(mask, message, typeUrl); } - return entity; + return message; } @Nullable diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java index 8ebe6e0966c..4d1f1a9eacf 100644 --- a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -35,7 +35,8 @@ public void have_private_constructor() { assertTrue(hasPrivateUtilityConstructor(FieldMasks.class)); } - public void apply_mask_to_single_entity() { + @Test + public void apply_mask_to_single_message() { } } From 76d236e2dec8b69dca31b943ff12ba61e4bd15f6 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 12:56:30 +0300 Subject: [PATCH 263/361] Add tests for FieldMasks. --- .../server/entity/FieldMasksShould.java | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java index 4d1f1a9eacf..776911ce63e 100644 --- a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -20,10 +20,29 @@ package org.spine3.server.entity; +import com.google.protobuf.Descriptors; +import com.google.protobuf.FieldMask; +import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.Message; import org.junit.Test; +import org.spine3.protobuf.TypeUrl; +import org.spine3.test.aggregate.Project; +import org.spine3.test.aggregate.ProjectId; +import org.spine3.test.aggregate.Task; +import org.spine3.test.aggregate.TaskId; +import org.spine3.test.clientservice.customer.Customer; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.spine3.test.Tests.assertMatchesMask; import static org.spine3.test.Tests.hasPrivateUtilityConstructor; +import static org.spine3.test.Verify.assertSize; /** * @author Dmytro Dashenkov @@ -37,6 +56,116 @@ public void have_private_constructor() { @Test public void apply_mask_to_single_message() { + final FieldMask fieldMask = Given.fieldMask(Project.ID_FIELD_NUMBER, Project.NAME_FIELD_NUMBER); + + final Project original = Given.newProject("some-string-id"); + + final Project masked = FieldMasks.applyMask(fieldMask, original, Given.TYPE); + + assertNotEquals(original, masked); + + assertMatchesMask(masked, fieldMask); + } + + @SuppressWarnings("MethodWithMultipleLoops") + @Test + public void apply_mask_to_message_collections() { + final FieldMask fieldMask = Given.fieldMask(Project.STATUS_FIELD_NUMBER, Project.TASK_FIELD_NUMBER); + final int count = 5; + + final Collection original = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final Project project = Given.newProject(String.format("project-%s", i)); + original.add(project); + } + + final Collection masked = FieldMasks.applyMask(fieldMask, original, Given.TYPE); + + assertSize(original.size(), masked); + + // Collection references are not the same + // noinspection ObjectEquality + assertFalse(original == masked); + + for (Project project : masked) { + assertMatchesMask(project, fieldMask); + + // Can't check repeated fields with assertMatchesMask + assertFalse(project.getTaskList().isEmpty()); + } + } + + @Test + public void apply_only_non_empty_mask() { + // Empty mask + final FieldMask mask = Given.fieldMask(); + + final Project origin = Given.newProject("read_whole_message"); + final Project clone = Project.newBuilder(origin).build(); + + final Project processed = FieldMasks.applyMask(mask, origin, Given.TYPE); + + // Check object itself was returned + assertTrue(processed == origin); + + // Check object was not changed + assertTrue(processed.equals(clone)); + } + + @Test(expected = IllegalArgumentException.class) + public void fail_to_mask_message_if_passed_type_dees_not_match() { + final FieldMask mask = Given.fieldMask(Project.ID_FIELD_NUMBER); + + final Project origin = Given.newProject("some-string"); + + FieldMasks.applyMask(mask, origin, Given.OTHER_TYPE); + } + + private static class Given { + + private static final TypeUrl TYPE = TypeUrl.of(Project.class); + private static final TypeUrl OTHER_TYPE = TypeUrl.of(Customer.class); + + private static Project newProject(String id) { + final ProjectId projectId = ProjectId.newBuilder() + .setId(id) + .build(); + + final Task first = Task.newBuilder() + .setTaskId(TaskId.newBuilder().setId(1).build()) + .setTitle("First Task") + .build(); + + final Task second = Task.newBuilder() + .setTaskId(TaskId.newBuilder().setId(2).build()) + .setTitle("Second Task") + .build(); + + final Project project = Project.newBuilder() + .setId(projectId) + .setName(String.format("Test project : %s", id)) + .addTask(first) + .addTask(second) + .setStatus(Project.Status.CREATED) + .build(); + + return project; + } + + private static FieldMask fieldMask(int... fieldIndeces) { + final FieldMask.Builder mask = FieldMask.newBuilder(); + final List allFields = Project.getDescriptor().getFields(); + + for (int i : fieldIndeces) { + mask.addPaths(allFields.get(i - 1).getFullName()); + } + + return mask.build(); + } + private static Message mockMessage() { + return mock(GeneratedMessageV3.class); + } } } From b812937b8eddb04ba94a41f6ac22165997942aa1 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 13:19:43 +0300 Subject: [PATCH 264/361] Remove redundant "given'. --- .../server/entity/FieldMasksShould.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java index 776911ce63e..b7e9bbb2991 100644 --- a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -22,8 +22,6 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.FieldMask; -import com.google.protobuf.GeneratedMessageV3; -import com.google.protobuf.Message; import org.junit.Test; import org.spine3.protobuf.TypeUrl; import org.spine3.test.aggregate.Project; @@ -39,7 +37,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; import static org.spine3.test.Tests.assertMatchesMask; import static org.spine3.test.Tests.hasPrivateUtilityConstructor; import static org.spine3.test.Verify.assertSize; @@ -92,7 +89,8 @@ public void apply_mask_to_message_collections() { assertMatchesMask(project, fieldMask); // Can't check repeated fields with assertMatchesMask - assertFalse(project.getTaskList().isEmpty()); + assertFalse(project.getTaskList() + .isEmpty()); } } @@ -102,7 +100,8 @@ public void apply_only_non_empty_mask() { final FieldMask mask = Given.fieldMask(); final Project origin = Given.newProject("read_whole_message"); - final Project clone = Project.newBuilder(origin).build(); + final Project clone = Project.newBuilder(origin) + .build(); final Project processed = FieldMasks.applyMask(mask, origin, Given.TYPE); @@ -129,18 +128,22 @@ private static class Given { private static Project newProject(String id) { final ProjectId projectId = ProjectId.newBuilder() - .setId(id) - .build(); + .setId(id) + .build(); final Task first = Task.newBuilder() - .setTaskId(TaskId.newBuilder().setId(1).build()) - .setTitle("First Task") - .build(); + .setTaskId(TaskId.newBuilder() + .setId(1) + .build()) + .setTitle("First Task") + .build(); final Task second = Task.newBuilder() - .setTaskId(TaskId.newBuilder().setId(2).build()) - .setTitle("Second Task") - .build(); + .setTaskId(TaskId.newBuilder() + .setId(2) + .build()) + .setTitle("Second Task") + .build(); final Project project = Project.newBuilder() .setId(projectId) @@ -155,17 +158,15 @@ private static Project newProject(String id) { private static FieldMask fieldMask(int... fieldIndeces) { final FieldMask.Builder mask = FieldMask.newBuilder(); - final List allFields = Project.getDescriptor().getFields(); + final List allFields = Project.getDescriptor() + .getFields(); for (int i : fieldIndeces) { - mask.addPaths(allFields.get(i - 1).getFullName()); + mask.addPaths(allFields.get(i - 1) + .getFullName()); } return mask.build(); } - - private static Message mockMessage() { - return mock(GeneratedMessageV3.class); - } } } From cd6ec222c17f80c0a18415a20eaf80d9c5590e0c Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 13:24:48 +0300 Subject: [PATCH 265/361] Fix broken dependency. --- .../test/java/org/spine3/server/entity/FieldMasksShould.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java index b7e9bbb2991..def6bd235e7 100644 --- a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -28,7 +28,7 @@ import org.spine3.test.aggregate.ProjectId; import org.spine3.test.aggregate.Task; import org.spine3.test.aggregate.TaskId; -import org.spine3.test.clientservice.customer.Customer; +import org.spine3.test.commandservice.customer.Customer; import java.util.Collection; import java.util.LinkedList; From 92f33696f4d9e3c8126520b1c8df24f3a5b70ca3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 5 Oct 2016 16:45:32 +0300 Subject: [PATCH 266/361] Complete `Queries` and `Queries.Targets` test coverage. --- .../main/java/org/spine3/base/Queries.java | 8 +- .../java/org/spine3/base/QueriesShould.java | 111 ++++++++++++++---- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index 46887f2afeb..66f7c114f84 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -36,7 +36,8 @@ import java.util.Arrays; import java.util.Set; -import static org.spine3.base.Queries.Targets.composeTarget; +import static org.spine3.base.Queries.Targets.allOf; +import static org.spine3.base.Queries.Targets.someOf; /** * Client-side utilities for working with queries. @@ -99,7 +100,6 @@ public static Query readAll(Class entityClass, String... path return result; } - /** * Create a {@link Query} to read certain entity states by IDs. * @@ -131,8 +131,7 @@ public static Query readAll(Class entityClass) { } private static Query composeQuery(Class entityClass, @Nullable Set ids, @Nullable FieldMask fieldMask) { - final Target target = composeTarget(entityClass, ids); - + final Target target = ids == null ? allOf(entityClass) : someOf(entityClass, ids); final Query.Builder queryBuilder = Query.newBuilder() .setTarget(target); if (fieldMask != null) { @@ -143,7 +142,6 @@ private static Query composeQuery(Class entityClass, @Nullabl return result; } - /** * Client-side utilities for working with {@link Query} and {@link org.spine3.client.Subscription} targets. * diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java index 54766ca1245..3bb8f6299cb 100644 --- a/client/src/test/java/org/spine3/base/QueriesShould.java +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -46,14 +46,21 @@ /** * @author Alex Tymchenko */ -@SuppressWarnings({"LocalVariableNamingConvention", "MagicNumber"}) +@SuppressWarnings({"LocalVariableNamingConvention", "MagicNumber", "MethodParameterNamingConvention"}) public class QueriesShould { + private static final Class TARGET_ENTITY_CLASS = TestEntity.class; + @Test public void have_private_constructor() { assertTrue(hasPrivateUtilityConstructor(Queries.class)); } + @Test + public void have_private_constructor_of_targets_class() { + assertTrue(hasPrivateUtilityConstructor(Queries.Targets.class)); + } + @Test public void compose_proper_read_all_query() { final Class targetEntityClass = TestEntity.class; @@ -70,32 +77,37 @@ public void compose_proper_read_all_query() { @Test public void compose_proper_read_all_query_with_single_path() { final Class targetEntityClass = TestEntity.class; - final String firstPropertyFieldName = TestEntity.getDescriptor() - .getFields() - .get(1) - .getFullName(); - final Query readAllWithPathFilteringQuery = Queries.readAll(targetEntityClass, firstPropertyFieldName); + final String expectedEntityPath = singleTestEntityPath(); + final Query readAllWithPathFilteringQuery = Queries.readAll(targetEntityClass, expectedEntityPath); assertNotNull(readAllWithPathFilteringQuery); checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllWithPathFilteringQuery); - final FieldMask fieldMask = readAllWithPathFilteringQuery.getFieldMask(); + verifySinglePathInQuery(expectedEntityPath, readAllWithPathFilteringQuery); + } + + private static void verifySinglePathInQuery(String expectedEntityPath, Query query) { + final FieldMask fieldMask = query.getFieldMask(); assertEquals(1, fieldMask.getPathsCount()); // as we set the only path value. final String firstPath = fieldMask.getPaths(0); - assertEquals(firstPropertyFieldName, firstPath); + assertEquals(expectedEntityPath, firstPath); } @Test public void compose_proper_read_all_query_with_multiple_paths() { final Class targetEntityClass = TestEntity.class; - final String[] paths = {"some", "random", "paths"}; + final String[] paths = multipleRandomPaths(); final Query readAllWithPathFilteringQuery = Queries.readAll(targetEntityClass, paths); assertNotNull(readAllWithPathFilteringQuery); checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllWithPathFilteringQuery); + verifyMultiplePathsInQuery(paths, readAllWithPathFilteringQuery); + } + + private static void verifyMultiplePathsInQuery(String[] paths, Query readAllWithPathFilteringQuery) { final FieldMask fieldMask = readAllWithPathFilteringQuery.getFieldMask(); assertEquals(paths.length, fieldMask.getPathsCount()); final ProtocolStringList pathsList = fieldMask.getPathsList(); @@ -106,30 +118,79 @@ public void compose_proper_read_all_query_with_multiple_paths() { @Test public void compose_proper_read_by_ids_query() { - final Class targetEntityClass = TestEntity.class; - - final Set testEntityIds = newHashSet(TestEntityId.newBuilder() - .setValue(1) - .build(), - TestEntityId.newBuilder() - .setValue(7) - .build(), - TestEntityId.newBuilder() - .setValue(15) - .build() - ); - final Query readByIdsQuery = Queries.readByIds(targetEntityClass, testEntityIds); + final Set testEntityIds = multipleIds(); + final Query readByIdsQuery = Queries.readByIds(TARGET_ENTITY_CLASS, testEntityIds); assertNotNull(readByIdsQuery); checkFieldMaskEmpty(readByIdsQuery); - final Target target = checkTarget(targetEntityClass, readByIdsQuery); - final EntityFilters filters = target.getFilters(); + final Target target = checkTarget(TARGET_ENTITY_CLASS, readByIdsQuery); + + verifyIdFilter(testEntityIds, target.getFilters()); + + } + + @Test + public void compose_proper_read_by_ids_query_with_single_path() { + final Set testEntityIds = multipleIds(); + final String expectedPath = singleTestEntityPath(); + final Query readByIdsWithSinglePathQuery = Queries.readByIds( + TARGET_ENTITY_CLASS, + testEntityIds, + expectedPath); + assertNotNull(readByIdsWithSinglePathQuery); + + final Target target = checkTarget(TARGET_ENTITY_CLASS, readByIdsWithSinglePathQuery); + + verifyIdFilter(testEntityIds, target.getFilters()); + verifySinglePathInQuery(expectedPath, readByIdsWithSinglePathQuery); + } + + @Test + public void compose_proper_read_by_ids_query_with_multiple_paths() { + final Set testEntityIds = multipleIds(); + final String[] paths = multipleRandomPaths(); + final Query readByIdsWithSinglePathQuery = Queries.readByIds( + TARGET_ENTITY_CLASS, + testEntityIds, + paths); + assertNotNull(readByIdsWithSinglePathQuery); + + final Target target = checkTarget(TARGET_ENTITY_CLASS, readByIdsWithSinglePathQuery); + + verifyIdFilter(testEntityIds, target.getFilters()); + verifyMultiplePathsInQuery(paths, readByIdsWithSinglePathQuery); + } + + private static String[] multipleRandomPaths() { + return new String[]{"some", "random", "paths"}; + } + + private static String singleTestEntityPath() { + return TestEntity.getDescriptor() + .getFields() + .get(1) + .getFullName(); + } + + private static Set multipleIds() { + return newHashSet(TestEntityId.newBuilder() + .setValue(1) + .build(), + TestEntityId.newBuilder() + .setValue(7) + .build(), + TestEntityId.newBuilder() + .setValue(15) + .build()); + } + + private static void verifyIdFilter(Set expectedIds, EntityFilters filters) { assertNotNull(filters); final EntityIdFilter idFilter = filters.getIdFilter(); assertNotNull(idFilter); final List actualListOfIds = idFilter.getIdsList(); - for (TestEntityId testEntityId : testEntityIds) { + for (TestEntityId testEntityId : expectedIds) { final EntityId expectedEntityId = EntityId.newBuilder() .setId(AnyPacker.pack(testEntityId)) .build(); From 2e1854d3b4c07572ce545285fa55e45b01b0cf81 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 16:48:57 +0300 Subject: [PATCH 267/361] Fix formatting issues. --- .../org/spine3/server/entity/EntityRepository.java | 2 +- .../java/org/spine3/server/entity/FieldMasks.java | 4 +++- .../org/spine3/server/entity/FieldMasksShould.java | 14 +++----------- .../procman/ProcessManagerRepositoryShould.java | 5 +---- .../projection/ProjectionRepositoryShould.java | 14 ++++++++++---- testutil/src/main/java/org/spine3/test/Tests.java | 5 ++--- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 44fe3a6a604..e5a7a070607 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -160,7 +160,7 @@ public ImmutableCollection loadAll(Iterable ids, FieldMask fieldMask) { final I id = idIterator.next(); final EntityStorageRecord record = recordIterator.next(); - if (record == null) { // Record is nullable here since RecordStorage::findBulk returns an Iterable that may contain nulls. + if (record == null) { // Record is nullable here since RecordStorage#findBulk returns an {@code Iterable} that may contain nulls. continue; } diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index ee9783c0d22..813b6a4b70f 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -27,6 +27,7 @@ import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -56,8 +57,9 @@ private FieldMasks() { * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. * @param messages {@link Message}s to filter. * @param typeUrl Type of the {@link Message}s. - * @return Non-null unmodifiable {@link Collection} of {@link Message}s of the same type that the input had. + * @return {@link Nonnull} unmodifiable {@link Collection} containing {@link Message}s with {@code FieldMask} applied. */ + @Nonnull @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection messages, TypeUrl typeUrl) { final List filtered = new LinkedList<>(); diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java index def6bd235e7..62f486c5139 100644 --- a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -54,17 +54,15 @@ public void have_private_constructor() { @Test public void apply_mask_to_single_message() { final FieldMask fieldMask = Given.fieldMask(Project.ID_FIELD_NUMBER, Project.NAME_FIELD_NUMBER); - final Project original = Given.newProject("some-string-id"); final Project masked = FieldMasks.applyMask(fieldMask, original, Given.TYPE); assertNotEquals(original, masked); - assertMatchesMask(masked, fieldMask); } - @SuppressWarnings("MethodWithMultipleLoops") + @SuppressWarnings({"MethodWithMultipleLoops", "ObjectEquality"}) @Test public void apply_mask_to_message_collections() { final FieldMask fieldMask = Given.fieldMask(Project.STATUS_FIELD_NUMBER, Project.TASK_FIELD_NUMBER); @@ -82,7 +80,6 @@ public void apply_mask_to_message_collections() { assertSize(original.size(), masked); // Collection references are not the same - // noinspection ObjectEquality assertFalse(original == masked); for (Project project : masked) { @@ -96,14 +93,13 @@ public void apply_mask_to_message_collections() { @Test public void apply_only_non_empty_mask() { - // Empty mask - final FieldMask mask = Given.fieldMask(); + final FieldMask emptyMask = Given.fieldMask(); final Project origin = Given.newProject("read_whole_message"); final Project clone = Project.newBuilder(origin) .build(); - final Project processed = FieldMasks.applyMask(mask, origin, Given.TYPE); + final Project processed = FieldMasks.applyMask(emptyMask, origin, Given.TYPE); // Check object itself was returned assertTrue(processed == origin); @@ -130,7 +126,6 @@ private static Project newProject(String id) { final ProjectId projectId = ProjectId.newBuilder() .setId(id) .build(); - final Task first = Task.newBuilder() .setTaskId(TaskId.newBuilder() .setId(1) @@ -152,7 +147,6 @@ private static Project newProject(String id) { .addTask(second) .setStatus(Project.Status.CREATED) .build(); - return project; } @@ -160,12 +154,10 @@ private static FieldMask fieldMask(int... fieldIndeces) { final FieldMask.Builder mask = FieldMask.newBuilder(); final List allFields = Project.getDescriptor() .getFields(); - for (int i : fieldIndeces) { mask.addPaths(allFields.get(i - 1) .getFullName()); } - return mask.build(); } } diff --git a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java index c388fc1ad59..f5439d5acdd 100644 --- a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java @@ -213,9 +213,7 @@ public void return_event_classes() { protected EntityRepository repository() { final TestProcessManagerRepository repo = new TestProcessManagerRepository( TestBoundedContextFactory.newBoundedContext()); - repo.initStorage(InMemoryStorageFactory.getInstance()); - return repo; } @@ -227,14 +225,13 @@ protected TestProcessManager entity() { @Override protected List entities(int count) { - final List procmans = new ArrayList<>(); + final List procmans = new ArrayList<>(count); for (int i = 0; i < count; i++) { final ProjectId id = ProjectId.newBuilder().setId(String.format("procman-number-%s", i)).build(); procmans.add(new TestProcessManager(id)); } - return procmans; } diff --git a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java index 09e26780e39..ea664946021 100644 --- a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java @@ -218,7 +218,9 @@ public void catches_up_from_EventStorage() { @Override protected TestProjection entity() { - final TestProjection projection = new TestProjection(ProjectId.newBuilder().setId("single-test-projection").build()); + final TestProjection projection = new TestProjection(ProjectId.newBuilder() + .setId("single-test-projection") + .build()); return projection; } @@ -228,7 +230,9 @@ protected List entities(int count) { for (int i = 0; i < count; i++) { final TestProjection projection = new TestProjection( - ProjectId.newBuilder().setId(String.format("test-projection-%s", i)).build()); + ProjectId.newBuilder() + .setId(String.format("test-projection-%s", i)) + .build()); projections.add(projection); } @@ -250,12 +254,14 @@ private void keep(Message eventMessage) { eventMessagesDelivered.put(getState().getId(), eventMessage); } - /* package */ static boolean processed(Message eventMessage) { + /* package */ + static boolean processed(Message eventMessage) { final boolean result = eventMessagesDelivered.containsValue(eventMessage); return result; } - /* package */ static void clearMessageDeliveryHistory() { + /* package */ + static void clearMessageDeliveryHistory() { eventMessagesDelivered.clear(); } diff --git a/testutil/src/main/java/org/spine3/test/Tests.java b/testutil/src/main/java/org/spine3/test/Tests.java index a89bd4b26d9..efcb9004e31 100644 --- a/testutil/src/main/java/org/spine3/test/Tests.java +++ b/testutil/src/main/java/org/spine3/test/Tests.java @@ -24,7 +24,6 @@ import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; -import org.junit.Assert; import org.spine3.protobuf.Timestamps; import org.spine3.users.UserId; @@ -33,6 +32,7 @@ import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; +import static org.junit.Assert.assertEquals; /** * Utilities for testing. @@ -127,8 +127,7 @@ public static void assertMatchesMask(Message message, FieldMask fieldMask) { if (field.isRepeated()) { continue; } - - Assert.assertEquals(message.hasField(field), paths.contains(field.getFullName())); + assertEquals(message.hasField(field), paths.contains(field.getFullName())); } } From c799df9d72ad742e39243ba909bcb37648db0ef4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 5 Oct 2016 17:51:36 +0300 Subject: [PATCH 268/361] Adjust code style and naming in `QueriesShould` tests. --- .../java/org/spine3/base/QueriesShould.java | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java index 3bb8f6299cb..fb8f3fbbade 100644 --- a/client/src/test/java/org/spine3/base/QueriesShould.java +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -67,10 +67,8 @@ public void compose_proper_read_all_query() { final Query readAllQuery = Queries.readAll(targetEntityClass); assertNotNull(readAllQuery); - // `EntityFilters` must be default as this value was not set. checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllQuery); - // `FieldMask` must be default as `paths` were not set. checkFieldMaskEmpty(readAllQuery); } @@ -82,20 +80,11 @@ public void compose_proper_read_all_query_with_single_path() { assertNotNull(readAllWithPathFilteringQuery); checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllWithPathFilteringQuery); - verifySinglePathInQuery(expectedEntityPath, readAllWithPathFilteringQuery); } - private static void verifySinglePathInQuery(String expectedEntityPath, Query query) { - final FieldMask fieldMask = query.getFieldMask(); - assertEquals(1, fieldMask.getPathsCount()); // as we set the only path value. - - final String firstPath = fieldMask.getPaths(0); - assertEquals(expectedEntityPath, firstPath); - } - @Test - public void compose_proper_read_all_query_with_multiple_paths() { + public void compose_proper_read_all_query_with_multiple_random_paths() { final Class targetEntityClass = TestEntity.class; final String[] paths = multipleRandomPaths(); @@ -103,19 +92,9 @@ public void compose_proper_read_all_query_with_multiple_paths() { assertNotNull(readAllWithPathFilteringQuery); checkTypeCorrectAndFiltersEmpty(targetEntityClass, readAllWithPathFilteringQuery); - verifyMultiplePathsInQuery(paths, readAllWithPathFilteringQuery); } - private static void verifyMultiplePathsInQuery(String[] paths, Query readAllWithPathFilteringQuery) { - final FieldMask fieldMask = readAllWithPathFilteringQuery.getFieldMask(); - assertEquals(paths.length, fieldMask.getPathsCount()); - final ProtocolStringList pathsList = fieldMask.getPathsList(); - for (String expectedPath : paths) { - assertTrue(pathsList.contains(expectedPath)); - } - } - @Test public void compose_proper_read_by_ids_query() { final Set testEntityIds = multipleIds(); @@ -127,7 +106,6 @@ public void compose_proper_read_by_ids_query() { final Target target = checkTarget(TARGET_ENTITY_CLASS, readByIdsQuery); verifyIdFilter(testEntityIds, target.getFilters()); - } @Test @@ -147,7 +125,7 @@ public void compose_proper_read_by_ids_query_with_single_path() { } @Test - public void compose_proper_read_by_ids_query_with_multiple_paths() { + public void compose_proper_read_by_ids_query_with_multiple_random_paths() { final Set testEntityIds = multipleIds(); final String[] paths = multipleRandomPaths(); final Query readByIdsWithSinglePathQuery = Queries.readByIds( @@ -162,6 +140,23 @@ public void compose_proper_read_by_ids_query_with_multiple_paths() { verifyMultiplePathsInQuery(paths, readByIdsWithSinglePathQuery); } + private static void verifyMultiplePathsInQuery(String[] paths, Query readAllWithPathFilteringQuery) { + final FieldMask fieldMask = readAllWithPathFilteringQuery.getFieldMask(); + assertEquals(paths.length, fieldMask.getPathsCount()); + final ProtocolStringList pathsList = fieldMask.getPathsList(); + for (String expectedPath : paths) { + assertTrue(pathsList.contains(expectedPath)); + } + } + + private static void verifySinglePathInQuery(String expectedEntityPath, Query query) { + final FieldMask fieldMask = query.getFieldMask(); + assertEquals(1, fieldMask.getPathsCount()); // as we set the only path value. + + final String firstPath = fieldMask.getPaths(0); + assertEquals(expectedEntityPath, firstPath); + } + private static String[] multipleRandomPaths() { return new String[]{"some", "random", "paths"}; } @@ -221,5 +216,4 @@ private static Target checkTarget(Class targetEntityClass, Query que assertEquals(expectedTypeName, entityTarget.getType()); return entityTarget; } - } From 81adba6b2c9350041ff052ae293b9b440c643636 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 5 Oct 2016 18:10:18 +0300 Subject: [PATCH 269/361] Correct comment. --- server/src/main/java/org/spine3/server/entity/FieldMasks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 813b6a4b70f..ab9ca480d4c 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -57,7 +57,7 @@ private FieldMasks() { * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. * @param messages {@link Message}s to filter. * @param typeUrl Type of the {@link Message}s. - * @return {@link Nonnull} unmodifiable {@link Collection} containing {@link Message}s with {@code FieldMask} applied. + * @return messages with the {@code FieldMask} applied */ @Nonnull @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) From b390ba3601dcadf18c41e114506bde8eff6248b4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 5 Oct 2016 19:44:11 +0300 Subject: [PATCH 270/361] Extract a resulting value before actual `return` into a separate variable; remove excessive lines. --- .../main/java/org/spine3/server/entity/EntityRepository.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index e5a7a070607..1dd6ed50af5 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -187,14 +187,13 @@ public ImmutableCollection loadAll() { final ImmutableCollection entities = FluentIterable.from(recordMap.entrySet()) .transform(new Function, E>() { - @Nullable @Override public E apply(@Nullable Map.Entry input) { Preconditions.checkNotNull(input); - return toEntity(input.getKey(), input.getValue()); + final E result = toEntity(input.getKey(), input.getValue()); + return result; } - }) .toList(); return entities; From b645edbceb9ce69b554a3da2000767900de8da81 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 5 Oct 2016 19:47:55 +0300 Subject: [PATCH 271/361] Extract a factory method for the "storage record to entity" transformer. --- .../server/entity/EntityRepository.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 1dd6ed50af5..f34845ce93e 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -186,15 +186,7 @@ public ImmutableCollection loadAll() { final ImmutableCollection entities = FluentIterable.from(recordMap.entrySet()) - .transform(new Function, E>() { - @Nullable - @Override - public E apply(@Nullable Map.Entry input) { - Preconditions.checkNotNull(input); - final E result = toEntity(input.getKey(), input.getValue()); - return result; - } - }) + .transform(storageRecordToEntityTransformer()) .toList(); return entities; } @@ -271,4 +263,16 @@ private EntityStorageRecord toEntityRecord(E entity) { .setVersion(version); return builder.build(); } + + private Function, E> storageRecordToEntityTransformer() { + return new Function, E>() { + @Nullable + @Override + public E apply(@Nullable Map.Entry input) { + Preconditions.checkNotNull(input); + final E result = toEntity(input.getKey(), input.getValue()); + return result; + } + }; + } } From 8b0e6cd0a6ecd4ccaf1280ee76fb89be2c2cd104 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 5 Oct 2016 19:52:00 +0300 Subject: [PATCH 272/361] Improve `loadAll` documentation. --- .../main/java/org/spine3/server/entity/EntityRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index f34845ce93e..0842ec42bf2 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -160,7 +160,8 @@ public ImmutableCollection loadAll(Iterable ids, FieldMask fieldMask) { final I id = idIterator.next(); final EntityStorageRecord record = recordIterator.next(); - if (record == null) { // Record is nullable here since RecordStorage#findBulk returns an {@code Iterable} that may contain nulls. + if (record == null) { /* Record is nullable here since {@code RecordStorage#findBulk} * + * returns an {@code Iterable} that may contain nulls. */ continue; } From 9be366a5f9893752515c9d9b661bfc2b401304d3 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 15:30:53 +0300 Subject: [PATCH 273/361] * Avoid multiple @SuppressWarnings("unchecked") by generifying the abstract test class declaration; * update the descendants accordingly. * Reformat the code according to the latest convention. --- .../AbstractEntityRepositoryShould.java | 36 ++++++------ .../ProcessManagerRepositoryShould.java | 57 ++++++++++++------- .../ProjectionRepositoryShould.java | 7 ++- 3 files changed, 55 insertions(+), 45 deletions(-) diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 64cf1773e6e..62f14512151 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -40,12 +40,11 @@ /** * @author Dmytro Dashenkov */ -public abstract class AbstractEntityRepositoryShould> { +public abstract class AbstractEntityRepositoryShould, I, S extends Message> { - @SuppressWarnings("unchecked") @Test public void find_single_entity_by_id() { - final EntityRepository repo = repository(); + final EntityRepository repo = repository(); final E entity = entity(); @@ -56,10 +55,10 @@ public void find_single_entity_by_id() { assertEquals(found, entity); } - @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + @SuppressWarnings("MethodWithMultipleLoops") @Test public void find_multiple_entities_by_ids() { - final EntityRepository repo = repository(); + final EntityRepository repo = repository(); final int count = 10; final List entities = entities(count); @@ -68,11 +67,12 @@ public void find_multiple_entities_by_ids() { repo.store(entity); } - final List ids = new LinkedList<>(); + final List ids = new LinkedList<>(); // Find some of the records (half of them in this case) for (int i = 0; i < count / 2; i++) { - ids.add(entities.get(i).getId()); + ids.add(entities.get(i) + .getId()); } final Collection found = repo.loadAll(ids); @@ -84,10 +84,10 @@ public void find_multiple_entities_by_ids() { } } - @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + @SuppressWarnings("MethodWithMultipleLoops") @Test public void handle_wrong_passed_ids() { - final EntityRepository repo = repository(); + final EntityRepository repo = repository(); final int count = 10; final List entities = entities(count); @@ -96,12 +96,13 @@ public void handle_wrong_passed_ids() { repo.store(entity); } - final List ids = new LinkedList<>(); + final List ids = new LinkedList<>(); for (int i = 0; i < count; i++) { - ids.add(entities.get(i).getId()); + ids.add(entities.get(i) + .getId()); } - final Entity sideEntity = entity(); + final Entity sideEntity = entity(); ids.add(sideEntity.getId()); @@ -114,18 +115,16 @@ public void handle_wrong_passed_ids() { } } - @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) + @SuppressWarnings("MethodWithMultipleLoops") @Test public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { - final EntityRepository repo = repository(); + final EntityRepository repo = repository(); final int count = 10; final List entities = entities(count); - for (E entity : entities) { repo.store(entity); } - final List ids = new LinkedList<>(); // Find some of the records (half of them in this case) @@ -143,18 +142,15 @@ public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { final EntityFilters filters = EntityFilters.newBuilder() .setIdFilter(filter) .build(); - final String firstFieldName = entities.get(0) .getState() .getDescriptorForType() .getFields() .get(0) .getFullName(); - final FieldMask firstFieldOnly = FieldMask.newBuilder() .addPaths(firstFieldName) .build(); - final Iterable readEntities = repo.find(filters, firstFieldOnly); assertSize(ids.size(), readEntities); @@ -169,7 +165,7 @@ public void retrieve_all_records_with_entity_filters_and_field_mask_applied() { Tests.assertMatchesMask(state, fieldMask); } - protected abstract EntityRepository repository(); + protected abstract EntityRepository repository(); protected abstract E entity(); diff --git a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java index f5439d5acdd..e41f73b4422 100644 --- a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java @@ -78,7 +78,8 @@ * @author Alexander Litus */ @SuppressWarnings("InstanceMethodNamingConvention") -public class ProcessManagerRepositoryShould extends AbstractEntityRepositoryShould { +public class ProcessManagerRepositoryShould + extends AbstractEntityRepositoryShould { private static final ProjectId ID = Given.AggregateId.newProjectId(); @@ -93,18 +94,19 @@ public void setUp() { eventBus = spy(TestEventBusFactory.create()); boundedContext = TestBoundedContextFactory.newBoundedContext(eventBus); - boundedContext.getCommandBus().register(new CommandDispatcher() { - @Override - public Set getCommandClasses() { - return CommandClass.setOf(AddTask.class); - } + boundedContext.getCommandBus() + .register(new CommandDispatcher() { + @Override + public Set getCommandClasses() { + return CommandClass.setOf(AddTask.class); + } - @Override - public void dispatch(Command request) throws Exception { - // Simply swallow the command. We need this dispatcher for allowing Process Manager - // under test to route the AddTask command. - } - }); + @Override + public void dispatch(Command request) throws Exception { + // Simply swallow the command. We need this dispatcher for allowing Process Manager + // under test to route the AddTask command. + } + }); repository = new TestProcessManagerRepository(boundedContext); repository.initStorage(InMemoryStorageFactory.getInstance()); @@ -210,7 +212,7 @@ public void return_event_classes() { } @Override - protected EntityRepository repository() { + protected EntityRepository repository() { final TestProcessManagerRepository repo = new TestProcessManagerRepository( TestBoundedContextFactory.newBoundedContext()); repo.initStorage(InMemoryStorageFactory.getInstance()); @@ -219,7 +221,9 @@ public void return_event_classes() { @Override protected TestProcessManager entity() { - final ProjectId id = ProjectId.newBuilder().setId("123-id").build(); + final ProjectId id = ProjectId.newBuilder() + .setId("123-id") + .build(); return new TestProcessManager(id); } @@ -228,14 +232,15 @@ protected List entities(int count) { final List procmans = new ArrayList<>(count); for (int i = 0; i < count; i++) { - final ProjectId id = ProjectId.newBuilder().setId(String.format("procman-number-%s", i)).build(); + final ProjectId id = ProjectId.newBuilder() + .setId(String.format("procman-number-%s", i)) + .build(); procmans.add(new TestProcessManager(id)); } return procmans; } - private static class TestProcessManagerRepository extends ProcessManagerRepository { @@ -254,8 +259,11 @@ private TestProcessManagerRepository(BoundedContext boundedContext) { /** The event message we store for inspecting in delivery tests. */ private static final Multimap messagesDelivered = HashMultimap.create(); - @SuppressWarnings("PublicConstructorInNonPublicClass") /* A Process Manager constructor must be public by - convention. It is used by reflection and is part of public API of process managers. */ + @SuppressWarnings( + {"PublicConstructorInNonPublicClass", /* A Process Manager constructor must be public + * by convention. It is used by reflection + * and is part of public API of process managers. */ + "WeakerAccess"}) public TestProcessManager(ProjectId id) { super(id); } @@ -264,12 +272,13 @@ private void keep(Message commandOrEventMsg) { messagesDelivered.put(getState().getId(), commandOrEventMsg); } - /* package */ static boolean processed(Message eventMessage) { + private static boolean processed(Message eventMessage) { final boolean result = messagesDelivered.containsValue(eventMessage); return result; } - /* package */ static void clearMessageDeliveryHistory() { + /* package */ + static void clearMessageDeliveryHistory() { messagesDelivered.clear(); } @@ -327,6 +336,8 @@ private void handleProjectStarted() { incrementState(newState); } + @SuppressWarnings("UnusedParameters") /* The parameter left to show that a command subscriber + can have two parameters. */ @Assign public ProjectCreated handle(CreateProject command, CommandContext ignored) { keep(command); @@ -335,6 +346,8 @@ public ProjectCreated handle(CreateProject command, CommandContext ignored) { return Given.EventMessage.projectCreated(command.getProjectId()); } + @SuppressWarnings("UnusedParameters") /* The parameter left to show that a command subscriber + can have two parameters. */ @Assign public TaskAdded handle(AddTask command, CommandContext ignored) { keep(command); @@ -351,8 +364,8 @@ public CommandRouted handle(StartProject command, CommandContext context) { final Message addTask = Given.CommandMessage.addTask(command.getProjectId()); return newRouter().of(command, context) - .add(addTask) - .route(); + .add(addTask) + .route(); } } } diff --git a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java index ea664946021..f094b4bcf19 100644 --- a/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/projection/ProjectionRepositoryShould.java @@ -63,7 +63,8 @@ * @author Alexander Litus */ @SuppressWarnings("InstanceMethodNamingConvention") -public class ProjectionRepositoryShould extends AbstractEntityRepositoryShould { +public class ProjectionRepositoryShould + extends AbstractEntityRepositoryShould { private static final ProjectId ID = Given.AggregateId.newProjectId(); @@ -212,7 +213,7 @@ public void catches_up_from_EventStorage() { } @Override - protected EntityRepository repository() { + protected EntityRepository repository() { return repository; } @@ -288,7 +289,7 @@ public void on(TaskAdded event) { /* EventContext parameter left to show that a projection subscriber can have two parameters. */ @Subscribe - public void on(ProjectStarted event, EventContext ignored) { + public void on(ProjectStarted event, @SuppressWarnings("UnusedParameters") EventContext ignored) { keep(event); final Project newState = getState().toBuilder() .setStatus(Project.Status.STARTED) From d6a336f026623f79622e8a8eafbd2423917172e0 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 15:38:33 +0300 Subject: [PATCH 274/361] Cover `EntityRepository#loadAll` with tests. Also remove some redundant lines from a related test. --- .../AbstractEntityRepositoryShould.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java index 62f14512151..7addf37da97 100644 --- a/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java +++ b/server/src/test/java/org/spine3/server/entity/AbstractEntityRepositoryShould.java @@ -86,28 +86,50 @@ public void find_multiple_entities_by_ids() { @SuppressWarnings("MethodWithMultipleLoops") @Test - public void handle_wrong_passed_ids() { + public void find_all_entities() { final EntityRepository repo = repository(); - final int count = 10; + final int count = 150; final List entities = entities(count); for (E entity : entities) { repo.store(entity); } + final Collection found = repo.loadAll(); + assertSize(entities.size(), found); + + for (E entity : found) { + assertContains(entity, entities); + } + } + @Test + public void find_no_entities_if_empty() { + final EntityRepository repo = repository(); + + final Collection found = repo.loadAll(); + assertSize(0, found); + } + + @SuppressWarnings("MethodWithMultipleLoops") + @Test + public void handle_wrong_passed_ids() { + final EntityRepository repo = repository(); + + final int count = 10; + final List entities = entities(count); + for (E entity : entities) { + repo.store(entity); + } final List ids = new LinkedList<>(); for (int i = 0; i < count; i++) { ids.add(entities.get(i) .getId()); } - final Entity sideEntity = entity(); - ids.add(sideEntity.getId()); final Collection found = repo.loadAll(ids); - assertSize(ids.size() - 1, found); // Check we've found all existing items for (E entity : found) { From 07df1e59dfa335ee367f6f0bc7e26e081f92f1b5 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 15:43:38 +0300 Subject: [PATCH 275/361] Use `Preconditions.checkState` instead of explicit `throw new ...` to make the code cleaner. --- .../java/org/spine3/server/entity/EntityRepository.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 0842ec42bf2..4022a10eae3 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -49,6 +49,7 @@ import java.util.List; import java.util.Map; +import static com.google.common.base.Preconditions.checkState; import static org.spine3.protobuf.AnyPacker.unpack; import static org.spine3.protobuf.Messages.toMessageClass; import static org.spine3.validate.Validate.isDefault; @@ -224,10 +225,10 @@ public I apply(@Nullable EntityId input) { final TypeUrl typeUrl = TypeUrl.ofEnclosed(idAsAny); final Class messageClass = toMessageClass(typeUrl); - if (!expectedIdClass.equals(messageClass)) { - throw new IllegalArgumentException("Unexpected ID of type " + messageClass + " encountered. " + - "Expected: " + expectedIdClass); - } + final boolean classIsSame = expectedIdClass.equals(messageClass); + checkState(classIsSame, + "Unexpected ID of type " + messageClass + " encountered. " + "Expected: " + expectedIdClass); + final Message idAsMessage = AnyPacker.unpack(idAsAny); // As the message class is the same as expected, the conversion is safe. From 8e0ae4190dc515c849b5855d94d0ebd53de7733d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 15:51:15 +0300 Subject: [PATCH 276/361] Reformat `Stand` code and use `Preconditions.checkState` instead of throwing an exception. --- .../java/org/spine3/server/stand/Stand.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index b12f52ab82b..624ff3b0e39 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -59,7 +59,6 @@ import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -70,6 +69,7 @@ import java.util.concurrent.Executor; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Maps.newHashMap; /** @@ -100,7 +100,6 @@ public class Stand implements AutoCloseable { */ private final StandSubscriptionRegistry subscriptionRegistry = new StandSubscriptionRegistry(); - /** An instance of executor used to invoke callbacks */ private final Executor callbackExecutor; @@ -301,14 +300,13 @@ private ImmutableCollection internalExecute(Query query) { return result; } - - @SuppressWarnings("MethodWithMoreThanThreeNegations") // A lot of small logical conditions is checked. private ImmutableCollection fetchFromStandStorage(Query query, final TypeUrl typeUrl) { ImmutableCollection result; final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); - final boolean shouldApplyFieldMask = !fieldMask.getPathsList().isEmpty(); + final boolean shouldApplyFieldMask = !fieldMask.getPathsList() + .isEmpty(); if (target.getIncludeAll()) { result = shouldApplyFieldMask ? @@ -382,7 +380,8 @@ private static ImmutableCollection fetchFromEntityRepository(Q final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); - if (target.getIncludeAll() && fieldMask.getPathsList().isEmpty()) { + if (target.getIncludeAll() && fieldMask.getPathsList() + .isEmpty()) { result = repository.loadAll(); } else { final EntityFilters filters = target.getFilters(); @@ -406,7 +405,6 @@ private static void feedStateRecordsToBuilder(ImmutableList.Builder resultB } } - /** * Register a supplier for the objects of a certain {@link TypeUrl} to be able * to read them in response to a {@link org.spine3.client.Query}. @@ -453,12 +451,10 @@ public interface StandUpdateCallback { void onEntityStateUpdate(Any newEntityState); } - public static class Builder { private StandStorage storage; private Executor callbackExecutor; - /** * Set an instance of {@link StandStorage} to be used to persist the latest an Aggregate states. * @@ -472,7 +468,6 @@ public Builder setStorage(StandStorage storage) { return this; } - public Executor getCallbackExecutor() { return callbackExecutor; } @@ -494,7 +489,6 @@ public StandStorage getStorage() { return storage; } - /** * Build an instance of {@code Stand}. * @@ -527,9 +521,8 @@ private static final class StandSubscriptionRegistry { private final Map subscriptionToAttrs = newHashMap(); private synchronized void activate(Subscription subscription, StandUpdateCallback callback) { - if (!subscriptionToAttrs.containsKey(subscription)) { - throw new RuntimeException("Cannot find the subscription in the registry."); - } + checkState(subscriptionToAttrs.containsKey(subscription), + "Cannot find the subscription in the registry."); final SubscriptionRecord subscriptionRecord = subscriptionToAttrs.get(subscription); subscriptionRecord.activate(callback); } From 34bf83898acce2d73a75074611379accfc7beb29 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 15:54:48 +0300 Subject: [PATCH 277/361] Avoid warnings in FieldMasks due to ignored exception caught. --- .../java/org/spine3/server/entity/FieldMasks.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index ab9ca480d4c..72043be135d 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -87,7 +87,10 @@ public static Collection appl filtered.add((M) builder.build()); } - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { + } catch (NoSuchMethodException | + InvocationTargetException | + IllegalAccessException | + InstantiationException ignored) { // If any reflection failure happens, return all the data without any mask applied. return Collections.unmodifiableCollection(messages); } @@ -130,7 +133,10 @@ public static M applyMask(@Suppr return (M) builder.build(); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { + } catch (NoSuchMethodException | + InvocationTargetException | + IllegalAccessException | + InstantiationException ignored) { return message; } @@ -164,7 +170,7 @@ private static Class getBuilderForType(TypeUrl ty //noinspection unchecked builderClass = (Class) Class.forName(KnownTypes.getClassName(typeUrl).value()) .getClasses()[0]; - } catch (ClassNotFoundException | ClassCastException e) { + } catch (ClassNotFoundException | ClassCastException ignored) { builderClass = null; } From d6a4c058723e1c0357a7ecfc7b35dcd0ad002d84 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 18:14:09 +0300 Subject: [PATCH 278/361] Avoid warning suppression statements by refactoring the code of `FieldMasks`. --- .../org/spine3/server/entity/FieldMasks.java | 100 ++++++++++-------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 72043be135d..65788312de2 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -48,24 +48,26 @@ private FieldMasks() { } /** - *

        Applies the given {@code FieldMask} to given collection of {@link Message}s. + * Applies the given {@code FieldMask} to given collection of {@link Message}s. * Does not change the {@link Collection} itself. * - *

        n case the {@code FieldMask} instance contains invalid field declarations, they are ignored and + *

        In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and * do not affect the execution result. * * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. * @param messages {@link Message}s to filter. - * @param typeUrl Type of the {@link Message}s. + * @param type Type of the {@link Message}s. * @return messages with the {@code FieldMask} applied */ @Nonnull - @SuppressWarnings({"MethodWithMultipleLoops", "unchecked"}) - public static Collection applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, Collection messages, TypeUrl typeUrl) { + public static Collection applyMask( + FieldMask mask, + Collection messages, + TypeUrl type) { final List filtered = new LinkedList<>(); final ProtocolStringList filter = mask.getPathsList(); - final Class builderClass = getBuilderForType(typeUrl); + final Class builderClass = getBuilderForType(type); if (filter.isEmpty() || builderClass == null) { return Collections.unmodifiableCollection(messages); @@ -76,15 +78,8 @@ public static Collection appl builderConstructor.setAccessible(true); for (Message wholeMessage : messages) { - final B builder = builderConstructor.newInstance(); - - for (Descriptors.FieldDescriptor field : wholeMessage.getDescriptorForType().getFields()) { - if (filter.contains(field.getFullName())) { - builder.setField(field, wholeMessage.getField(field)); - } - } - - filtered.add((M) builder.build()); + final M message = messageForFilter(filter, builderConstructor, wholeMessage); + filtered.add(message); } } catch (NoSuchMethodException | @@ -99,21 +94,19 @@ public static Collection appl } /** - *

        Applies the given {@code FieldMask} to a single {@link Message}. - ** + * Applies the given {@code FieldMask} to a single {@link Message}. + * *

        In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and * do not affect the execution result. * - * @param mask {@code FieldMask} instance to apply. + * @param mask {@code FieldMask} instance to apply. * @param message The {@link Message} to apply given {@code FieldMask} to. - * @param typeUrl Type of the {@link Message}. + * @param type Type of the {@link Message}. * @return A {@link Message} of the same type as the given one with only selected fields. */ - @SuppressWarnings("unchecked") - public static M applyMask(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M message, TypeUrl typeUrl) { + public static M applyMask(FieldMask mask, M message, TypeUrl type) { final ProtocolStringList filter = mask.getPathsList(); - - final Class builderClass = getBuilderForType(typeUrl); + final Class builderClass = getBuilderForType(type); if (filter.isEmpty() || builderClass == null) { return message; @@ -123,15 +116,8 @@ public static M applyMask(@Suppr final Constructor builderConstructor = builderClass.getDeclaredConstructor(); builderConstructor.setAccessible(true); - final B builder = builderConstructor.newInstance(); - - for (Descriptors.FieldDescriptor field : message.getDescriptorForType().getFields()) { - if (filter.contains(field.getFullName())) { - builder.setField(field, message.getField(field)); - } - } - - return (M) builder.build(); + final M result = messageForFilter(filter, builderConstructor, message); + return result; } catch (NoSuchMethodException | InvocationTargetException | @@ -139,42 +125,62 @@ public static M applyMask(@Suppr InstantiationException ignored) { return message; } - - } /** - *

        Applies the {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. + * Applies the {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. * *

        In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and * do not affect the execution result. * * @param mask The {@code FieldMask} to apply. - * @param message The {@link Message} to apply given mask to. + * @param message The {@link Message} to apply given mask to. * @param typeUrl Type of given {@link Message}. * @return A {@link Message} of the same type as the given one with only selected fields - * if the {@code mask} is valid, {@code message} itself otherwise. + * if the {@code mask} is valid, {@code message} itself otherwise. */ - public static M applyIfValid(@SuppressWarnings("TypeMayBeWeakened") FieldMask mask, M message, TypeUrl typeUrl) { - if (!mask.getPathsList().isEmpty()) { + public static M applyIfValid(FieldMask mask, M message, TypeUrl typeUrl) { + if (!mask.getPathsList() + .isEmpty()) { return applyMask(mask, message, typeUrl); } return message; } + private static M messageForFilter( + ProtocolStringList filter, + Constructor builderConstructor, Message wholeMessage) + throws InstantiationException, + IllegalAccessException, + InvocationTargetException { + final B builder = builderConstructor.newInstance(); + + final List fields = wholeMessage.getDescriptorForType() + .getFields(); + for (Descriptors.FieldDescriptor field : fields) { + if (filter.contains(field.getFullName())) { + builder.setField(field, wholeMessage.getField(field)); + } + } + @SuppressWarnings("unchecked") // It's fine as the constructor is of {@code MessageCls.Builder} type. + final M result = (M) builder.build(); + return result; + } + @Nullable private static Class getBuilderForType(TypeUrl typeUrl) { - Class builderClass; try { - //noinspection unchecked - builderClass = (Class) Class.forName(KnownTypes.getClassName(typeUrl).value()) - .getClasses()[0]; + final String className = KnownTypes.getClassName(typeUrl) + .value(); + final Class builderClazz = Class.forName(className) + .getClasses()[0]; + // Assuming {@code KnownTypes#getClassName(TypeUrl)} works properly. + @SuppressWarnings("unchecked") + Class result = (Class) builderClazz; + return result; } catch (ClassNotFoundException | ClassCastException ignored) { - builderClass = null; + return null; } - - return builderClass; - } } From f54def56c375d56e1b4e6b974f79997bebf262cc Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 18:16:25 +0300 Subject: [PATCH 279/361] Remove redundant empty lines. --- server/src/main/java/org/spine3/server/entity/FieldMasks.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 65788312de2..0930ae367d2 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -66,7 +66,6 @@ public static Collection apply TypeUrl type) { final List filtered = new LinkedList<>(); final ProtocolStringList filter = mask.getPathsList(); - final Class builderClass = getBuilderForType(type); if (filter.isEmpty() || builderClass == null) { @@ -81,7 +80,6 @@ public static Collection apply final M message = messageForFilter(filter, builderConstructor, wholeMessage); filtered.add(message); } - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | @@ -89,7 +87,6 @@ public static Collection apply // If any reflection failure happens, return all the data without any mask applied. return Collections.unmodifiableCollection(messages); } - return Collections.unmodifiableList(filtered); } @@ -144,7 +141,6 @@ public static M applyIfValid(FieldMask mask, M message, Type .isEmpty()) { return applyMask(mask, message, typeUrl); } - return message; } From c9c97c5b2640515ac4b6bedb25f8078860fb795a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 18:42:36 +0300 Subject: [PATCH 280/361] Adjust code formatting and variable wrapping. --- .../java/org/spine3/server/stand/Stand.java | 140 ++++++++++-------- 1 file changed, 78 insertions(+), 62 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 624ff3b0e39..5ad56f62c0c 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -100,19 +100,24 @@ public class Stand implements AutoCloseable { */ private final StandSubscriptionRegistry subscriptionRegistry = new StandSubscriptionRegistry(); - /** An instance of executor used to invoke callbacks */ + /** + * An instance of executor used to invoke callbacks + */ private final Executor callbackExecutor; - /** The mapping between {@code TypeUrl} instances and repositories providing the entities of this type */ - private final ConcurrentMap> typeToRepositoryMap = new ConcurrentHashMap<>(); + /** + * The mapping between {@code TypeUrl} instances and repositories providing the entities of this type + */ + private final ConcurrentMap> typeToRepositoryMap = new ConcurrentHashMap<>(); /** - * Store the known {@link org.spine3.server.aggregate.Aggregate} types in order to distinguish them among all + * Stores known {@link org.spine3.server.aggregate.Aggregate} types in order to distinguish them among all * instances of {@code TypeUrl}. * *

        Once this instance of {@code Stand} receives an update as {@link Any}, the {@code Aggregate} states - * are persisted for further usage. While the rest of entity updates are not; they are only propagated to - * the registered callbacks. + * are persisted for further usage. The entities that are not {@code Aggregate} are not persisted, + * and only propagated to the registered callbacks. */ private final Set knownAggregateTypes = Sets.newConcurrentHashSet(); @@ -131,8 +136,8 @@ public static Builder newBuilder() { *

        In case the entity update represents the new {@link org.spine3.server.aggregate.Aggregate} state, * store the new value for the {@code Aggregate} to each of the configured instances of {@link StandStorage}. * - *

        Each {@code Aggregate } state value is stored as one-to-one to its {@link org.spine3.protobuf.TypeUrl} obtained - * via {@link Any#getTypeUrl()}. + *

        Each {@code Aggregate } state value is stored as one-to-one to its {@link org.spine3.protobuf.TypeUrl} + * obtained via {@link Any#getTypeUrl()}. * *

        In case {@code Stand} already contains the state for this {@code Aggregate}, the value will be replaced. * @@ -145,8 +150,6 @@ public static Builder newBuilder() { * * @param entityState the entity state */ - @SuppressWarnings("MethodWithMultipleLoops") /* It's fine, since the second loop is most likely - * executed in async fashion. */ public void update(final Object id, final Any entityState) { final String typeUrlString = entityState.getTypeUrl(); final TypeUrl typeUrl = TypeUrl.of(typeUrlString); @@ -163,21 +166,7 @@ public void update(final Object id, final Any entityState) { storage.write(aggregateStateId, record); } - if (subscriptionRegistry.hasType(typeUrl)) { - final Set allRecords = subscriptionRegistry.byType(typeUrl); - for (final SubscriptionRecord subscriptionRecord : allRecords) { - - final boolean subscriptionIsActive = subscriptionRecord.isActive(); - if (subscriptionIsActive && subscriptionRecord.matches(typeUrl, id, entityState)) { - callbackExecutor.execute(new Runnable() { - @Override - public void run() { - subscriptionRecord.callback.onEntityStateUpdate(entityState); - } - }); - } - } - } + notifyMatchingSubscriptions(id, entityState, typeUrl); } /** @@ -296,55 +285,82 @@ private ImmutableCollection internalExecute(Query query) { } final ImmutableList result = resultBuilder.build(); - return result; } - @SuppressWarnings("MethodWithMoreThanThreeNegations") // A lot of small logical conditions is checked. private ImmutableCollection fetchFromStandStorage(Query query, final TypeUrl typeUrl) { ImmutableCollection result; final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); final boolean shouldApplyFieldMask = !fieldMask.getPathsList() .isEmpty(); - if (target.getIncludeAll()) { result = shouldApplyFieldMask ? storage.readAllByType(typeUrl, fieldMask) : storage.readAllByType(typeUrl); - } else { - final EntityFilters filters = target.getFilters(); - final boolean idsAreDefined = !filters.getIdFilter() - .getIdsList() - .isEmpty(); - if (idsAreDefined) { - final EntityIdFilter idFilter = filters.getIdFilter(); - final Collection stateIds = Collections2.transform(idFilter.getIdsList(), aggregateStateIdTransformer(typeUrl)); - - if (stateIds.size() == 1) { - // no need to trigger bulk reading. - // may be more effective, as bulk reading implies additional time and performance expenses. - final AggregateStateId singleId = stateIds.iterator() - .next(); - final EntityStorageRecord singleResult = shouldApplyFieldMask ? - storage.read(singleId, fieldMask) : - storage.read(singleId); - result = ImmutableList.of(singleResult); - } else { - result = handleBulkRead(stateIds, fieldMask, shouldApplyFieldMask); - } + result = doFetchWithFilters(typeUrl, target, fieldMask); + } + + return result; + } + + private ImmutableCollection doFetchWithFilters(TypeUrl typeUrl, + Target target, + FieldMask fieldMask) { + ImmutableCollection result; + final EntityFilters filters = target.getFilters(); + final boolean shouldApplyFieldMask = !fieldMask.getPathsList() + .isEmpty(); + final boolean idsAreDefined = !filters.getIdFilter() + .getIdsList() + .isEmpty(); + if (idsAreDefined) { + final EntityIdFilter idFilter = filters.getIdFilter(); + final Collection stateIds = Collections2.transform(idFilter.getIdsList(), + aggregateStateIdTransformer(typeUrl)); + if (stateIds.size() == 1) { + // no need to trigger bulk reading. + // may be more effective, as bulk reading implies additional time and performance expenses. + final AggregateStateId singleId = stateIds.iterator() + .next(); + final EntityStorageRecord singleResult = shouldApplyFieldMask ? + storage.read(singleId, fieldMask) : + storage.read(singleId); + result = ImmutableList.of(singleResult); } else { - result = ImmutableList.of(); + result = handleBulkRead(stateIds, fieldMask, shouldApplyFieldMask); } - + } else { + result = ImmutableList.of(); } - return result; } - private ImmutableCollection handleBulkRead(Collection stateIds, FieldMask fieldMask, boolean applyFieldMask) { + private void notifyMatchingSubscriptions(Object id, final Any entityState, TypeUrl typeUrl) { + if (subscriptionRegistry.hasType(typeUrl)) { + final Set allRecords = subscriptionRegistry.byType(typeUrl); + + for (final SubscriptionRecord subscriptionRecord : allRecords) { + + final boolean subscriptionIsActive = subscriptionRecord.isActive(); + final boolean stateMatches = subscriptionRecord.matches(typeUrl, id, entityState); + if (subscriptionIsActive && stateMatches) { + callbackExecutor.execute(new Runnable() { + @Override + public void run() { + subscriptionRecord.callback.onEntityStateUpdate(entityState); + } + }); + } + } + } + } + + private ImmutableCollection handleBulkRead(Collection stateIds, + FieldMask fieldMask, + boolean applyFieldMask) { ImmutableCollection result; final Iterable bulkReadResults = applyFieldMask ? storage.readBulk(stateIds, fieldMask) : @@ -375,7 +391,10 @@ public AggregateStateId apply(@Nullable EntityId input) { }; } - private static ImmutableCollection fetchFromEntityRepository(Query query, EntityRepository repository) { + private static ImmutableCollection fetchFromEntityRepository( + Query query, + EntityRepository repository) { + final ImmutableCollection result; final Target target = query.getTarget(); final FieldMask fieldMask = query.getFieldMask(); @@ -390,7 +409,8 @@ private static ImmutableCollection fetchFromEntityRepository(Q return result; } - private static void feedEntitiesToBuilder(ImmutableList.Builder resultBuilder, ImmutableCollection all) { + private static void feedEntitiesToBuilder(ImmutableList.Builder resultBuilder, + ImmutableCollection all) { for (Entity record : all) { final Message state = record.getState(); final Any packedState = AnyPacker.pack(state); @@ -398,7 +418,8 @@ private static void feedEntitiesToBuilder(ImmutableList.Builder resultBuild } } - private static void feedStateRecordsToBuilder(ImmutableList.Builder resultBuilder, ImmutableCollection all) { + private static void feedStateRecordsToBuilder(ImmutableList.Builder resultBuilder, + ImmutableCollection all) { for (EntityStorageRecord record : all) { final Any state = record.getState(); resultBuilder.add(state); @@ -428,7 +449,6 @@ private static void feedStateRecordsToBuilder(ImmutableList.Builder resultB if (repository instanceof AggregateRepository) { knownAggregateTypes.add(entityType); } - } /** @@ -495,12 +515,10 @@ public StandStorage getStorage() { * @return the instance of Stand */ public Stand build() { - if (storage == null) { storage = InMemoryStandStorage.newBuilder() .build(); } - if (callbackExecutor == null) { callbackExecutor = MoreExecutors.directExecutor(); } @@ -522,7 +540,7 @@ private static final class StandSubscriptionRegistry { private synchronized void activate(Subscription subscription, StandUpdateCallback callback) { checkState(subscriptionToAttrs.containsKey(subscription), - "Cannot find the subscription in the registry."); + "Cannot find the subscription in the registry."); final SubscriptionRecord subscriptionRecord = subscriptionToAttrs.get(subscription); subscriptionRecord.activate(callback); } @@ -558,7 +576,6 @@ private synchronized void removeSubscription(Subscription subscription) { typeToAttrs.get(attributes.type) .remove(attributes); } - subscriptionToAttrs.remove(subscription); } @@ -613,7 +630,6 @@ private boolean matches( } else { result = false; } - return result; } From ba51276e84e0a971669154c5b885ae697fd68e2b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 18:50:57 +0300 Subject: [PATCH 281/361] Improve readability of the internal Query execution code. --- server/src/main/java/org/spine3/server/stand/Stand.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 5ad56f62c0c..851f328d486 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -274,13 +274,12 @@ private ImmutableCollection internalExecute(Query query) { // the target references an entity state final ImmutableCollection entities = fetchFromEntityRepository(query, repository); - feedEntitiesToBuilder(resultBuilder, entities); + } else if (knownAggregateTypes.contains(typeUrl)) { // the target relates to an {@code Aggregate} state final ImmutableCollection stateRecords = fetchFromStandStorage(query, typeUrl); - feedStateRecordsToBuilder(resultBuilder, stateRecords); } From 1547d4e34dc7cb28a09b15f8cf9308e186cefe60 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 19:12:11 +0300 Subject: [PATCH 282/361] * Add Query -> TypeUrl convenience extractor: use it in Stand for better readability; cover it with tests. * Mark KnownTypes#getTypeUrl(String) as @Nullable and handle it through the codebase. --- .../main/java/org/spine3/base/Queries.java | 17 +++++++ .../java/org/spine3/protobuf/KnownTypes.java | 48 ++++++++++--------- .../java/org/spine3/base/QueriesShould.java | 26 ++++++++++ .../java/org/spine3/server/QueryService.java | 13 +++-- .../spine3/server/SubscriptionService.java | 13 +++-- .../java/org/spine3/server/stand/Stand.java | 9 ++-- 6 files changed, 87 insertions(+), 39 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index 66f7c114f84..c282872865d 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -30,6 +30,7 @@ import org.spine3.client.Query; import org.spine3.client.Target; import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; import javax.annotation.Nullable; @@ -142,6 +143,22 @@ private static Query composeQuery(Class entityClass, @Nullabl return result; } + /** + * Extract the type of {@link Target} for the given {@link Query}. + * + *

        Returns null if the {@code Target} type is unknown to the application. + * + * @param query the query of interest. + * @return the type of the {@code Query#getTarget()} or null, if the type is unknown. + */ + @Nullable + public static TypeUrl typeOf(Query query) { + final Target target = query.getTarget(); + final String typeAsString = target.getType(); + final TypeUrl type = KnownTypes.getTypeUrl(typeAsString); + return type; + } + /** * Client-side utilities for working with {@link Query} and {@link org.spine3.client.Subscription} targets. * diff --git a/client/src/main/java/org/spine3/protobuf/KnownTypes.java b/client/src/main/java/org/spine3/protobuf/KnownTypes.java index d4755da2bfe..e5e70721e7e 100644 --- a/client/src/main/java/org/spine3/protobuf/KnownTypes.java +++ b/client/src/main/java/org/spine3/protobuf/KnownTypes.java @@ -63,6 +63,7 @@ import org.spine3.protobuf.error.UnknownTypeException; import org.spine3.type.ClassName; +import javax.annotation.Nullable; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -105,8 +106,8 @@ public class KnownTypes { */ private static final ImmutableMap typeNameToUrlMap = buildTypeToUrlMap(knownTypes); - - private KnownTypes() {} + private KnownTypes() { + } /** Retrieves Protobuf type URLs known to the application. */ public static ImmutableSet getTypeUrls() { @@ -145,7 +146,8 @@ public static ClassName getClassName(TypeUrl typeUrl) throws UnknownTypeExceptio * @throws IllegalStateException if there is no Protobuf type for the specified class */ public static TypeUrl getTypeUrl(ClassName className) { - final TypeUrl result = knownTypes.inverse().get(className); + final TypeUrl result = knownTypes.inverse() + .get(className); if (result == null) { throw new IllegalStateException("No Protobuf type URL found for the Java class " + className); } @@ -153,6 +155,7 @@ public static TypeUrl getTypeUrl(ClassName className) { } /** Returns a Protobuf type URL by Protobuf type name. */ + @Nullable public static TypeUrl getTypeUrl(String typeName) { final TypeUrl typeUrl = typeNameToUrlMap.get(typeName); return typeUrl; @@ -203,7 +206,8 @@ private void putProperties(Properties properties) { *

        This method needs to be updated with introduction of new Google Protobuf types * after they are used in the framework. */ - @SuppressWarnings("OverlyLongMethod") // OK as there are many types in Protobuf and we want to keep this code in one place. + @SuppressWarnings("OverlyLongMethod") + // OK as there are many types in Protobuf and we want to keep this code in one place. private Builder addStandardProtobufTypes() { // Types from `any.proto`. put(Any.class); @@ -217,14 +221,14 @@ private Builder addStandardProtobufTypes() { put(DescriptorProtos.FileDescriptorSet.class); put(DescriptorProtos.FileDescriptorProto.class); put(DescriptorProtos.DescriptorProto.class); - // Inner types of `DescriptorProto` - put(DescriptorProtos.DescriptorProto.ExtensionRange.class); - put(DescriptorProtos.DescriptorProto.ReservedRange.class); + // Inner types of `DescriptorProto` + put(DescriptorProtos.DescriptorProto.ExtensionRange.class); + put(DescriptorProtos.DescriptorProto.ReservedRange.class); put(DescriptorProtos.FieldDescriptorProto.class); - putEnum(DescriptorProtos.FieldDescriptorProto.Type.getDescriptor(), - DescriptorProtos.FieldDescriptorProto.Type.class); - putEnum(DescriptorProtos.FieldDescriptorProto.Label.getDescriptor(), + putEnum(DescriptorProtos.FieldDescriptorProto.Type.getDescriptor(), + DescriptorProtos.FieldDescriptorProto.Type.class); + putEnum(DescriptorProtos.FieldDescriptorProto.Label.getDescriptor(), DescriptorProtos.FieldDescriptorProto.Label.class); put(DescriptorProtos.OneofDescriptorProto.class); @@ -237,21 +241,21 @@ private Builder addStandardProtobufTypes() { DescriptorProtos.FileOptions.OptimizeMode.class); put(DescriptorProtos.MessageOptions.class); put(DescriptorProtos.FieldOptions.class); - putEnum(DescriptorProtos.FieldOptions.CType.getDescriptor(), - DescriptorProtos.FieldOptions.CType.class); - putEnum(DescriptorProtos.FieldOptions.JSType.getDescriptor(), - DescriptorProtos.FieldOptions.JSType.class); + putEnum(DescriptorProtos.FieldOptions.CType.getDescriptor(), + DescriptorProtos.FieldOptions.CType.class); + putEnum(DescriptorProtos.FieldOptions.JSType.getDescriptor(), + DescriptorProtos.FieldOptions.JSType.class); put(DescriptorProtos.EnumOptions.class); put(DescriptorProtos.EnumValueOptions.class); put(DescriptorProtos.ServiceOptions.class); put(DescriptorProtos.MethodOptions.class); put(DescriptorProtos.UninterpretedOption.class); put(DescriptorProtos.SourceCodeInfo.class); - // Inner types of `SourceCodeInfo`. - put(DescriptorProtos.SourceCodeInfo.Location.class); + // Inner types of `SourceCodeInfo`. + put(DescriptorProtos.SourceCodeInfo.Location.class); put(DescriptorProtos.GeneratedCodeInfo.class); - // Inner types of `GeneratedCodeInfo`. - put(DescriptorProtos.GeneratedCodeInfo.Annotation.class); + // Inner types of `GeneratedCodeInfo`. + put(DescriptorProtos.GeneratedCodeInfo.Annotation.class); // Types from `duration.proto`. put(Duration.class); @@ -277,8 +281,8 @@ private Builder addStandardProtobufTypes() { // Types from `type.proto`. put(Type.class); put(Field.class); - putEnum(Field.Kind.getDescriptor(), Field.Kind.class); - putEnum(Field.Cardinality.getDescriptor(), Field.Cardinality.class); + putEnum(Field.Kind.getDescriptor(), Field.Kind.class); + putEnum(Field.Cardinality.getDescriptor(), Field.Cardinality.class); put(com.google.protobuf.Enum.class); put(EnumValue.class); put(Option.class); @@ -313,8 +317,8 @@ private void putEnum(EnumDescriptor desc, Class enumClass) { private void put(TypeUrl typeUrl, ClassName className) { if (resultMap.containsKey(typeUrl)) { log().warn("Duplicate key in the {} map: {}. " + - "It may be caused by the `task.descriptorSetOptions.includeImports` option " + - "set to `true` in the `build.gradle`.", KnownTypes.class.getName(), typeUrl); + "It may be caused by the `task.descriptorSetOptions.includeImports` option " + + "set to `true` in the `build.gradle`.", KnownTypes.class.getName(), typeUrl); return; } resultMap.put(typeUrl, className); diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java index fb8f3fbbade..ae80b6f846c 100644 --- a/client/src/test/java/org/spine3/base/QueriesShould.java +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -40,6 +40,7 @@ import static com.google.common.collect.Sets.newHashSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.spine3.test.Tests.hasPrivateUtilityConstructor; @@ -49,7 +50,9 @@ @SuppressWarnings({"LocalVariableNamingConvention", "MagicNumber", "MethodParameterNamingConvention"}) public class QueriesShould { + // See {@code queries_should.proto} for declaration. private static final Class TARGET_ENTITY_CLASS = TestEntity.class; + private static final String TARGET_ENTITY_TYPE_URL = "type.spine3.org/spine.test.queries.TestEntity"; @Test public void have_private_constructor() { @@ -140,6 +143,29 @@ public void compose_proper_read_by_ids_query_with_multiple_random_paths() { verifyMultiplePathsInQuery(paths, readByIdsWithSinglePathQuery); } + @Test + public void return_proper_type_for_known_target() { + final Target target = Queries.Targets.allOf(TARGET_ENTITY_CLASS); + final Query query = Query.newBuilder() + .setTarget(target) + .build(); + final TypeUrl type = Queries.typeOf(query); + assertNotNull(type); + assertEquals(TARGET_ENTITY_TYPE_URL, type.toString()); + } + + @Test + public void return_null_if_target_type_unknown() { + final Target target = Target.newBuilder() + .setType("Inexistent Message Type") + .build(); + final Query query = Query.newBuilder() + .setTarget(target) + .build(); + final TypeUrl type = Queries.typeOf(query); + assertNull(type); + } + private static void verifyMultiplePathsInQuery(String[] paths, Query readAllWithPathFilteringQuery) { final FieldMask fieldMask = readAllWithPathFilteringQuery.getFieldMask(); assertEquals(paths.length, fieldMask.getPathsCount()); diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index ae10dc13877..8e708b650a5 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -27,14 +27,16 @@ import io.grpc.stub.StreamObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spine3.base.Queries; import org.spine3.client.Query; import org.spine3.client.QueryResponse; import org.spine3.client.grpc.QueryServiceGrpc; -import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; import java.util.Set; +import static com.google.common.base.Preconditions.checkNotNull; + /** * The {@code QueryService} provides a synchronous way to fetch read-side state from the server. * @@ -57,18 +59,15 @@ public static Builder newBuilder() { @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. @Override public void read(Query query, StreamObserver responseObserver) { - log().debug("Incoming query: {}", query); - final String typeAsString = query.getTarget() - .getType(); - final TypeUrl type = KnownTypes.getTypeUrl(typeAsString); - final BoundedContext boundedContext = typeToContextMap.get(type); + final TypeUrl type = Queries.typeOf(query); + checkNotNull(type, "Unknown type for query target"); + final BoundedContext boundedContext = typeToContextMap.get(type); try { boundedContext.getStand() .execute(query, responseObserver); - } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { log().error("Error processing query", e); responseObserver.onError(e); diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index 9c83bd2a73b..e321eebf11a 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -21,7 +21,6 @@ */ package org.spine3.server; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -43,6 +42,8 @@ import java.util.Set; +import static com.google.common.base.Preconditions.checkNotNull; + /** * The {@code SubscriptionService} provides an asynchronous way to fetch read-side state from the server. * @@ -93,7 +94,7 @@ public void activate(final Subscription subscription, final StreamObserver responseO private BoundedContext selectBoundedContext(Subscription subscription) { final String typeAsString = subscription.getType(); final TypeUrl type = KnownTypes.getTypeUrl(typeAsString); - return typeToContextMap.get(type); + checkNotNull(type, "Unknown type of the subscription"); + final BoundedContext result = typeToContextMap.get(type); + return result; } private BoundedContext selectBoundedContext(Target target) { final String typeAsString = target.getType(); final TypeUrl type = KnownTypes.getTypeUrl(typeAsString); - return typeToContextMap.get(type); + checkNotNull(type, "Unknown type of the target"); + final BoundedContext result = typeToContextMap.get(type); + return result; } public static class Builder { diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 851f328d486..3d0388a20b8 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -36,6 +36,7 @@ import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.spine3.base.Identifiers; +import org.spine3.base.Queries; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; @@ -45,7 +46,6 @@ import org.spine3.client.Subscription; import org.spine3.client.Target; import org.spine3.protobuf.AnyPacker; -import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.Timestamps; import org.spine3.protobuf.TypeUrl; import org.spine3.server.aggregate.AggregateRepository; @@ -261,15 +261,12 @@ public void execute(Query query, StreamObserver responseObserver) } private ImmutableCollection internalExecute(Query query) { - final ImmutableList.Builder resultBuilder = ImmutableList.builder(); - final Target target = query.getTarget(); + final TypeUrl typeUrl = Queries.typeOf(query); + checkNotNull(typeUrl, "Target type unknown"); - final String type = target.getType(); - final TypeUrl typeUrl = KnownTypes.getTypeUrl(type); final EntityRepository repository = typeToRepositoryMap.get(typeUrl); - if (repository != null) { // the target references an entity state From 373c91ba2ed46c79a01ce2b8affb308aaebaf636 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 20:34:50 +0300 Subject: [PATCH 283/361] Generate UUID with Spine utility. --- server/src/main/java/org/spine3/server/stand/Stand.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 3d0388a20b8..52cf4786816 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -542,8 +542,7 @@ private synchronized void activate(Subscription subscription, StandUpdateCallbac } private synchronized Subscription addSubscription(Target target) { - final String subscriptionId = UUID.randomUUID() - .toString(); + final String subscriptionId = Identifiers.newUuid(); final String typeAsString = target.getType(); final TypeUrl type = TypeUrl.of(typeAsString); final Subscription subscription = Subscription.newBuilder() From f4018553cbf3cc995b1a0c72e046b1047f2e10f9 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Thu, 6 Oct 2016 20:40:52 +0300 Subject: [PATCH 284/361] Specify that `Subscription` instances should not be created in scope of the client code, but inside the read-side implementation. --- client/src/main/proto/spine/client/subscription.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/main/proto/spine/client/subscription.proto b/client/src/main/proto/spine/client/subscription.proto index 09e1553e8aa..14e221a2546 100644 --- a/client/src/main/proto/spine/client/subscription.proto +++ b/client/src/main/proto/spine/client/subscription.proto @@ -72,7 +72,8 @@ message SubscriptionUpdate { // The subscription object. // -// Created when the client subscribes to a topic. +// Created when the client subscribes to a topic inside the read-side implementation. +// Generally should not be created in the client code. // See SubscriptionService#Subscribe(Topic). message Subscription { From ddbf154545767560648bb5a74cc9bfcac9f80ebf Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 7 Oct 2016 12:28:37 +0300 Subject: [PATCH 285/361] Add test for applying an empty field mask onto a collection of messages. --- .../server/entity/FieldMasksShould.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java index 62f486c5139..6b8fbdbacef 100644 --- a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -31,6 +31,7 @@ import org.spine3.test.commandservice.customer.Customer; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -92,7 +93,7 @@ public void apply_mask_to_message_collections() { } @Test - public void apply_only_non_empty_mask() { + public void apply_only_non_empty_mask_to_single_item() { final FieldMask emptyMask = Given.fieldMask(); final Project origin = Given.newProject("read_whole_message"); @@ -108,6 +109,35 @@ public void apply_only_non_empty_mask() { assertTrue(processed.equals(clone)); } + @SuppressWarnings({"ObjectEquality", "MethodWithMultipleLoops"}) + @Test + public void apply_only_non_empty_mask_to_collection() { + final FieldMask emptyMask = Given.fieldMask(); + + final Collection original = new LinkedList<>(); + final int count = 5; + + for (int i = 0; i < count; i++) { + final Project project = Given.newProject(String.format("test-data--%s", i)); + original.add(project); + } + + final Collection processed = FieldMasks.applyMask(emptyMask, original, Given.TYPE); + + assertSize(original.size(), processed); + + // The argument is not returned + assertFalse(original == processed); + + // A copy of the argument is returned (Collection type may differ) + final Iterator processedProjects = processed.iterator(); + + for (Project anOriginal : original) { + assertTrue(processedProjects.next() + .equals(anOriginal)); + } + } + @Test(expected = IllegalArgumentException.class) public void fail_to_mask_message_if_passed_type_dees_not_match() { final FieldMask mask = Given.fieldMask(Project.ID_FIELD_NUMBER); From 21676bef52df91bcaf2ef888dddbf508e9b4aa89 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 7 Oct 2016 13:11:20 +0300 Subject: [PATCH 286/361] Add test for selecting singleton list from stand with field mask applied. --- .../org/spine3/server/stand/StandShould.java | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 5294e4b6e79..7a15f20a83b 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -64,6 +64,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -561,7 +562,47 @@ public void onNext(QueryResponse value) { @Test public void retrieve_whole_entity_if_nothing_is_requested() { //noinspection ZeroLengthArrayAllocation - requestSampleCustomer(new int[] {}, getDuplicateCostumerStreamObserver()); + requestSampleCustomer(new int[]{}, getDuplicateCostumerStreamObserver()); + } + + @SuppressWarnings("MethodWithMultipleLoops") + @Test + public void select_entity_singleton_by_id_and_apply_field_masks() { + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder() + .build()); + final String customerFqn = Customer.getDescriptor() + .getFullName(); + final String[] paths = {customerFqn + ".id", customerFqn + ".name"}; + final FieldMask fieldMask = FieldMask.newBuilder() + .addAllPaths(Arrays.asList(paths)) + .build(); + + final List customers = new LinkedList<>(); + final int count = 10; + + for (int i = 0; i < count; i++) { + // Has new id each time + final Customer customer = getSampleCustomer(); + customers.add(customer); + + stand.update(customer.getId(), AnyPacker.pack(customer)); + } + + final Set ids = Collections.singleton(customers.get(0) + .getId()); + final Query customerQuery = Queries.readByIds(Customer.class, ids, paths); + + final MemoizeQueryResponseObserver observer = new MemoizeQueryResponseObserver(); + stand.execute(customerQuery, observer); + + final List read = observer.responseHandled.getMessagesList(); + assertSize(1, read); + + final Customer customer = AnyPacker.unpack(read.get(0)); + assertMatches(customer, fieldMask); + assertTrue(ids.contains(customer.getId())); + + verifyObserver(observer); } @Test From eede00df248ab95f9d6098a9316e5ee48a262ca8 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 7 Oct 2016 13:32:38 +0300 Subject: [PATCH 287/361] Add exception logging for FiledMasks. --- .../org/spine3/server/entity/FieldMasks.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index ab9ca480d4c..82631bf7f4e 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -24,8 +24,11 @@ import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.ProtocolStringList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; +import org.spine3.server.SubscriptionService; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -44,6 +47,8 @@ @SuppressWarnings("UtilityClass") public class FieldMasks { + //private static final Logger log = + private FieldMasks() { } @@ -131,6 +136,7 @@ public static M applyMask(@Suppr return (M) builder.build(); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { + log().warn(String.format("Constructor for type %s could not be found or called: ", builderClass.getCanonicalName()), e); return message; } @@ -160,15 +166,37 @@ public static M applyIfValid(@SuppressWarnings("TypeMayBeWe @Nullable private static Class getBuilderForType(TypeUrl typeUrl) { Class builderClass; + final String className = KnownTypes.getClassName(typeUrl) + .value(); try { //noinspection unchecked - builderClass = (Class) Class.forName(KnownTypes.getClassName(typeUrl).value()) + builderClass = (Class) Class.forName(className) .getClasses()[0]; - } catch (ClassNotFoundException | ClassCastException e) { + } catch (ClassNotFoundException e) { + final String message = String.format( + "Class for name %s could not be found. Try to rebuild the project. Make sure \"known_types.properties\" exists.", + className); + log().warn(message, e); + builderClass = null; + } catch (ClassCastException e) { + final String message = String.format( + "Class %s Must be assignable from com.google.protobuf.Message. Try to rebuild the project. Make sure type URL is valid.", + className); + log().warn(message, e); builderClass = null; } return builderClass; } + + private static Logger log() { + return LogSingleton.INSTANCE.value; + } + + private enum LogSingleton { + INSTANCE; + @SuppressWarnings("NonSerializableFieldInSerializableClass") + private final Logger value = LoggerFactory.getLogger(SubscriptionService.class); + } } From d576b5f4692ae4377d3a74f797f0af691c25ad9f Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 7 Oct 2016 13:37:28 +0300 Subject: [PATCH 288/361] Remove redundant commented code. --- server/src/main/java/org/spine3/server/entity/FieldMasks.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 82631bf7f4e..252930ca42d 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -47,8 +47,6 @@ @SuppressWarnings("UtilityClass") public class FieldMasks { - //private static final Logger log = - private FieldMasks() { } From baeddf54b8c63be03d5e2bfe137778cb79c22bdf Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Fri, 7 Oct 2016 18:10:09 +0300 Subject: [PATCH 289/361] Fix issues from PR. --- .../org/spine3/server/entity/FieldMasks.java | 16 +++++++++++----- .../org/spine3/server/stand/StandShould.java | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 252930ca42d..afb5d1ff052 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory; import org.spine3.protobuf.KnownTypes; import org.spine3.protobuf.TypeUrl; -import org.spine3.server.SubscriptionService; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -47,6 +46,13 @@ @SuppressWarnings("UtilityClass") public class FieldMasks { + private static final String CONSTRUCTOR_INVOCATION_ERROR_LOGGING_PATTERN = + "Constructor for type %s could not be found or called: "; + private static final String BUILDER_CLASS_ERROR_LOGGING_PATTERN = + "Class for name %s could not be found. Try to rebuild the project. Make sure \"known_types.properties\" exists."; + private static final String TYPE_CAST_ERROR_LOGGING_PATTERN = + "Class %s must be assignable from com.google.protobuf.Message. Try to rebuild the project. Make sure type URL is valid."; + private FieldMasks() { } @@ -134,7 +140,7 @@ public static M applyMask(@Suppr return (M) builder.build(); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { - log().warn(String.format("Constructor for type %s could not be found or called: ", builderClass.getCanonicalName()), e); + log().warn(String.format(CONSTRUCTOR_INVOCATION_ERROR_LOGGING_PATTERN, builderClass.getCanonicalName()), e); return message; } @@ -172,13 +178,13 @@ private static Class getBuilderForType(TypeUrl ty .getClasses()[0]; } catch (ClassNotFoundException e) { final String message = String.format( - "Class for name %s could not be found. Try to rebuild the project. Make sure \"known_types.properties\" exists.", + BUILDER_CLASS_ERROR_LOGGING_PATTERN, className); log().warn(message, e); builderClass = null; } catch (ClassCastException e) { final String message = String.format( - "Class %s Must be assignable from com.google.protobuf.Message. Try to rebuild the project. Make sure type URL is valid.", + TYPE_CAST_ERROR_LOGGING_PATTERN, className); log().warn(message, e); builderClass = null; @@ -195,6 +201,6 @@ private static Logger log() { private enum LogSingleton { INSTANCE; @SuppressWarnings("NonSerializableFieldInSerializableClass") - private final Logger value = LoggerFactory.getLogger(SubscriptionService.class); + private final Logger value = LoggerFactory.getLogger(FieldMasks.class); } } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 7a15f20a83b..29134baa01e 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -581,7 +581,7 @@ public void select_entity_singleton_by_id_and_apply_field_masks() { final int count = 10; for (int i = 0; i < count; i++) { - // Has new id each time + // Has new ID each time final Customer customer = getSampleCustomer(); customers.add(customer); From d8dad1aa955efcda1df5448cbdea81419c6613fc Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 10 Oct 2016 15:31:26 +0300 Subject: [PATCH 290/361] Fix variable name. --- .../src/test/java/org/spine3/server/stand/StandShould.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 29134baa01e..50a5ad9b58f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -570,9 +570,9 @@ public void retrieve_whole_entity_if_nothing_is_requested() { public void select_entity_singleton_by_id_and_apply_field_masks() { final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder() .build()); - final String customerFqn = Customer.getDescriptor() - .getFullName(); - final String[] paths = {customerFqn + ".id", customerFqn + ".name"}; + final String customerDescriptor = Customer.getDescriptor() + .getFullName(); + final String[] paths = {customerDescriptor + ".id", customerDescriptor + ".name"}; final FieldMask fieldMask = FieldMask.newBuilder() .addAllPaths(Arrays.asList(paths)) .build(); From 080ae9bb33aa81d24cf62dad0612ec2a0e1d2090 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 10 Oct 2016 15:42:58 +0300 Subject: [PATCH 291/361] Merge. --- .../main/java/org/spine3/server/entity/FieldMasks.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 1d0f95189fc..2c1d352888a 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -125,15 +125,6 @@ public static M applyMask(FieldMa final M result = messageForFilter(filter, builderConstructor, message); return result; - - for (Descriptors.FieldDescriptor field : message.getDescriptorForType().getFields()) { - if (filter.contains(field.getFullName())) { - builder.setField(field, message.getField(field)); - } - } - - return (M) builder.build(); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | From 063200d82fcdd7928ca65b300a1bc494b46061df Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 16:26:05 +0300 Subject: [PATCH 292/361] Trigger`onCompleted()` for the gRPC response observers in the expected cases. --- .../main/java/org/spine3/server/SubscriptionService.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index e321eebf11a..e04bb686627 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -62,8 +62,8 @@ public static Builder newBuilder() { return new Builder(); } - @Override @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. + @Override public void subscribe(Topic topic, StreamObserver responseObserver) { log().debug("Creating the subscription to a topic: {}", topic); @@ -79,12 +79,11 @@ public void subscribe(Topic topic, StreamObserver responseObserver } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { log().error("Error processing subscription request", e); responseObserver.onError(e); - responseObserver.onCompleted(); } } - @Override @SuppressWarnings("RefusedBequest") // as we override default implementation with `unimplemented` status. + @Override public void activate(final Subscription subscription, final StreamObserver responseObserver) { log().debug("Activating the subscription: {}", subscription); @@ -105,11 +104,9 @@ public void onEntityStateUpdate(Any newEntityState) { }; final Stand targetStand = boundedContext.getStand(); targetStand.activate(subscription, updateCallback); - } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { log().error("Error activating the subscription", e); responseObserver.onError(e); - responseObserver.onCompleted(); } } @@ -123,10 +120,10 @@ public void cancel(Subscription subscription, StreamObserver responseO final Stand stand = boundedContext.getStand(); stand.cancel(subscription); responseObserver.onNext(Responses.ok()); + responseObserver.onCompleted(); } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { log().error("Error processing cancel subscription request", e); responseObserver.onError(e); - responseObserver.onCompleted(); } } From 422b0c894799046f8393c5ce88fe1c7d1084cb3e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 16:30:41 +0300 Subject: [PATCH 293/361] Document missing parameter. --- server/src/main/java/org/spine3/server/stand/Stand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 52cf4786816..3ff45ff2f55 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -63,7 +63,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; @@ -148,6 +147,7 @@ public static Builder newBuilder() { * *

        The matching callbacks are executed with the {@link #callbackExecutor}. * + * @param id the entity identifier * @param entityState the entity state */ public void update(final Object id, final Any entityState) { From 00c01038f795c24502d2402da7fe14cf8c432b25 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 16:32:49 +0300 Subject: [PATCH 294/361] Rename the `getKnownAggregateTypes` to `getExposedAggregateTypes` to avoid confusion with `KnownTypes`. --- .../src/main/java/org/spine3/server/stand/Stand.java | 4 ++-- .../test/java/org/spine3/server/stand/StandShould.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 3ff45ff2f55..3f5348ef09b 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -214,7 +214,7 @@ public void cancel(Subscription subscription) { /** * Read all {@link Entity} types exposed for reading by this instance of {@code Stand}. * - *

        The result includes all values from {@link #getKnownAggregateTypes()} as well. + *

        The result includes all values from {@link #getExposedAggregateTypes()} as well. * * @return the set of types as {@link TypeUrl} instances */ @@ -235,7 +235,7 @@ public ImmutableSet getAvailableTypes() { * @return the set of types as {@link TypeUrl} instances */ @CheckReturnValue - public ImmutableSet getKnownAggregateTypes() { + public ImmutableSet getExposedAggregateTypes() { final ImmutableSet result = ImmutableSet.copyOf(knownAggregateTypes); return result; } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 50a5ad9b58f..86bc2110f55 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -114,7 +114,7 @@ public void initialize_with_empty_builder() { assertNotNull(stand); assertTrue("Available types must be empty after the initialization.", stand.getAvailableTypes() .isEmpty()); - assertTrue("Known aggregate types must be empty after the initialization", stand.getKnownAggregateTypes() + assertTrue("Exposed aggregate types must be empty after the initialization", stand.getExposedAggregateTypes() .isEmpty()); } @@ -131,7 +131,7 @@ public void register_projection_repositories() { stand.registerTypeSupplier(standTestProjectionRepo); checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); - final ImmutableSet knownAggregateTypes = stand.getKnownAggregateTypes(); + final ImmutableSet knownAggregateTypes = stand.getExposedAggregateTypes(); // As we registered a projection repo, known aggregate types should be still empty. assertTrue("For some reason an aggregate type was registered", knownAggregateTypes.isEmpty()); @@ -153,13 +153,13 @@ public void register_aggregate_repositories() { final Descriptors.Descriptor customerEntityDescriptor = Customer.getDescriptor(); checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); - checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); + checkHasExactlyOne(stand.getExposedAggregateTypes(), customerEntityDescriptor); @SuppressWarnings("LocalVariableNamingConvention") final CustomerAggregateRepository anotherCustomerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(anotherCustomerAggregateRepo); checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); - checkHasExactlyOne(stand.getKnownAggregateTypes(), customerEntityDescriptor); + checkHasExactlyOne(stand.getExposedAggregateTypes(), customerEntityDescriptor); } @Test @@ -1073,7 +1073,7 @@ public boolean matches(EntityStorageRecord argument) { private static void checkTypesEmpty(Stand stand) { assertTrue(stand.getAvailableTypes() .isEmpty()); - assertTrue(stand.getKnownAggregateTypes() + assertTrue(stand.getExposedAggregateTypes() .isEmpty()); } From b5378eebc6a9bb863c5ce511f72c03ea8bf21743 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 16:37:25 +0300 Subject: [PATCH 295/361] Rename the `Stand#getAvailableTypes` to `getExposedTypes` for consistency; document the method behaviour better. --- .../java/org/spine3/server/QueryService.java | 6 +- .../spine3/server/SubscriptionService.java | 4 +- .../java/org/spine3/server/stand/Stand.java | 6 +- .../server/SubscriptionServiceShould.java | 2 +- .../org/spine3/server/stand/StandShould.java | 114 ++++++++++-------- 5 files changed, 72 insertions(+), 60 deletions(-) diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index 8e708b650a5..78a59aa4c4a 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -120,10 +120,10 @@ private ImmutableMap createBoundedContextMap() { private static void addBoundedContext(ImmutableMap.Builder mapBuilder, BoundedContext boundedContext) { - final ImmutableSet availableTypes = boundedContext.getStand() - .getAvailableTypes(); + final ImmutableSet exposedTypes = boundedContext.getStand() + .getExposedTypes(); - for (TypeUrl availableType : availableTypes) { + for (TypeUrl availableType : exposedTypes) { mapBuilder.put(availableType, boundedContext); } } diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index e04bb686627..be91d5502c0 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -194,8 +194,8 @@ private static void addBoundedContext(ImmutableMap.Builder availableTypes = stand.getAvailableTypes(); - for (TypeUrl availableType : availableTypes) { + final ImmutableSet exposedTypes = stand.getExposedTypes(); + for (TypeUrl availableType : exposedTypes) { mapBuilder.put(availableType, boundedContext); } } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 3f5348ef09b..ef815236fb5 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -214,12 +214,14 @@ public void cancel(Subscription subscription) { /** * Read all {@link Entity} types exposed for reading by this instance of {@code Stand}. * + *

        Use {@link Stand#registerTypeSupplier(Repository)} to expose a type. + * *

        The result includes all values from {@link #getExposedAggregateTypes()} as well. * * @return the set of types as {@link TypeUrl} instances */ @CheckReturnValue - public ImmutableSet getAvailableTypes() { + public ImmutableSet getExposedTypes() { final ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); final Set projectionTypes = typeToRepositoryMap.keySet(); resultBuilder.addAll(projectionTypes) @@ -232,6 +234,8 @@ public ImmutableSet getAvailableTypes() { * Read all {@link org.spine3.server.aggregate.Aggregate} entity types exposed for reading * by this instance of {@code Stand}. * + *

        Use {@link Stand#registerTypeSupplier(Repository)} to expose an {@code Aggregate} type. + * * @return the set of types as {@link TypeUrl} instances */ @CheckReturnValue diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index e009de810a3..b0c795cbdd5 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -138,7 +138,7 @@ public void subscribe_to_topic() { .build(); final String type = boundedContext.getStand() - .getAvailableTypes() + .getExposedTypes() .iterator() .next() .getTypeName(); diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 86bc2110f55..d22ec42bc9f 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -105,17 +105,16 @@ public class StandShould { private static final int TOTAL_CUSTOMERS_FOR_BATCH_READING = 10; private static final int TOTAL_PROJECTS_FOR_BATCH_READING = 10; - @Test public void initialize_with_empty_builder() { final Stand.Builder builder = Stand.newBuilder(); final Stand stand = builder.build(); assertNotNull(stand); - assertTrue("Available types must be empty after the initialization.", stand.getAvailableTypes() - .isEmpty()); + assertTrue("Exposed types must be empty after the initialization.", stand.getExposedTypes() + .isEmpty()); assertTrue("Exposed aggregate types must be empty after the initialization", stand.getExposedAggregateTypes() - .isEmpty()); + .isEmpty()); } @@ -129,7 +128,7 @@ public void register_projection_repositories() { final StandTestProjectionRepository standTestProjectionRepo = new StandTestProjectionRepository(boundedContext); stand.registerTypeSupplier(standTestProjectionRepo); - checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); + checkHasExactlyOne(stand.getExposedTypes(), Project.getDescriptor()); final ImmutableSet knownAggregateTypes = stand.getExposedAggregateTypes(); // As we registered a projection repo, known aggregate types should be still empty. @@ -137,7 +136,7 @@ public void register_projection_repositories() { final StandTestProjectionRepository anotherTestProjectionRepo = new StandTestProjectionRepository(boundedContext); stand.registerTypeSupplier(anotherTestProjectionRepo); - checkHasExactlyOne(stand.getAvailableTypes(), Project.getDescriptor()); + checkHasExactlyOne(stand.getExposedTypes(), Project.getDescriptor()); } @Test @@ -152,13 +151,13 @@ public void register_aggregate_repositories() { stand.registerTypeSupplier(customerAggregateRepo); final Descriptors.Descriptor customerEntityDescriptor = Customer.getDescriptor(); - checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); + checkHasExactlyOne(stand.getExposedTypes(), customerEntityDescriptor); checkHasExactlyOne(stand.getExposedAggregateTypes(), customerEntityDescriptor); @SuppressWarnings("LocalVariableNamingConvention") final CustomerAggregateRepository anotherCustomerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(anotherCustomerAggregateRepo); - checkHasExactlyOne(stand.getAvailableTypes(), customerEntityDescriptor); + checkHasExactlyOne(stand.getExposedTypes(), customerEntityDescriptor); checkHasExactlyOne(stand.getExposedAggregateTypes(), customerEntityDescriptor); } @@ -198,7 +197,6 @@ public void operate_with_storage_provided_through_builder() { final CustomerAggregateRepository customerAggregateRepo = new CustomerAggregateRepository(boundedContext); stand.registerTypeSupplier(customerAggregateRepo); - final int numericIdValue = 17; final CustomerId customerId = customerIdFor(numericIdValue); final CustomerAggregate customerAggregate = customerAggregateRepo.create(customerId); @@ -248,7 +246,6 @@ public void return_empty_list_to_unknown_type_reading() { @Test public void return_empty_list_for_aggregate_read_by_ids_on_empty_stand_storage() { - final Query readCustomersById = Queries.readByIds(Customer.class, newHashSet( customerIdFor(1), customerIdFor(2) )); @@ -278,7 +275,6 @@ public void return_single_result_for_projection_read_by_id() { doCheckReadingProjectsById(1); } - @Test public void return_multiple_results_for_projection_batch_read_by_ids() { doCheckReadingProjectsById(TOTAL_PROJECTS_FOR_BATCH_READING); @@ -286,10 +282,13 @@ public void return_multiple_results_for_projection_batch_read_by_ids() { @Test public void return_multiple_results_for_projection_batch_read_by_ids_with_field_mask() { - final List projectFields = Project.getDescriptor().getFields(); + final List projectFields = Project.getDescriptor() + .getFields(); doCheckReadingCustomersByIdAndFieldMask( - projectFields.get(0).getFullName(), // ID - projectFields.get(1).getFullName()); // Name + projectFields.get(0) + .getFullName(), // ID + projectFields.get(1) + .getFullName()); // Name } @Test @@ -314,7 +313,6 @@ public void trigger_subscription_callback_upon_update_of_aggregate() { assertEquals(packedState, memoizeCallback.newEntityState); } - @Test public void trigger_subscription_callback_upon_update_of_projection() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); @@ -337,7 +335,6 @@ public void trigger_subscription_callback_upon_update_of_projection() { assertEquals(packedState, memoizeCallback.newEntityState); } - @Test public void allow_cancelling_subscriptions() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); @@ -372,7 +369,6 @@ public void do_not_fail_if_cancelling_inexistent_subscription() { stand.cancel(inexistentSubscription); } - @SuppressWarnings("MethodWithMultipleLoops") @Test public void trigger_each_subscription_callback_once_for_multiple_subscriptions() { @@ -387,7 +383,6 @@ public void trigger_each_subscription_callback_once_for_multiple_subscriptions() callbacks.add(callback); } - final Map.Entry sampleData = fillSampleCustomers(1).entrySet() .iterator() .next(); @@ -396,7 +391,6 @@ public void trigger_each_subscription_callback_once_for_multiple_subscriptions() final Any packedState = AnyPacker.pack(customer); stand.update(customerId, packedState); - for (MemoizeStandUpdateCallback callback : callbacks) { assertEquals(packedState, callback.newEntityState); verify(callback, times(1)).onEntityStateUpdate(any(Any.class)); @@ -471,7 +465,8 @@ private static ProjectId projectIdFor(int numericId) { @Test public void retrieve_all_data_if_field_mask_is_not_set() { - final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder() + .build()); final Customer sampleCustomer = getSampleCustomer(); @@ -488,8 +483,10 @@ public void onNext(QueryResponse value) { assertFalse(messages.isEmpty()); final Customer customer = AnyPacker.unpack(messages.get(0)); - for (Descriptors.FieldDescriptor field : customer.getDescriptorForType().getFields()) { - assertTrue(customer.getField(field).equals(sampleCustomer.getField(field))); + for (Descriptors.FieldDescriptor field : customer.getDescriptorForType() + .getFields()) { + assertTrue(customer.getField(field) + .equals(sampleCustomer.getField(field))); } } }; @@ -501,7 +498,7 @@ public void onNext(QueryResponse value) { @Test public void retrieve_only_selected_param_for_query() { - requestSampleCustomer(new int[] {Customer.NAME_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { + requestSampleCustomer(new int[]{Customer.NAME_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { super.onNext(value); @@ -514,14 +511,15 @@ public void onNext(QueryResponse value) { assertTrue(customer.getName() .equals(sampleCustomer.getName())); assertFalse(customer.hasId()); - assertTrue(customer.getNicknamesList().isEmpty()); + assertTrue(customer.getNicknamesList() + .isEmpty()); } }); } @Test public void retrieve_collection_fields_if_required() { - requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { + requestSampleCustomer(new int[]{Customer.NICKNAMES_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { super.onNext(value); @@ -541,7 +539,7 @@ public void onNext(QueryResponse value) { @Test public void retrieve_all_requested_fields() { - requestSampleCustomer(new int[] {Customer.NICKNAMES_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { + requestSampleCustomer(new int[]{Customer.NICKNAMES_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { @Override public void onNext(QueryResponse value) { super.onNext(value); @@ -608,14 +606,17 @@ public void select_entity_singleton_by_id_and_apply_field_masks() { @Test public void handle_mistakes_in_query_silently() { //noinspection ZeroLengthArrayAllocation - final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder() + .build()); final Customer sampleCustomer = getSampleCustomer(); stand.update(sampleCustomer.getId(), AnyPacker.pack(sampleCustomer)); // FieldMask with invalid type URLs. - final String[] paths = {"invalid_type_url_example", Project.getDescriptor().getFields().get(2).getFullName()}; + final String[] paths = {"invalid_type_url_example", Project.getDescriptor() + .getFields() + .get(2).getFullName()}; final Query customerQuery = Queries.readAll(Customer.class, paths); @@ -670,17 +671,23 @@ public void onNext(QueryResponse value) { private static Customer getSampleCustomer() { //noinspection NumericCastThatLosesPrecision return Customer.newBuilder() - .setId(CustomerId.newBuilder().setNumber((int) UUID.randomUUID() - .getLeastSignificantBits())) - .setName(PersonName.newBuilder().setGivenName("Socrates").build()) - .addNicknames(PersonName.newBuilder().setGivenName("Philosopher")) - .addNicknames(PersonName.newBuilder().setGivenName("Wise guy")) + .setId(CustomerId.newBuilder() + .setNumber((int) UUID.randomUUID() + .getLeastSignificantBits())) + .setName(PersonName.newBuilder() + .setGivenName("Socrates") + .build()) + .addNicknames(PersonName.newBuilder() + .setGivenName("Philosopher")) + .addNicknames(PersonName.newBuilder() + .setGivenName("Wise guy")) .build(); } private static void requestSampleCustomer(int[] fieldIndexes, final MemoizeQueryResponseObserver observer) { - final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder() + .build()); final Customer sampleCustomer = getSampleCustomer(); @@ -690,9 +697,9 @@ private static void requestSampleCustomer(int[] fieldIndexes, final MemoizeQuery for (int i = 0; i < fieldIndexes.length; i++) { paths[i] = Customer.getDescriptor() - .getFields() - .get(fieldIndexes[i]) - .getFullName(); + .getFields() + .get(fieldIndexes[i]) + .getFullName(); } final Query customerQuery = Queries.readAll(Customer.class, paths); @@ -745,7 +752,8 @@ private static void doCheckReadingProjectsById(int numberOfProjects) { @SuppressWarnings("MethodWithMultipleLoops") private static void doCheckReadingCustomersByIdAndFieldMask(String... paths) { - final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder().build()); + final Stand stand = prepareStandWithAggregateRepo(InMemoryStandStorage.newBuilder() + .build()); final int querySize = 2; @@ -753,8 +761,9 @@ private static void doCheckReadingCustomersByIdAndFieldMask(String... paths) { for (int i = 0; i < querySize; i++) { final Customer customer = getSampleCustomer().toBuilder() - .setId(CustomerId.newBuilder().setNumber(i)) - .build(); + .setId(CustomerId.newBuilder() + .setNumber(i)) + .build(); stand.update(customer.getId(), AnyPacker.pack(customer)); @@ -763,7 +772,9 @@ private static void doCheckReadingCustomersByIdAndFieldMask(String... paths) { final Query customerQuery = Queries.readByIds(Customer.class, ids, paths); - final FieldMask fieldMask = FieldMask.newBuilder().addAllPaths(Arrays.asList(paths)).build(); + final FieldMask fieldMask = FieldMask.newBuilder() + .addAllPaths(Arrays.asList(paths)) + .build(); final MemoizeQueryResponseObserver observer = new MemoizeQueryResponseObserver() { @Override @@ -840,10 +851,9 @@ private static void checkEmptyResultOnNonEmptyStorageForQueryTarget(Target custo assertTrue("Query returned a non-empty response message list though the filter was not set", messageList.isEmpty()); } - @SuppressWarnings("ConstantConditions") private static void setupExpectedBulkReadBehaviour(Map sampleCustomers, TypeUrl customerType, - StandStorage standStorageMock) { + StandStorage standStorageMock) { final ImmutableList.Builder stateIdsBuilder = ImmutableList.builder(); final ImmutableList.Builder recordsBuilder = ImmutableList.builder(); for (CustomerId customerId : sampleCustomers.keySet()) { @@ -868,7 +878,7 @@ private static void setupExpectedBulkReadBehaviour(Map sam @SuppressWarnings("ConstantConditions") private static void setupExpectedFindAllBehaviour(Map sampleProjects, - StandTestProjectionRepository projectionRepository) { + StandTestProjectionRepository projectionRepository) { final Set projectIds = sampleProjects.keySet(); final ImmutableCollection allResults = toProjectionCollection(projectIds); @@ -967,7 +977,6 @@ private static Map fillSampleCustomers(int numberOfCustome for (int customerIndex = 0; customerIndex < numberOfCustomers; customerIndex++) { - final int numericId = randomizer.nextInt(); final CustomerId customerId = customerIdFor(numericId); final Customer customer = Customer.newBuilder() @@ -1017,10 +1026,10 @@ private static void fillRichSampleProjects(Map sampleProject .build(); final Project project = Project.newBuilder() - .setId(projectId) - .setName(String.valueOf(projectIndex)) - .setStatus(Project.Status.CREATED) - .build(); + .setId(projectId) + .setName(String.valueOf(projectIndex)) + .setStatus(Project.Status.CREATED) + .build(); sampleProjects.put(projectId, project); } @@ -1039,7 +1048,6 @@ private static List checkAndGetMessageList(MemoizeQueryResponseObserver res return messageList; } - private static Stand prepareStandWithAggregateRepo(StandStorage standStorage) { final Stand stand = Stand.newBuilder() .setStorage(standStorage) @@ -1071,7 +1079,7 @@ public boolean matches(EntityStorageRecord argument) { } private static void checkTypesEmpty(Stand stand) { - assertTrue(stand.getAvailableTypes() + assertTrue(stand.getExposedTypes() .isEmpty()); assertTrue(stand.getExposedAggregateTypes() .isEmpty()); @@ -1097,7 +1105,8 @@ public void onEntityStateUpdate(Any newEntityState) { private static void assertMatches(Message message, FieldMask fieldMask) { final List paths = fieldMask.getPathsList(); - for (Descriptors.FieldDescriptor field : message.getDescriptorForType().getFields()) { + for (Descriptors.FieldDescriptor field : message.getDescriptorForType() + .getFields()) { // Protobuf limitation, has no effect on the test. if (field.isRepeated()) { @@ -1108,7 +1117,6 @@ private static void assertMatches(Message message, FieldMask fieldMask) { } } - // ***** Inner classes used for tests. ***** /** From b5619381f0bb209982aa4bd386485852e8f3099b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 16:43:25 +0300 Subject: [PATCH 296/361] Reformat test code to fit 120 symbols per line as much as possible. --- .../org/spine3/server/stand/StandShould.java | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index d22ec42bc9f..60551341c52 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -539,22 +539,23 @@ public void onNext(QueryResponse value) { @Test public void retrieve_all_requested_fields() { - requestSampleCustomer(new int[]{Customer.NICKNAMES_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, new MemoizeQueryResponseObserver() { - @Override - public void onNext(QueryResponse value) { - super.onNext(value); - - final List messages = value.getMessagesList(); - assertFalse(messages.isEmpty()); - - final Customer sampleCustomer = getSampleCustomer(); - final Customer customer = AnyPacker.unpack(messages.get(0)); - assertEquals(customer.getNicknamesList(), sampleCustomer.getNicknamesList()); - - assertFalse(customer.hasName()); - assertTrue(customer.hasId()); - } - }); + requestSampleCustomer(new int[]{Customer.NICKNAMES_FIELD_NUMBER - 1, Customer.ID_FIELD_NUMBER - 1}, + new MemoizeQueryResponseObserver() { + @Override + public void onNext(QueryResponse value) { + super.onNext(value); + + final List messages = value.getMessagesList(); + assertFalse(messages.isEmpty()); + + final Customer sampleCustomer = getSampleCustomer(); + final Customer customer = AnyPacker.unpack(messages.get(0)); + assertEquals(customer.getNicknamesList(), sampleCustomer.getNicknamesList()); + + assertFalse(customer.hasName()); + assertTrue(customer.hasId()); + } + }); } @Test @@ -848,7 +849,8 @@ private static void checkEmptyResultOnNonEmptyStorageForQueryTarget(Target custo verifyObserver(responseObserver); final List messageList = checkAndGetMessageList(responseObserver); - assertTrue("Query returned a non-empty response message list though the filter was not set", messageList.isEmpty()); + assertTrue("Query returned a non-empty response message list " + + "though the filter was not set", messageList.isEmpty()); } @SuppressWarnings("ConstantConditions") @@ -872,7 +874,7 @@ private static void setupExpectedBulkReadBehaviour(Map sam final ImmutableList stateIds = stateIdsBuilder.build(); final ImmutableList records = recordsBuilder.build(); - final Iterable matchingIds = argThat(aggregateIdsIterableMatcher(stateIds)); + final Iterable matchingIds = argThat(idsMatcher(stateIds)); when(standStorageMock.readBulk(matchingIds)).thenReturn(records); } @@ -922,14 +924,16 @@ public boolean matches(EntityFilters argument) { } private static ImmutableCollection toProjectionCollection(Collection values) { - final Collection transformed = Collections2.transform(values, new Function() { - @Nullable - @Override - public StandTestProjection apply(@Nullable ProjectId input) { - checkNotNull(input); - return new StandTestProjection(input); - } - }); + final Collection transformed = + Collections2.transform(values, + new Function() { + @Nullable + @Override + public StandTestProjection apply(@Nullable ProjectId input) { + checkNotNull(input); + return new StandTestProjection(input); + } + }); final ImmutableList result = ImmutableList.copyOf(transformed); return result; } @@ -947,7 +951,7 @@ public boolean matches(Iterable argument) { }; } - private static ArgumentMatcher> aggregateIdsIterableMatcher(final List stateIds) { + private static ArgumentMatcher> idsMatcher(final List stateIds) { return new ArgumentMatcher>() { @Override public boolean matches(Iterable argument) { From f872c30090765c7ec1b990289315490685d5f33e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 18:37:59 +0300 Subject: [PATCH 297/361] Extract query processing into a standalone helper class and ensure it's covered with tests. --- .../spine3/server/stand/QueryProcessor.java | 223 ++++++++++++++++++ .../java/org/spine3/server/stand/Stand.java | 158 +------------ .../server/stand/QueryProcessorShould.java | 39 +++ 3 files changed, 266 insertions(+), 154 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/stand/QueryProcessor.java create mode 100644 server/src/test/java/org/spine3/server/stand/QueryProcessorShould.java diff --git a/server/src/main/java/org/spine3/server/stand/QueryProcessor.java b/server/src/main/java/org/spine3/server/stand/QueryProcessor.java new file mode 100644 index 00000000000..705ce05dd68 --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/QueryProcessor.java @@ -0,0 +1,223 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import io.grpc.stub.StreamObserver; +import org.spine3.base.Queries; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; +import org.spine3.client.Query; +import org.spine3.client.Target; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.entity.Entity; +import org.spine3.server.entity.EntityRepository; +import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.storage.StandStorage; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Query processing helper utility. + * + * @author Alex Tymchenko + */ +/* package */ +@SuppressWarnings("UtilityClass") +class QueryProcessor { + + private QueryProcessor() { + } + + /** + * Performs query processing as a part of {@link Stand#execute(Query, StreamObserver)}. + * + * @param query an instance of {@code Query} to process + * @param standStorage internal storage of {@link Stand} + * @param aggregateTypes {@link org.spine3.server.aggregate.Aggregate} types exposed by the {@code Stand} + * @param typeToRepositoryMap map of exposed types and related repositories registered in the {@code Stand} + * @return the query result + */ + /* package */ + static ImmutableCollection processQuery(Query query, + StandStorage standStorage, + Set aggregateTypes, + Map> typeToRepositoryMap) { + final ImmutableList.Builder resultBuilder = ImmutableList.builder(); + + final TypeUrl typeUrl = Queries.typeOf(query); + checkNotNull(typeUrl, "Target type unknown"); + + final EntityRepository repository = typeToRepositoryMap.get(typeUrl); + if (repository != null) { + + // the target references an entity state + final ImmutableCollection entities = fetchFromEntityRepository(query, repository); + feedEntitiesToBuilder(resultBuilder, entities); + + } else if (aggregateTypes.contains(typeUrl)) { + + // the target relates to an {@code Aggregate} state + final ImmutableCollection stateRecords = fetchFromStandStorage(query, standStorage, typeUrl); + feedStateRecordsToBuilder(resultBuilder, stateRecords); + } + + final ImmutableList result = resultBuilder.build(); + return result; + } + + private static ImmutableCollection fetchFromEntityRepository( + Query query, + EntityRepository repository) { + + final ImmutableCollection result; + final Target target = query.getTarget(); + final FieldMask fieldMask = query.getFieldMask(); + + if (target.getIncludeAll() && fieldMask.getPathsList() + .isEmpty()) { + result = repository.loadAll(); + } else { + final EntityFilters filters = target.getFilters(); + result = repository.find(filters, fieldMask); + } + return result; + } + + private static void feedEntitiesToBuilder(ImmutableList.Builder resultBuilder, + ImmutableCollection all) { + for (Entity record : all) { + final Message state = record.getState(); + final Any packedState = AnyPacker.pack(state); + resultBuilder.add(packedState); + } + } + + private static void feedStateRecordsToBuilder(ImmutableList.Builder resultBuilder, + ImmutableCollection all) { + for (EntityStorageRecord record : all) { + final Any state = record.getState(); + resultBuilder.add(state); + } + } + + private static ImmutableCollection fetchFromStandStorage(Query query, + StandStorage standStorage, + final TypeUrl typeUrl) { + ImmutableCollection result; + final Target target = query.getTarget(); + final FieldMask fieldMask = query.getFieldMask(); + final boolean shouldApplyFieldMask = !fieldMask.getPathsList() + .isEmpty(); + if (target.getIncludeAll()) { + result = shouldApplyFieldMask ? + standStorage.readAllByType(typeUrl, fieldMask) : + standStorage.readAllByType(typeUrl); + } else { + result = doFetchWithFilters(standStorage, typeUrl, target, fieldMask); + } + + return result; + } + + private static ImmutableCollection doFetchWithFilters(StandStorage standStorage, + TypeUrl typeUrl, + Target target, + FieldMask fieldMask) { + ImmutableCollection result; + final EntityFilters filters = target.getFilters(); + final boolean shouldApplyFieldMask = !fieldMask.getPathsList() + .isEmpty(); + final boolean idsAreDefined = !filters.getIdFilter() + .getIdsList() + .isEmpty(); + if (idsAreDefined) { + final EntityIdFilter idFilter = filters.getIdFilter(); + final Collection stateIds = Collections2.transform(idFilter.getIdsList(), + aggregateStateIdTransformer(typeUrl)); + if (stateIds.size() == 1) { + // no need to trigger bulk reading. + // may be more effective, as bulk reading implies additional time and performance expenses. + final AggregateStateId singleId = stateIds.iterator() + .next(); + final EntityStorageRecord singleResult = shouldApplyFieldMask ? + standStorage.read(singleId, fieldMask) : + standStorage.read(singleId); + result = ImmutableList.of(singleResult); + } else { + result = handleBulkRead(standStorage, stateIds, fieldMask, shouldApplyFieldMask); + } + } else { + result = ImmutableList.of(); + } + return result; + } + + private static ImmutableCollection handleBulkRead(StandStorage standStorage, + Collection stateIds, + FieldMask fieldMask, + boolean applyFieldMask) { + ImmutableCollection result; + final Iterable bulkReadResults = applyFieldMask ? + standStorage.readBulk(stateIds, fieldMask) : + standStorage.readBulk(stateIds); + result = FluentIterable.from(bulkReadResults) + .filter(new Predicate() { + @Override + public boolean apply(@Nullable EntityStorageRecord input) { + return input != null; + } + }) + .toList(); + return result; + } + + private static Function aggregateStateIdTransformer(final TypeUrl typeUrl) { + return new Function() { + @Nullable + @Override + public AggregateStateId apply(@Nullable EntityId input) { + checkNotNull(input); + + final Any rawId = input.getId(); + final Message unpackedId = AnyPacker.unpack(rawId); + final AggregateStateId stateId = AggregateStateId.of(unpackedId, typeUrl); + return stateId; + } + }; + } +} diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index ef815236fb5..3a31094dc39 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -21,22 +21,15 @@ */ package org.spine3.server.stand; -import com.google.common.base.Function; import com.google.common.base.Objects; -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; -import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; import org.spine3.base.Identifiers; -import org.spine3.base.Queries; import org.spine3.base.Responses; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; @@ -45,7 +38,6 @@ import org.spine3.client.QueryResponse; import org.spine3.client.Subscription; import org.spine3.client.Target; -import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.Timestamps; import org.spine3.protobuf.TypeUrl; import org.spine3.server.aggregate.AggregateRepository; @@ -57,8 +49,6 @@ import org.spine3.server.storage.memory.InMemoryStandStorage; import javax.annotation.CheckReturnValue; -import javax.annotation.Nullable; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -67,7 +57,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Maps.newHashMap; @@ -255,7 +244,10 @@ public ImmutableSet getExposedAggregateTypes() { * @param responseObserver an observer to feed the query results to. */ public void execute(Query query, StreamObserver responseObserver) { - final ImmutableCollection readResult = internalExecute(query); + final ImmutableCollection readResult = QueryProcessor.processQuery(query, + storage, + knownAggregateTypes, + typeToRepositoryMap); final QueryResponse response = QueryResponse.newBuilder() .addAllMessages(readResult) .setResponse(Responses.ok()) @@ -264,80 +256,6 @@ public void execute(Query query, StreamObserver responseObserver) responseObserver.onCompleted(); } - private ImmutableCollection internalExecute(Query query) { - final ImmutableList.Builder resultBuilder = ImmutableList.builder(); - - final TypeUrl typeUrl = Queries.typeOf(query); - checkNotNull(typeUrl, "Target type unknown"); - - final EntityRepository repository = typeToRepositoryMap.get(typeUrl); - if (repository != null) { - - // the target references an entity state - final ImmutableCollection entities = fetchFromEntityRepository(query, repository); - feedEntitiesToBuilder(resultBuilder, entities); - - } else if (knownAggregateTypes.contains(typeUrl)) { - - // the target relates to an {@code Aggregate} state - final ImmutableCollection stateRecords = fetchFromStandStorage(query, typeUrl); - feedStateRecordsToBuilder(resultBuilder, stateRecords); - } - - final ImmutableList result = resultBuilder.build(); - return result; - } - - private ImmutableCollection fetchFromStandStorage(Query query, final TypeUrl typeUrl) { - ImmutableCollection result; - final Target target = query.getTarget(); - final FieldMask fieldMask = query.getFieldMask(); - final boolean shouldApplyFieldMask = !fieldMask.getPathsList() - .isEmpty(); - if (target.getIncludeAll()) { - result = shouldApplyFieldMask ? - storage.readAllByType(typeUrl, fieldMask) : - storage.readAllByType(typeUrl); - } else { - - result = doFetchWithFilters(typeUrl, target, fieldMask); - } - - return result; - } - - private ImmutableCollection doFetchWithFilters(TypeUrl typeUrl, - Target target, - FieldMask fieldMask) { - ImmutableCollection result; - final EntityFilters filters = target.getFilters(); - final boolean shouldApplyFieldMask = !fieldMask.getPathsList() - .isEmpty(); - final boolean idsAreDefined = !filters.getIdFilter() - .getIdsList() - .isEmpty(); - if (idsAreDefined) { - final EntityIdFilter idFilter = filters.getIdFilter(); - final Collection stateIds = Collections2.transform(idFilter.getIdsList(), - aggregateStateIdTransformer(typeUrl)); - if (stateIds.size() == 1) { - // no need to trigger bulk reading. - // may be more effective, as bulk reading implies additional time and performance expenses. - final AggregateStateId singleId = stateIds.iterator() - .next(); - final EntityStorageRecord singleResult = shouldApplyFieldMask ? - storage.read(singleId, fieldMask) : - storage.read(singleId); - result = ImmutableList.of(singleResult); - } else { - result = handleBulkRead(stateIds, fieldMask, shouldApplyFieldMask); - } - } else { - result = ImmutableList.of(); - } - return result; - } - private void notifyMatchingSubscriptions(Object id, final Any entityState, TypeUrl typeUrl) { if (subscriptionRegistry.hasType(typeUrl)) { final Set allRecords = subscriptionRegistry.byType(typeUrl); @@ -358,74 +276,6 @@ public void run() { } } - private ImmutableCollection handleBulkRead(Collection stateIds, - FieldMask fieldMask, - boolean applyFieldMask) { - ImmutableCollection result; - final Iterable bulkReadResults = applyFieldMask ? - storage.readBulk(stateIds, fieldMask) : - storage.readBulk(stateIds); - result = FluentIterable.from(bulkReadResults) - .filter(new Predicate() { - @Override - public boolean apply(@Nullable EntityStorageRecord input) { - return input != null; - } - }) - .toList(); - return result; - } - - private static Function aggregateStateIdTransformer(final TypeUrl typeUrl) { - return new Function() { - @Nullable - @Override - public AggregateStateId apply(@Nullable EntityId input) { - checkNotNull(input); - - final Any rawId = input.getId(); - final Message unpackedId = AnyPacker.unpack(rawId); - final AggregateStateId stateId = AggregateStateId.of(unpackedId, typeUrl); - return stateId; - } - }; - } - - private static ImmutableCollection fetchFromEntityRepository( - Query query, - EntityRepository repository) { - - final ImmutableCollection result; - final Target target = query.getTarget(); - final FieldMask fieldMask = query.getFieldMask(); - - if (target.getIncludeAll() && fieldMask.getPathsList() - .isEmpty()) { - result = repository.loadAll(); - } else { - final EntityFilters filters = target.getFilters(); - result = repository.find(filters, fieldMask); - } - return result; - } - - private static void feedEntitiesToBuilder(ImmutableList.Builder resultBuilder, - ImmutableCollection all) { - for (Entity record : all) { - final Message state = record.getState(); - final Any packedState = AnyPacker.pack(state); - resultBuilder.add(packedState); - } - } - - private static void feedStateRecordsToBuilder(ImmutableList.Builder resultBuilder, - ImmutableCollection all) { - for (EntityStorageRecord record : all) { - final Any state = record.getState(); - resultBuilder.add(state); - } - } - /** * Register a supplier for the objects of a certain {@link TypeUrl} to be able * to read them in response to a {@link org.spine3.client.Query}. diff --git a/server/src/test/java/org/spine3/server/stand/QueryProcessorShould.java b/server/src/test/java/org/spine3/server/stand/QueryProcessorShould.java new file mode 100644 index 00000000000..08810d8d4d7 --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/QueryProcessorShould.java @@ -0,0 +1,39 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.spine3.test.Tests.hasPrivateUtilityConstructor; + +/** + * @author Alex Tymchenko + */ +public class QueryProcessorShould { + + @Test + public void have_private_constructor() { + assertTrue(hasPrivateUtilityConstructor(QueryProcessor.class)); + } + +} From b399298469c4c64a5eaf3ed61e655b53faafaaf8 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 18:43:36 +0300 Subject: [PATCH 298/361] Rename `StandUpdateCallback` to `EntityUpdateCallback` and its method to `onStateChanged`. --- .../spine3/server/SubscriptionService.java | 4 +- .../java/org/spine3/server/stand/Stand.java | 27 +++++++------ .../org/spine3/server/stand/StandShould.java | 38 +++++++++---------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index be91d5502c0..79a7b94e3ef 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -90,9 +90,9 @@ public void activate(final Subscription subscription, final StreamObserverAfter the activation, the clients will start receiving the updates via {@code StandUpdateCallback} + *

        After the activation, the clients will start receiving the updates via {@code EntityUpdateCallback} * upon the changes in the entities, defined by the {@code Target} attribute used for this subscription. * * @param subscription the subscription to activate. - * @param callback an instance of {@link StandUpdateCallback} executed upon entity update. + * @param callback an instance of {@link EntityUpdateCallback} executed upon entity update. * @see #subscribe(Target) */ - public void activate(Subscription subscription, StandUpdateCallback callback) { + public void activate(Subscription subscription, EntityUpdateCallback callback) { subscriptionRegistry.activate(subscription, callback); } /** * Cancel the {@link Subscription}. * - *

        Typically invoked to cancel the previous {@link #activate(Subscription, StandUpdateCallback)} call. + *

        Typically invoked to cancel the previous {@link #activate(Subscription, EntityUpdateCallback)} call. *

        After this method is called, the subscribers stop receiving the updates, * related to the given {@code Subscription}. * @@ -268,7 +268,7 @@ private void notifyMatchingSubscriptions(Object id, final Any entityState, TypeU callbackExecutor.execute(new Runnable() { @Override public void run() { - subscriptionRecord.callback.onEntityStateUpdate(entityState); + subscriptionRecord.callback.onStateChanged(entityState); } }); } @@ -313,12 +313,17 @@ public void close() throws Exception { /** * A contract for the callbacks to be executed upon entity state change. * - * @see #activate(Subscription, StandUpdateCallback) + * @see #activate(Subscription, EntityUpdateCallback) * @see #cancel(Subscription) */ - public interface StandUpdateCallback { + public interface EntityUpdateCallback { - void onEntityStateUpdate(Any newEntityState); + /** + * Called when a certain entity state is updated. + * + * @param newEntityState new state of the entity + */ + void onStateChanged(Any newEntityState); } public static class Builder { @@ -388,7 +393,7 @@ private static final class StandSubscriptionRegistry { private final Map> typeToAttrs = newHashMap(); private final Map subscriptionToAttrs = newHashMap(); - private synchronized void activate(Subscription subscription, StandUpdateCallback callback) { + private synchronized void activate(Subscription subscription, EntityUpdateCallback callback) { checkState(subscriptionToAttrs.containsKey(subscription), "Cannot find the subscription in the registry."); final SubscriptionRecord subscriptionRecord = subscriptionToAttrs.get(subscription); @@ -446,7 +451,7 @@ private static final class SubscriptionRecord { private final Subscription subscription; private final Target target; private final TypeUrl type; - private StandUpdateCallback callback = null; + private EntityUpdateCallback callback = null; private SubscriptionRecord(Subscription subscription, Target target, TypeUrl type) { this.subscription = subscription; @@ -454,7 +459,7 @@ private SubscriptionRecord(Subscription subscription, Target target, TypeUrl typ this.type = type; } - private void activate(StandUpdateCallback callback) { + private void activate(EntityUpdateCallback callback) { this.callback = callback; } diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 60551341c52..50ab8ecafcd 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -296,7 +296,7 @@ public void trigger_subscription_callback_upon_update_of_aggregate() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final Target allCustomers = Queries.Targets.allOf(Customer.class); - final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); + final MemoizeEntityUpdateCallback memoizeCallback = new MemoizeEntityUpdateCallback(); final Subscription subscription = stand.subscribe(allCustomers); stand.activate(subscription, memoizeCallback); assertNotNull(subscription); @@ -318,7 +318,7 @@ public void trigger_subscription_callback_upon_update_of_projection() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final Target allProjects = Queries.Targets.allOf(Project.class); - final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); + final MemoizeEntityUpdateCallback memoizeCallback = new MemoizeEntityUpdateCallback(); final Subscription subscription = stand.subscribe(allProjects); stand.activate(subscription, memoizeCallback); assertNotNull(subscription); @@ -340,7 +340,7 @@ public void allow_cancelling_subscriptions() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final Target allCustomers = Queries.Targets.allOf(Customer.class); - final MemoizeStandUpdateCallback memoizeCallback = new MemoizeStandUpdateCallback(); + final MemoizeEntityUpdateCallback memoizeCallback = new MemoizeEntityUpdateCallback(); final Subscription subscription = stand.subscribe(allCustomers); stand.activate(subscription, memoizeCallback); assertNull(memoizeCallback.newEntityState); @@ -375,11 +375,11 @@ public void trigger_each_subscription_callback_once_for_multiple_subscriptions() final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final Target allCustomers = Queries.Targets.allOf(Customer.class); - final Set callbacks = newHashSet(); + final Set callbacks = newHashSet(); final int totalCallbacks = 100; for (int callbackIndex = 0; callbackIndex < totalCallbacks; callbackIndex++) { - final MemoizeStandUpdateCallback callback = subscribeWithCallback(stand, allCustomers); + final MemoizeEntityUpdateCallback callback = subscribeWithCallback(stand, allCustomers); callbacks.add(callback); } @@ -391,9 +391,9 @@ public void trigger_each_subscription_callback_once_for_multiple_subscriptions() final Any packedState = AnyPacker.pack(customer); stand.update(customerId, packedState); - for (MemoizeStandUpdateCallback callback : callbacks) { + for (MemoizeEntityUpdateCallback callback : callbacks) { assertEquals(packedState, callback.newEntityState); - verify(callback, times(1)).onEntityStateUpdate(any(Any.class)); + verify(callback, times(1)).onStateChanged(any(Any.class)); } } @@ -401,7 +401,7 @@ public void trigger_each_subscription_callback_once_for_multiple_subscriptions() public void do_not_trigger_subscription_callbacks_in_case_of_another_type_criterion_mismatch() { final Stand stand = prepareStandWithAggregateRepo(mock(StandStorage.class)); final Target allProjects = Queries.Targets.allOf(Project.class); - final MemoizeStandUpdateCallback callback = subscribeWithCallback(stand, allProjects); + final MemoizeEntityUpdateCallback callback = subscribeWithCallback(stand, allProjects); final Map.Entry sampleData = fillSampleCustomers(1).entrySet() .iterator() @@ -411,7 +411,7 @@ public void do_not_trigger_subscription_callbacks_in_case_of_another_type_criter final Any packedState = AnyPacker.pack(customer); stand.update(customerId, packedState); - verify(callback, never()).onEntityStateUpdate(any(Any.class)); + verify(callback, never()).onStateChanged(any(Any.class)); } @Test @@ -422,10 +422,10 @@ public void trigger_subscription_callbacks_matching_by_id() { final Target someCustomers = Queries.Targets.someOf(Customer.class, sampleCustomers.keySet()); final Set callbackStates = newHashSet(); - final MemoizeStandUpdateCallback callback = new MemoizeStandUpdateCallback() { + final MemoizeEntityUpdateCallback callback = new MemoizeEntityUpdateCallback() { @Override - public void onEntityStateUpdate(Any newEntityState) { - super.onEntityStateUpdate(newEntityState); + public void onStateChanged(Any newEntityState) { + super.onStateChanged(newEntityState); final Customer customerInCallback = AnyPacker.unpack(newEntityState); callbackStates.add(customerInCallback); } @@ -443,8 +443,8 @@ public void onEntityStateUpdate(Any newEntityState) { assertEquals(newHashSet(sampleCustomers.values()), callbackStates); } - private static MemoizeStandUpdateCallback subscribeWithCallback(Stand stand, Target subscriptionTarget) { - final MemoizeStandUpdateCallback callback = spy(new MemoizeStandUpdateCallback()); + private static MemoizeEntityUpdateCallback subscribeWithCallback(Stand stand, Target subscriptionTarget) { + final MemoizeEntityUpdateCallback callback = spy(new MemoizeEntityUpdateCallback()); final Subscription subscription = stand.subscribe(subscriptionTarget); stand.activate(subscription, callback); assertNull(callback.newEntityState); @@ -1098,10 +1098,10 @@ private static void checkHasExactlyOne(Set availableTypes, Descriptors. assertEquals("Type was registered incorrectly", expectedTypeUrl, actualTypeUrl); } - private static Stand.StandUpdateCallback emptyUpdateCallback() { - return new Stand.StandUpdateCallback() { + private static Stand.EntityUpdateCallback emptyUpdateCallback() { + return new Stand.EntityUpdateCallback() { @Override - public void onEntityStateUpdate(Any newEntityState) { + public void onStateChanged(Any newEntityState) { //do nothing } }; @@ -1149,12 +1149,12 @@ public void onCompleted() { } - private static class MemoizeStandUpdateCallback implements Stand.StandUpdateCallback { + private static class MemoizeEntityUpdateCallback implements Stand.EntityUpdateCallback { private Any newEntityState; @Override - public void onEntityStateUpdate(Any newEntityState) { + public void onStateChanged(Any newEntityState) { this.newEntityState = newEntityState; } } From bf86807c02945d444e9b3c7383986ac2c918c641 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 18:44:04 +0300 Subject: [PATCH 299/361] Remove unused test helper method. --- .../org/spine3/server/stand/StandShould.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 50ab8ecafcd..6ff69545c08 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -1022,23 +1022,6 @@ private static void fillSampleProjects(Map sampleProjects, i } } - private static void fillRichSampleProjects(Map sampleProjects, int numberOfProjects) { - for (int projectIndex = 0; projectIndex < numberOfProjects; projectIndex++) { - final ProjectId projectId = ProjectId.newBuilder() - .setId(UUID.randomUUID() - .toString()) - .build(); - - final Project project = Project.newBuilder() - .setId(projectId) - .setName(String.valueOf(projectIndex)) - .setStatus(Project.Status.CREATED) - .build(); - - sampleProjects.put(projectId, project); - } - } - private static List checkAndGetMessageList(MemoizeQueryResponseObserver responseObserver) { assertTrue("Query has not completed successfully", responseObserver.isCompleted); assertNull("Throwable has been caught upon query execution", responseObserver.throwable); From b80b4df8b85ac50de496d2fc2749409d420836d4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:02:55 +0300 Subject: [PATCH 300/361] Extract the subscription registry and subscription record to make Stand more readable; document the methods of the extracted classes and make the classes @Internal. --- .../java/org/spine3/server/stand/Stand.java | 156 +----------------- .../server/stand/SubscriptionRecord.java | 141 ++++++++++++++++ .../server/stand/SubscriptionRegistry.java | 135 +++++++++++++++ 3 files changed, 279 insertions(+), 153 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java create mode 100644 server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index ffea1b09038..ced9d842d57 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -21,7 +21,6 @@ */ package org.spine3.server.stand; -import com.google.common.base.Objects; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -29,11 +28,7 @@ import com.google.protobuf.Any; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; -import org.spine3.base.Identifiers; import org.spine3.base.Responses; -import org.spine3.client.EntityFilters; -import org.spine3.client.EntityId; -import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; import org.spine3.client.QueryResponse; import org.spine3.client.Subscription; @@ -49,17 +44,11 @@ import org.spine3.server.storage.memory.InMemoryStandStorage; import javax.annotation.CheckReturnValue; -import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.Maps.newHashMap; - /** * A container for storing the lastest {@link org.spine3.server.aggregate.Aggregate} states. * @@ -86,7 +75,7 @@ public class Stand implements AutoCloseable { /** * Manages the subscriptions for this instance of {@code Stand}. */ - private final StandSubscriptionRegistry subscriptionRegistry = new StandSubscriptionRegistry(); + private final SubscriptionRegistry subscriptionRegistry = new SubscriptionRegistry(); /** * An instance of executor used to invoke callbacks @@ -268,7 +257,8 @@ private void notifyMatchingSubscriptions(Object id, final Any entityState, TypeU callbackExecutor.execute(new Runnable() { @Override public void run() { - subscriptionRecord.callback.onStateChanged(entityState); + subscriptionRecord.getCallback() + .onStateChanged(entityState); } }); } @@ -383,144 +373,4 @@ public Stand build() { } } - /** - * Registry for subscription management. - * - *

        Provides a quick access to the subscription records by {@link TypeUrl}. - *

        Responsible for {@link Subscription} object instantiation. - */ - private static final class StandSubscriptionRegistry { - private final Map> typeToAttrs = newHashMap(); - private final Map subscriptionToAttrs = newHashMap(); - - private synchronized void activate(Subscription subscription, EntityUpdateCallback callback) { - checkState(subscriptionToAttrs.containsKey(subscription), - "Cannot find the subscription in the registry."); - final SubscriptionRecord subscriptionRecord = subscriptionToAttrs.get(subscription); - subscriptionRecord.activate(callback); - } - - private synchronized Subscription addSubscription(Target target) { - final String subscriptionId = Identifiers.newUuid(); - final String typeAsString = target.getType(); - final TypeUrl type = TypeUrl.of(typeAsString); - final Subscription subscription = Subscription.newBuilder() - .setId(subscriptionId) - .setType(typeAsString) - .build(); - final SubscriptionRecord attributes = new SubscriptionRecord(subscription, target, type); - - if (!typeToAttrs.containsKey(type)) { - typeToAttrs.put(type, new HashSet()); - } - typeToAttrs.get(type) - .add(attributes); - - subscriptionToAttrs.put(subscription, attributes); - return subscription; - } - - private synchronized void removeSubscription(Subscription subscription) { - if (!subscriptionToAttrs.containsKey(subscription)) { - return; - } - final SubscriptionRecord attributes = subscriptionToAttrs.get(subscription); - - if (typeToAttrs.containsKey(attributes.type)) { - typeToAttrs.get(attributes.type) - .remove(attributes); - } - subscriptionToAttrs.remove(subscription); - } - - private synchronized Set byType(TypeUrl type) { - final Set result = typeToAttrs.get(type); - return result; - } - - private synchronized boolean hasType(TypeUrl type) { - final boolean result = typeToAttrs.containsKey(type); - return result; - } - } - - /** - * Represents the attributes of a single subscription. - */ - private static final class SubscriptionRecord { - private final Subscription subscription; - private final Target target; - private final TypeUrl type; - private EntityUpdateCallback callback = null; - - private SubscriptionRecord(Subscription subscription, Target target, TypeUrl type) { - this.subscription = subscription; - this.target = target; - this.type = type; - } - - private void activate(EntityUpdateCallback callback) { - this.callback = callback; - } - - private boolean isActive() { - final boolean result = this.callback != null; - return result; - } - - private boolean matches( - TypeUrl type, - Object id, - // entityState will be later used for more advanced filtering - @SuppressWarnings("UnusedParameters") Any entityState - ) { - final boolean result; - - final boolean typeMatches = this.type.equals(type); - if (typeMatches) { - final boolean includeAll = target.getIncludeAll(); - final EntityFilters filters = target.getFilters(); - result = includeAll || matchByFilters(id, filters); - } else { - result = false; - } - return result; - } - - private static boolean matchByFilters(Object id, EntityFilters filters) { - final boolean result; - final EntityIdFilter givenIdFilter = filters - .getIdFilter(); - final boolean idFilterSet = !EntityIdFilter.getDefaultInstance() - .equals(givenIdFilter); - if (idFilterSet) { - final Any idAsAny = Identifiers.idToAny(id); - final EntityId givenEntityId = EntityId.newBuilder() - .setId(idAsAny) - .build(); - final List idsList = givenIdFilter.getIdsList(); - result = idsList.contains(givenEntityId); - } else { - result = false; - } - return result; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof SubscriptionRecord)) { - return false; - } - SubscriptionRecord that = (SubscriptionRecord) o; - return Objects.equal(subscription, that.subscription); - } - - @Override - public int hashCode() { - return Objects.hashCode(subscription); - } - } } diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java new file mode 100644 index 00000000000..2ddc32e5e71 --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java @@ -0,0 +1,141 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.base.Objects; +import com.google.protobuf.Any; +import org.spine3.Internal; +import org.spine3.base.Identifiers; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; +import org.spine3.client.Subscription; +import org.spine3.client.Target; +import org.spine3.protobuf.TypeUrl; + +import java.util.List; + +/** + * Represents the attributes of a single subscription. + * + * @see SubscriptionRegistry + */ +@Internal +/* package */ final class SubscriptionRecord { + private final Subscription subscription; + private final Target target; + private final TypeUrl type; + private Stand.EntityUpdateCallback callback = null; + + /* package */ SubscriptionRecord(Subscription subscription, Target target, TypeUrl type) { + this.subscription = subscription; + this.target = target; + this.type = type; + } + + /** + * Attach an activation callback to this record. + * + * @param callback the callback to attach + */ + /* package */ void activate(Stand.EntityUpdateCallback callback) { + this.callback = callback; + } + + /** + * Checks whether this record has a callback attached. + */ + /* package */ boolean isActive() { + final boolean result = this.callback != null; + return result; + } + + /** + * Checks whether this record matches the given parameters. + * + * @param type the type to match + * @param id the ID to match + * @param entityState the entity state to match + * @return {@code true} if this record matches all the given parameters, {@code false} otherwise. + */ + /* package */ boolean matches( + TypeUrl type, + Object id, + // entityState will be later used for more advanced filtering + @SuppressWarnings("UnusedParameters") Any entityState + ) { + final boolean result; + + final boolean typeMatches = this.type.equals(type); + if (typeMatches) { + final boolean includeAll = target.getIncludeAll(); + final EntityFilters filters = target.getFilters(); + result = includeAll || matchByFilters(id, filters); + } else { + result = false; + } + return result; + } + + private static boolean matchByFilters(Object id, EntityFilters filters) { + final boolean result; + final EntityIdFilter givenIdFilter = filters.getIdFilter(); + final boolean idFilterSet = !EntityIdFilter.getDefaultInstance() + .equals(givenIdFilter); + if (idFilterSet) { + final Any idAsAny = Identifiers.idToAny(id); + final EntityId givenEntityId = EntityId.newBuilder() + .setId(idAsAny) + .build(); + final List idsList = givenIdFilter.getIdsList(); + result = idsList.contains(givenEntityId); + } else { + result = false; + } + return result; + } + + /* package */ TypeUrl getType() { + return type; + } + + /* package */ Stand.EntityUpdateCallback getCallback() { + return callback; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SubscriptionRecord)) { + return false; + } + SubscriptionRecord that = (SubscriptionRecord) o; + return Objects.equal(subscription, that.subscription); + } + + @Override + public int hashCode() { + return Objects.hashCode(subscription); + } +} diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java new file mode 100644 index 00000000000..3f04e7c404b --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java @@ -0,0 +1,135 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import org.spine3.Internal; +import org.spine3.base.Identifiers; +import org.spine3.client.Subscription; +import org.spine3.client.Target; +import org.spine3.protobuf.TypeUrl; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Maps.newHashMap; + +/** + * Registry for subscription management. + * + *

        Provides a quick access to the subscription records by {@link TypeUrl}. + *

        Responsible for {@link Subscription} object instantiation. + */ +@Internal +/* package */ final class SubscriptionRegistry { + private final Map> typeToAttrs = newHashMap(); + private final Map subscriptionToAttrs = newHashMap(); + + /** + * Activate the subscription with the passed callback. + * + *

        The callback passed will become associated with the subscription. + * + * @param subscription the subscription to activate + * @param callback the callback to make active + */ + /* package */ + synchronized void activate(Subscription subscription, Stand.EntityUpdateCallback callback) { + checkState(subscriptionToAttrs.containsKey(subscription), + "Cannot find the subscription in the registry."); + final SubscriptionRecord subscriptionRecord = subscriptionToAttrs.get(subscription); + subscriptionRecord.activate(callback); + } + + /** + * Creates a subscription for the passed {@code Target} and adds it to the registry. + * + * @param target the target for a new subscription + * @return the created subscription + */ + /* package */ + synchronized Subscription addSubscription(Target target) { + final String subscriptionId = Identifiers.newUuid(); + final String typeAsString = target.getType(); + final TypeUrl type = TypeUrl.of(typeAsString); + final Subscription subscription = Subscription.newBuilder() + .setId(subscriptionId) + .setType(typeAsString) + .build(); + final SubscriptionRecord attributes = new SubscriptionRecord(subscription, target, type); + + if (!typeToAttrs.containsKey(type)) { + typeToAttrs.put(type, new HashSet()); + } + typeToAttrs.get(type) + .add(attributes); + + subscriptionToAttrs.put(subscription, attributes); + return subscription; + } + + /** + * Remove the subscription from this registry. + * + *

        If there is no such subscription in this instance of {@code SubscriptionRegistry}, invocation has no effect. + * + * @param subscription the subscription to remove + */ + /* package */ + synchronized void removeSubscription(Subscription subscription) { + if (!subscriptionToAttrs.containsKey(subscription)) { + return; + } + final SubscriptionRecord attributes = subscriptionToAttrs.get(subscription); + + if (typeToAttrs.containsKey(attributes.getType())) { + typeToAttrs.get(attributes.getType()) + .remove(attributes); + } + subscriptionToAttrs.remove(subscription); + } + + /** + * Filter the registered {@link SubscriptionRecord}s by their type. + * + * @param type the type to filter by + * @return the collection of filtered records + */ + /* package */ + synchronized Set byType(TypeUrl type) { + final Set result = typeToAttrs.get(type); + return result; + } + + /** + * Checks whether the current registry has the records related to a given type. + * + * @param type the type to check records for + * @return {@code true} if there are records with the given type, {@code false} otherwise + */ + /* package */ + synchronized boolean hasType(TypeUrl type) { + final boolean result = typeToAttrs.containsKey(type); + return result; + } +} From 88240e11f7358152b9cad855a5a6bcaa34c5922a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:14:46 +0300 Subject: [PATCH 301/361] Improve the StandFunnel behaviour description and add `@see` links to the entity repository integration points. --- .../org/spine3/server/stand/StandFunnel.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 1c4e9afc294..f745febe512 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -23,6 +23,8 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import org.spine3.base.Command; +import org.spine3.base.Event; import java.util.concurrent.Executor; @@ -30,20 +32,24 @@ import static com.google.common.base.Preconditions.checkState; /** - * Delivers the latest {@link org.spine3.server.aggregate.Aggregate} states to the {@link Stand}. + * Delivers the latest {@link org.spine3.server.entity.Entity} states from the {@code Entity} repositories + * to the {@link Stand}. * - *

        Note: Unlike {@link org.spine3.server.event.EventBus} and {@link org.spine3.server.command.CommandBus}, which assume - * many publishers and many subscribers, the funnel may have zero or more publishers (typically, instances of - * {@link org.spine3.server.aggregate.AggregateRepository}), but the only subscriber, the instance of {@link Stand}. + *

        Note: Unlike {@link org.spine3.server.event.EventBus} and {@link org.spine3.server.command.CommandBus}, + * which assume many publishers and many subscribers, the funnel may have zero or more publishers (typically, instances + * of {@link org.spine3.server.aggregate.AggregateRepository} or {@link org.spine3.server.projection.ProjectionRepository}), + * but the only subscriber, the instance of {@code Stand}. * *

        In scope of a single {@link org.spine3.server.BoundedContext} there can be the only instance of {@code StandFunnel}. * * @author Alex Tymchenko + * @see org.spine3.server.aggregate.AggregateRepository#dispatch(Command) + * @see org.spine3.server.projection.ProjectionRepository#dispatch(Event) */ public class StandFunnel { /** - * The instance of {@link Stand} to deliver the {@link org.spine3.server.aggregate.Aggregate} state updates to. + * The instance of {@link Stand} to deliver the {@code Entity} state updates to. */ private final Stand stand; @@ -52,7 +58,6 @@ public class StandFunnel { */ private final Executor executor; - private StandFunnel(Builder builder) { this.stand = builder.getStand(); this.executor = builder.getExecutor(); @@ -88,7 +93,7 @@ public static Builder newBuilder() { public static class Builder { /** - * The target {@code Stand} to deliver the {@code Aggregate} updates to. + * The target {@code Stand} to deliver the {@code Entity} updates to. */ private Stand stand; From 711d80ec6cff292e9fa54b303caab202736cd385 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:15:41 +0300 Subject: [PATCH 302/361] Remove a redundant empty line. --- server/src/main/java/org/spine3/server/stand/StandFunnel.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index f745febe512..83f58e5aa1a 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -78,7 +78,6 @@ public void run() { stand.update(id, entityState); } }); - } /** From a6342efd249ac72925a4a6fdf19852a51ee79aef Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:17:13 +0300 Subject: [PATCH 303/361] Clarify the default value of the StandFunnel executor --- server/src/main/java/org/spine3/server/stand/StandFunnel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 83f58e5aa1a..015b293f682 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -99,7 +99,7 @@ public static class Builder { /** * Optional {@code Executor} for delivering the data to {@code Stand}. * - *

        If not set, a default value will be set by the builder. + *

        If not set, a {@link MoreExecutors#directExecutor()} value will be set by the builder. */ private Executor executor; @@ -129,7 +129,7 @@ public Executor getExecutor() { * *

        The value must not be {@code null}. * - *

        If this method is not used, a default value will be used. + *

        If this method is not used, a {@link MoreExecutors#directExecutor()} value will be used. * * @param executor the instance of {@code Executor}. * @return {@code this} instance of {@code Builder} From 176540f681e22ce6926b3007f8cfd4cb4f7cce36 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:25:50 +0300 Subject: [PATCH 304/361] Add better JavaDoc comments and refactor the code to avoid long chain of calls. --- .../spine3/server/storage/RecordStorage.java | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index e76a1f33271..240793f6bcd 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -22,6 +22,7 @@ import com.google.protobuf.Any; import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; import org.spine3.SPI; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.TypeUrl; @@ -49,6 +50,9 @@ protected RecordStorage(boolean multitenant) { super(multitenant); } + /** + * {@inheritDoc} + */ @Override public EntityStorageRecord read(I id) { checkNotClosed(); @@ -62,21 +66,31 @@ public EntityStorageRecord read(I id) { } /** - * Read a single item from the storage. + * Reads a single item from the storage and applies a {@link FieldMask} to it. * - * @param id ID of the item to read. - * @param fieldMask Fields to read. - * @return Non-null {@code EntityStorageRecord} instance. + * @param id ID of the item to read. + * @param fieldMask fields to read. + * @return the item with the given ID and with the {@code FieldMask} applied. * @see #read(Object) */ public EntityStorageRecord read(I id, FieldMask fieldMask) { - final EntityStorageRecord.Builder builder = EntityStorageRecord.newBuilder(read(id)); + final EntityStorageRecord rawResult = read(id); + + final EntityStorageRecord.Builder builder = EntityStorageRecord.newBuilder(rawResult); final Any state = builder.getState(); - builder.setState(AnyPacker.pack(FieldMasks.applyIfValid(fieldMask, AnyPacker.unpack(state), TypeUrl.of(state.getTypeUrl())))); + final TypeUrl type = TypeUrl.of(state.getTypeUrl()); + final Message stateAsMessage = AnyPacker.unpack(state); + + final Message maskedState = FieldMasks.applyIfValid(fieldMask, stateAsMessage, type); + + final Any packedState = AnyPacker.pack(maskedState); + builder.setState(packedState); return builder.build(); } - + /** + * {@inheritDoc} + */ @Override public void write(I id, EntityStorageRecord record) { checkNotNull(id); @@ -86,6 +100,9 @@ public void write(I id, EntityStorageRecord record) { writeInternal(id, record); } + /** + * {@inheritDoc} + */ @Override public Iterable readBulk(Iterable ids) { checkNotClosed(); @@ -94,6 +111,13 @@ public Iterable readBulk(Iterable ids) { return readBulkInternal(ids); } + /** + * Reads multiple items from the storage and apply {@link FieldMask} to each of the results. + * + * @param ids the IDs of the items to read + * @param fieldMask the mask to apply + * @return the items with the given IDs and with the given {@code FieldMask} applied + */ public Iterable readBulk(Iterable ids, FieldMask fieldMask) { checkNotClosed(); checkNotNull(ids); @@ -101,6 +125,9 @@ public Iterable readBulk(Iterable ids, FieldMask fieldMa return readBulkInternal(ids, fieldMask); } + /** + * {@inheritDoc} + */ @Override public Map readAll() { checkNotClosed(); @@ -108,13 +135,18 @@ public Map readAll() { return readAllInternal(); } + /** + * Reads all items from the storage and apply {@link FieldMask} to each of the results. + * + * @param fieldMask the {@code FieldMask} to apply + * @return all items from this repository with the given {@code FieldMask} applied + */ public Map readAll(FieldMask fieldMask) { checkNotClosed(); return readAllInternal(fieldMask); } - // // Internal storage methods //--------------------------- @@ -134,7 +166,6 @@ public Map readAll(FieldMask fieldMask) { /** @see BulkStorageOperationsMixin#readBulk(java.lang.Iterable) */ protected abstract Iterable readBulkInternal(Iterable ids, FieldMask fieldMask); - /** @see BulkStorageOperationsMixin#readAll() */ protected abstract Map readAllInternal(); From 75eab6ae44ec73e133e96582d1f6e0eeab1794ea Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:28:15 +0300 Subject: [PATCH 305/361] Remove an empty line. --- server/src/main/java/org/spine3/server/storage/StandStorage.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index 54f4841d7a8..99814220ef9 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -43,7 +43,6 @@ protected StandStorage(boolean multitenant) { super(multitenant); } - /** * Reads all the state records by the given type. * From 2d1393366b389978b5626381f0c9c79f6eefb921 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:28:51 +0300 Subject: [PATCH 306/361] Remove empty lines. --- .../src/main/java/org/spine3/server/storage/StorageFactory.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/storage/StorageFactory.java b/server/src/main/java/org/spine3/server/storage/StorageFactory.java index 21297682052..52d2520c5b4 100644 --- a/server/src/main/java/org/spine3/server/storage/StorageFactory.java +++ b/server/src/main/java/org/spine3/server/storage/StorageFactory.java @@ -48,7 +48,6 @@ public interface StorageFactory extends AutoCloseable { /** Creates a new {@link StandStorage} instance. */ StandStorage createStandStorage(); - /** * Creates a new {@link AggregateStorage} instance. * @@ -73,5 +72,4 @@ public interface StorageFactory extends AutoCloseable { */ ProjectionStorage createProjectionStorage(Class> projectionClass); - } From 08fbc4cdaa6728cccd84c165f0376f5c986f0c4e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:31:57 +0300 Subject: [PATCH 307/361] Remove an empty line. --- .../spine3/server/storage/memory/InMemoryProjectionStorage.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java index 99b292f6c08..0199d6c8969 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java @@ -85,7 +85,6 @@ protected Iterable readBulkInternal(Iterable ids, FieldM return recordStorage.readBulk(ids, fieldMask); } - @Override protected Map readAllInternal() { final Map result = recordStorage.readAll(); From f5d4de063f2db4bb9e1dbec2bdc3d338a5c7823d Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 10 Oct 2016 19:37:34 +0300 Subject: [PATCH 308/361] Add tests for SubscriptionRecord. --- .../stand/SubscriptionRecordShould.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java diff --git a/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java b/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java new file mode 100644 index 00000000000..a57d7ca3489 --- /dev/null +++ b/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.stand; + +import com.google.protobuf.Any; +import org.junit.Test; +import org.spine3.base.Queries; +import org.spine3.client.Subscription; +import org.spine3.client.Target; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; +import org.spine3.test.aggregate.Project; +import org.spine3.test.aggregate.ProjectId; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Dmytro Dashenkov + */ +public class SubscriptionRecordShould { + + @Test + public void match_record_to_given_parameters() { + final SubscriptionRecord matchingRecord = new SubscriptionRecord(Given.subscription(), + Given.target(), + Given.TYPE); + + final Project entityState = Project.getDefaultInstance(); + final Any wrappedState = AnyPacker.pack(entityState); + final ProjectId redundantId = ProjectId.getDefaultInstance(); + + final boolean matchResult = matchingRecord.matches(Given.TYPE, redundantId, wrappedState); + + assertTrue(matchResult); + } + + @Test + public void be_equal_if_has_same_subscription() { + final Subscription oneSubscription = Given.subscription(); + final Subscription otherSubscription = Subscription.newBuilder() + .setId("breaking-id") + .build(); + + @SuppressWarnings("QuestionableName") + final SubscriptionRecord one = new SubscriptionRecord(oneSubscription, + Given.target(), + Given.TYPE); + + final SubscriptionRecord similar = new SubscriptionRecord(otherSubscription, + Given.target(), + Given.TYPE); + + final SubscriptionRecord same = new SubscriptionRecord(oneSubscription, + Given.target(), + Given.TYPE); + + assertFalse(one.equals(similar)); + assertTrue(one.equals(same)); + } + + @SuppressWarnings("UtilityClass") + private static class Given { + + private static final TypeUrl TYPE = TypeUrl.of(Project.class); + + private static Target target() { + final Target target = Queries.Targets.allOf(Project.class); + + return target; + } + + private static Subscription subscription() { + final Subscription subscription = Subscription.getDefaultInstance(); + + return subscription; + } + } +} From b098d69cee32a2d3460fa49d21b75c06629d4391 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:44:15 +0300 Subject: [PATCH 309/361] Simplify `findBulkInternal` implementation and remove warning suppression. --- .../storage/memory/InMemoryRecordStorage.java | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index c25f381851e..c212329438c 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -61,8 +61,6 @@ protected InMemoryRecordStorage(boolean multitenant) { super(multitenant); } - @SuppressWarnings("MethodWithMultipleLoops") /* It's OK for in-memory implementation - * as it is used primarily in tests. */ @Override protected Iterable readBulkInternal(final Iterable givenIds, FieldMask fieldMask) { final Map storage = getStorage(); @@ -70,39 +68,30 @@ protected Iterable readBulkInternal(final Iterable given // It is not possible to return an immutable collection, since {@code null} may be present in it. final Collection result = new LinkedList<>(); - TypeUrl typeUrl = null; - - int resultExpectedSize = 0; - for (I givenId : givenIds) { - resultExpectedSize++; - - for (I recordId : storage.keySet()) { - if (recordId.equals(givenId)) { - final EntityStorageRecord.Builder matchingRecord = storage.get(recordId).toBuilder(); - final Any state = matchingRecord.getState(); - - if (typeUrl == null) { - typeUrl = TypeUrl.of(state.getTypeUrl()); - } - - final Message wholeState = AnyPacker.unpack(state); - final Message maskedState = FieldMasks.applyIfValid(fieldMask, wholeState, typeUrl); - final Any processed = AnyPacker.pack(maskedState); - - matchingRecord.setState(processed); - - result.add(matchingRecord.build()); - } - } + final EntityStorageRecord matchingResult = findAndApplyFieldMask(storage, givenId, fieldMask); + result.add(matchingResult); + } + return result; + } - // If no record was found - if (result.size() < resultExpectedSize) { - result.add(null); - resultExpectedSize++; + private EntityStorageRecord findAndApplyFieldMask(Map storage, I givenId, FieldMask fieldMask) { + EntityStorageRecord matchingResult = null; + for (I recordId : storage.keySet()) { + if (recordId.equals(givenId)) { + EntityStorageRecord.Builder matchingRecord = storage.get(recordId) + .toBuilder(); + final Any state = matchingRecord.getState(); + final TypeUrl typeUrl = TypeUrl.of(state.getTypeUrl()); + final Message wholeState = AnyPacker.unpack(state); + final Message maskedState = FieldMasks.applyIfValid(fieldMask, wholeState, typeUrl); + final Any processed = AnyPacker.pack(maskedState); + + matchingRecord.setState(processed); + matchingResult = matchingRecord.build(); } } - return result; + return matchingResult; } @Override @@ -120,7 +109,8 @@ protected Map readAllInternal() { @Override protected Map readAllInternal(FieldMask fieldMask) { - if (fieldMask.getPathsList().isEmpty()) { + if (fieldMask.getPathsList() + .isEmpty()) { return readAllInternal(); } @@ -138,11 +128,19 @@ protected Map readAllInternal(FieldMask fieldMask) { public Message apply(@Nullable EntityStorageRecord input) { return input == null ? null : AnyPacker.unpack(input.getState()); } - }), TypeUrl.of(storage.entrySet().iterator().next().getValue().getState().getTypeUrl())); + }), TypeUrl.of(storage.entrySet() + .iterator() + .next() + .getValue() + .getState() + .getTypeUrl())); final Iterator messageIterator = records.iterator(); for (I key : storage.keySet()) { - result.put(key, storage.get(key).toBuilder().setState(AnyPacker.pack(messageIterator.next())).build()); + result.put(key, storage.get(key) + .toBuilder() + .setState(AnyPacker.pack(messageIterator.next())) + .build()); } return result.build(); From 63e7de56f53e5f6b92e9291a92eb1c7fc509bd2a Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 10 Oct 2016 19:46:22 +0300 Subject: [PATCH 310/361] Add dismatch tests for subscription record. --- .../stand/SubscriptionRecordShould.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java b/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java index a57d7ca3489..6cbf0c4abf9 100644 --- a/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java +++ b/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java @@ -21,6 +21,7 @@ package org.spine3.server.stand; import com.google.protobuf.Any; +import com.google.protobuf.Message; import org.junit.Test; import org.spine3.base.Queries; import org.spine3.client.Subscription; @@ -29,6 +30,9 @@ import org.spine3.protobuf.TypeUrl; import org.spine3.test.aggregate.Project; import org.spine3.test.aggregate.ProjectId; +import org.spine3.test.commandservice.customer.Customer; + +import java.util.Collections; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -53,6 +57,40 @@ public void match_record_to_given_parameters() { assertTrue(matchResult); } + @Test + public void fail_to_match_improper_type() { + final SubscriptionRecord notMatchingRecord = new SubscriptionRecord(Given.subscription(), + Given.target(), + Given.TYPE); + + final Project entityState = Project.getDefaultInstance(); + final Any wrappedState = AnyPacker.pack(entityState); + final ProjectId redundantId = ProjectId.getDefaultInstance(); + + final boolean matchResult = notMatchingRecord.matches(Given.OTHER_TYPE, redundantId, wrappedState); + + assertFalse(matchResult); + } + + @Test + public void fail_to_match_improper_target() { + final ProjectId nonExisitngId = ProjectId.newBuilder() + .setId("never-existed") + .build(); + + final SubscriptionRecord notMatchingRecord = new SubscriptionRecord(Given.subscription(), + Given.target(nonExisitngId), + Given.TYPE); + + final Project entityState = Project.getDefaultInstance(); + final Any wrappedState = AnyPacker.pack(entityState); + final ProjectId redundantId = ProjectId.getDefaultInstance(); + + final boolean matchResult = notMatchingRecord.matches(Given.TYPE, redundantId, wrappedState); + + assertFalse(matchResult); + } + @Test public void be_equal_if_has_same_subscription() { final Subscription oneSubscription = Given.subscription(); @@ -81,6 +119,7 @@ public void be_equal_if_has_same_subscription() { private static class Given { private static final TypeUrl TYPE = TypeUrl.of(Project.class); + private static final TypeUrl OTHER_TYPE = TypeUrl.of(Customer.class); private static Target target() { final Target target = Queries.Targets.allOf(Project.class); @@ -88,6 +127,12 @@ private static Target target() { return target; } + private static Target target(Message targetId) { + final Target target = Queries.Targets.someOf(Project.class, Collections.singleton(targetId)); + + return target; + } + private static Subscription subscription() { final Subscription subscription = Subscription.getDefaultInstance(); From f516e068e09c4cb104458fe2a8132fdb527a1a8c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Mon, 10 Oct 2016 19:54:09 +0300 Subject: [PATCH 311/361] Simplify the implementation of `readAllInternal`. --- .../storage/memory/InMemoryRecordStorage.java | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index c212329438c..5e5060377f4 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -20,8 +20,6 @@ package org.spine3.server.storage.memory; -import com.google.common.base.Function; -import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableMap; import com.google.protobuf.Any; import com.google.protobuf.FieldMask; @@ -34,9 +32,7 @@ import org.spine3.server.users.CurrentTenant; import org.spine3.users.TenantId; -import javax.annotation.Nullable; import java.util.Collection; -import java.util.Iterator; import java.util.LinkedList; import java.util.Map; @@ -117,30 +113,24 @@ protected Map readAllInternal(FieldMask fieldMask) { final Map storage = getStorage(); if (storage.isEmpty()) { - return newHashMap(); + return ImmutableMap.of(); } final ImmutableMap.Builder result = ImmutableMap.builder(); - final Collection records = FieldMasks.applyMask(fieldMask, Collections2.transform(storage.values(), new Function() { - @Nullable - @Override - public Message apply(@Nullable EntityStorageRecord input) { - return input == null ? null : AnyPacker.unpack(input.getState()); - } - }), TypeUrl.of(storage.entrySet() - .iterator() - .next() - .getValue() - .getState() - .getTypeUrl())); - - final Iterator messageIterator = records.iterator(); - for (I key : storage.keySet()) { - result.put(key, storage.get(key) - .toBuilder() - .setState(AnyPacker.pack(messageIterator.next())) - .build()); + for (Map.Entry storageEntry : storage.entrySet()) { + final I id = storageEntry.getKey(); + final EntityStorageRecord rawRecord = storageEntry.getValue(); + final TypeUrl type = TypeUrl.of(rawRecord.getState() + .getTypeUrl()); + final Any recordState = rawRecord.getState(); + final Message stateAsMessage = AnyPacker.unpack(recordState); + final Message processedState = FieldMasks.applyMask(fieldMask, stateAsMessage, type); + final Any packedState = AnyPacker.pack(processedState); + final EntityStorageRecord resultingRecord = EntityStorageRecord.newBuilder() + .setState(packedState) + .build(); + result.put(id, resultingRecord); } return result.build(); From bebaa50d74efbe4df969367d12650a1f67a77701 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 10 Oct 2016 20:02:57 +0300 Subject: [PATCH 312/361] Remove empty lines and fix typo. --- .../server/stand/SubscriptionRecordShould.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java b/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java index 6cbf0c4abf9..2cd60321ede 100644 --- a/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java +++ b/server/src/test/java/org/spine3/server/stand/SubscriptionRecordShould.java @@ -47,13 +47,11 @@ public void match_record_to_given_parameters() { final SubscriptionRecord matchingRecord = new SubscriptionRecord(Given.subscription(), Given.target(), Given.TYPE); - final Project entityState = Project.getDefaultInstance(); final Any wrappedState = AnyPacker.pack(entityState); final ProjectId redundantId = ProjectId.getDefaultInstance(); final boolean matchResult = matchingRecord.matches(Given.TYPE, redundantId, wrappedState); - assertTrue(matchResult); } @@ -62,32 +60,27 @@ public void fail_to_match_improper_type() { final SubscriptionRecord notMatchingRecord = new SubscriptionRecord(Given.subscription(), Given.target(), Given.TYPE); - final Project entityState = Project.getDefaultInstance(); final Any wrappedState = AnyPacker.pack(entityState); final ProjectId redundantId = ProjectId.getDefaultInstance(); final boolean matchResult = notMatchingRecord.matches(Given.OTHER_TYPE, redundantId, wrappedState); - assertFalse(matchResult); } @Test public void fail_to_match_improper_target() { - final ProjectId nonExisitngId = ProjectId.newBuilder() + final ProjectId nonExistingId = ProjectId.newBuilder() .setId("never-existed") .build(); - final SubscriptionRecord notMatchingRecord = new SubscriptionRecord(Given.subscription(), - Given.target(nonExisitngId), + Given.target(nonExistingId), Given.TYPE); - final Project entityState = Project.getDefaultInstance(); final Any wrappedState = AnyPacker.pack(entityState); final ProjectId redundantId = ProjectId.getDefaultInstance(); final boolean matchResult = notMatchingRecord.matches(Given.TYPE, redundantId, wrappedState); - assertFalse(matchResult); } @@ -97,20 +90,16 @@ public void be_equal_if_has_same_subscription() { final Subscription otherSubscription = Subscription.newBuilder() .setId("breaking-id") .build(); - @SuppressWarnings("QuestionableName") final SubscriptionRecord one = new SubscriptionRecord(oneSubscription, Given.target(), Given.TYPE); - final SubscriptionRecord similar = new SubscriptionRecord(otherSubscription, Given.target(), Given.TYPE); - final SubscriptionRecord same = new SubscriptionRecord(oneSubscription, Given.target(), Given.TYPE); - assertFalse(one.equals(similar)); assertTrue(one.equals(same)); } @@ -123,19 +112,16 @@ private static class Given { private static Target target() { final Target target = Queries.Targets.allOf(Project.class); - return target; } private static Target target(Message targetId) { final Target target = Queries.Targets.someOf(Project.class, Collections.singleton(targetId)); - return target; } private static Subscription subscription() { final Subscription subscription = Subscription.getDefaultInstance(); - return subscription; } } From 69ea7726cef8d0b8378b2ea57243aad5df29a447 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 10 Oct 2016 20:22:59 +0300 Subject: [PATCH 313/361] Cover InMemoryRecordStorage#readAll(FieldMask) empty storage case with a test. --- .../server/storage/RecordStorageShould.java | 46 +++++++++++++++++++ .../memory/InMemoryRecordStorageShould.java | 35 ++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 server/src/test/java/org/spine3/server/storage/RecordStorageShould.java create mode 100644 server/src/test/java/org/spine3/server/storage/memory/InMemoryRecordStorageShould.java diff --git a/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java new file mode 100644 index 00000000000..1a2436dd129 --- /dev/null +++ b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.storage; + +import com.google.protobuf.FieldMask; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertNotNull; +import static org.spine3.test.Verify.assertEmpty; + +/** + * @author Dmytro Dashenkov + */ +public abstract class RecordStorageShould { + + @Test + public void retrieve_empty_map_if_storage_is_empty() { + final RecordStorage storage = createStorage(); + + final Map empty = storage.readAll(FieldMask.getDefaultInstance()); + assertNotNull(empty); + assertEmpty(empty); + } + + protected abstract RecordStorage createStorage(); +} diff --git a/server/src/test/java/org/spine3/server/storage/memory/InMemoryRecordStorageShould.java b/server/src/test/java/org/spine3/server/storage/memory/InMemoryRecordStorageShould.java new file mode 100644 index 00000000000..ad4d421681d --- /dev/null +++ b/server/src/test/java/org/spine3/server/storage/memory/InMemoryRecordStorageShould.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.storage.memory; + +import org.spine3.server.storage.RecordStorage; +import org.spine3.server.storage.RecordStorageShould; + +/** + * @author Dmytro Dashenkov + */ +public class InMemoryRecordStorageShould extends RecordStorageShould { + + @Override + protected RecordStorage createStorage() { + return InMemoryRecordStorage.newInstance(false); + } +} From 3e70827645c5cffb46fa297b77c1643e657fbe50 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Mon, 10 Oct 2016 21:04:22 +0300 Subject: [PATCH 314/361] Add read all tests for projection storage. --- .../storage/ProjectionStorageShould.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index b02a910271e..46b3c83d542 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -20,17 +20,30 @@ package org.spine3.server.storage; +import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; import com.google.protobuf.Timestamp; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.Durations; +import org.spine3.protobuf.Timestamps; import org.spine3.test.Tests; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import static com.google.protobuf.util.Timestamps.add; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.spine3.protobuf.Timestamps.getCurrentTime; +import static org.spine3.test.Tests.assertMatchesMask; +import static org.spine3.test.Verify.assertContains; +import static org.spine3.test.Verify.assertSize; import static org.spine3.testdata.TestEntityStorageRecordFactory.newEntityStorageRecord; /** @@ -69,6 +82,77 @@ public void return_null_if_no_event_time_in_storage() { assertNull(time); } + @SuppressWarnings("MethodWithMultipleLoops") + @Test + public void read_all_messages() { + final int count = 5; + final List ids = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final I id = newId(); + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(Any.getDefaultInstance()) + .setWhenModified(Timestamps.getCurrentTime()) + .setVersion(1) + .build(); + storage.write(id, record); + ids.add(id); + } + + final Map read = storage.readAll(); + assertSize(ids.size(), read); + for (Map.Entry record : read.entrySet()) { + assertContains(record.getKey(), ids); + } + } + + @SuppressWarnings("MethodWithMultipleLoops") + @Test + public void read_all_messages_with_field_mask() { + final int count = 5; + final List ids = new LinkedList<>(); + + final String projectDescriptor = Project.getDescriptor() + .getFullName(); + @SuppressWarnings("DuplicateStringLiteralInspection") + final FieldMask fieldMask = FieldMask.newBuilder() + .addPaths(projectDescriptor + ".id") + .addPaths(projectDescriptor + ".name") + .build(); + + for (int i = 0; i < count; i++) { + final I id = newId(); + final ProjectId stateId = ProjectId.newBuilder() + .setId(id.toString()) + .build(); + final Project state = Project.newBuilder() + .setId(stateId) + .setName(String.format("project_%s", i)) + .setStatus(Project.Status.CREATED) + .build(); + final Any packedState = AnyPacker.pack(state); + + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(packedState) + .setWhenModified(Timestamps.getCurrentTime()) + .setVersion(1) + .build(); + storage.write(id, record); + ids.add(id); + } + + final Map read = storage.readAll(fieldMask); + assertSize(ids.size(), read); + for (Map.Entry record : read.entrySet()) { + assertContains(record.getKey(), ids); + + final Any packedState = record.getValue() + .getState(); + final Project state = AnyPacker.unpack(packedState); + assertMatchesMask(state, fieldMask); + } + } + @Test(expected = NullPointerException.class) public void throw_exception_if_write_null_event_time() { storage.writeLastHandledEventTime(Tests.nullRef()); From 1d8dd94af6b0769cdcec5bfdd3afacf7f1fc6c66 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 11 Oct 2016 11:54:30 +0300 Subject: [PATCH 315/361] Fix RecordStorage#readAll(FieldMask) on empty storage test. --- .../java/org/spine3/server/storage/RecordStorageShould.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java index 1a2436dd129..39272553426 100644 --- a/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java @@ -37,7 +37,11 @@ public abstract class RecordStorageShould { public void retrieve_empty_map_if_storage_is_empty() { final RecordStorage storage = createStorage(); - final Map empty = storage.readAll(FieldMask.getDefaultInstance()); + final FieldMask nonEmptyFieldMask = FieldMask.newBuilder() + .addPaths("invalid-path") + .build(); + + final Map empty = storage.readAll(nonEmptyFieldMask); assertNotNull(empty); assertEmpty(empty); } From 8a9b2b51dfa3b3216514441fca94182b80fa9abb Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 11 Oct 2016 12:03:57 +0300 Subject: [PATCH 316/361] Add empty read-all operations test for projection storage. --- .../server/storage/ProjectionStorageShould.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index 46b3c83d542..e25e44d1f00 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -43,6 +43,7 @@ import static org.spine3.protobuf.Timestamps.getCurrentTime; import static org.spine3.test.Tests.assertMatchesMask; import static org.spine3.test.Verify.assertContains; +import static org.spine3.test.Verify.assertEmpty; import static org.spine3.test.Verify.assertSize; import static org.spine3.testdata.TestEntityStorageRecordFactory.newEntityStorageRecord; @@ -153,6 +154,22 @@ public void read_all_messages_with_field_mask() { } } + @Test + public void retrieve_empty_map_if_store_is_empty() { + final Map noMask = storage.readAll(); + + final FieldMask nonEmptyMask = FieldMask.newBuilder() + .addPaths("invalid_path") + .build(); + final Map masked = storage.readAll(nonEmptyMask); + + assertEmpty(noMask); + assertEmpty(masked); + + // Same type + assertEquals(noMask, masked); + } + @Test(expected = NullPointerException.class) public void throw_exception_if_write_null_event_time() { storage.writeLastHandledEventTime(Tests.nullRef()); From fc84cd14a47edfe1b66b0caeb2f45626a9b3951e Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 11 Oct 2016 12:29:50 +0300 Subject: [PATCH 317/361] Add read-bulk tests for projection storage. --- .../storage/ProjectionStorageShould.java | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index e25e44d1f00..bcc75306103 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -32,6 +32,7 @@ import org.spine3.test.Tests; import org.spine3.test.projection.Project; import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.Task; import java.util.LinkedList; import java.util.List; @@ -40,6 +41,7 @@ import static com.google.protobuf.util.Timestamps.add; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.spine3.protobuf.Timestamps.getCurrentTime; import static org.spine3.test.Tests.assertMatchesMask; import static org.spine3.test.Verify.assertContains; @@ -170,6 +172,120 @@ public void retrieve_empty_map_if_store_is_empty() { assertEquals(noMask, masked); } + @SuppressWarnings("MethodWithMultipleLoops") + @Test + public void perform_read_bulk_operations() { + final int count = 10; + final List ids = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final I id = newId(); + final ProjectId projectId = ProjectId.newBuilder() + .setId(id.toString()) + .build(); + final Project state = Project.newBuilder() + .setId(projectId) + .build(); + + final Any packedState = AnyPacker.pack(state); + + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(packedState) + .setWhenModified(Timestamps.getCurrentTime()) + .setVersion(1) + .build(); + storage.write(id, record); + + // Request a subset of records + if (i % 2 == 0) { + ids.add(id); + } + } + + final Iterable read = storage.readBulk(ids); + assertSize(ids.size(), read); + + // Check data consistency + for (EntityStorageRecord record : read) { + final Any packedState = record.getState(); + final Project state = AnyPacker.unpack(packedState); + final ProjectId id = state.getId(); + final String stringIdRepr = id.getId(); + + boolean isIdPresent = false; + for (I genericId : ids) { + isIdPresent = genericId.toString() + .equals(stringIdRepr); + if (isIdPresent) { + break; + } + } + assertTrue(isIdPresent); + } + } + + @Test + public void perform_bulk_read_with_field_mask_operation() { + final int count = 10; + final List ids = new LinkedList<>(); + + final String projectDescriptor = Project.getDescriptor() + .getFullName(); + final FieldMask fieldMask = FieldMask.newBuilder() + .addPaths(projectDescriptor + ".id") + .addPaths(projectDescriptor + ".status") + .build(); + for (int i = 0; i < count; i++) { + final I id = newId(); + final ProjectId projectId = ProjectId.newBuilder() + .setId(id.toString()) + .build(); + final Project state = Project.newBuilder() + .setId(projectId) + .setName(String.format("project-number-%s", i)) + .setStatus(Project.Status.CREATED) + .addTask(Task.getDefaultInstance()) + .build(); + + final Any packedState = AnyPacker.pack(state); + + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(packedState) + .setWhenModified(Timestamps.getCurrentTime()) + .setVersion(1) + .build(); + storage.write(id, record); + + // Request a subset of records + if (i % 2 == 0) { + ids.add(id); + } + } + + final Iterable read = storage.readBulk(ids, fieldMask); + assertSize(ids.size(), read); + + // Check data consistency + for (EntityStorageRecord record : read) { + final Any packedState = record.getState(); + final Project state = AnyPacker.unpack(packedState); + final ProjectId id = state.getId(); + final String stringIdRepr = id.getId(); + + boolean isIdPresent = false; + for (I genericId : ids) { + isIdPresent = genericId.toString() + .equals(stringIdRepr); + if (isIdPresent) { + break; + } + } + assertTrue(isIdPresent); + + assertMatchesMask(state, fieldMask); + } + } + @Test(expected = NullPointerException.class) public void throw_exception_if_write_null_event_time() { storage.writeLastHandledEventTime(Tests.nullRef()); From ff19cc9f0ef1f3511bf3821b599177fd5215b902 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 11 Oct 2016 12:30:25 +0300 Subject: [PATCH 318/361] Add warnings supression. --- .../org/spine3/server/storage/ProjectionStorageShould.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index bcc75306103..839c75a660f 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -172,7 +172,7 @@ public void retrieve_empty_map_if_store_is_empty() { assertEquals(noMask, masked); } - @SuppressWarnings("MethodWithMultipleLoops") + @SuppressWarnings({"MethodWithMultipleLoops", "BreakStatement"}) @Test public void perform_read_bulk_operations() { final int count = 10; @@ -224,6 +224,7 @@ public void perform_read_bulk_operations() { } } + @SuppressWarnings({"MethodWithMultipleLoops", "BreakStatement"}) @Test public void perform_bulk_read_with_field_mask_operation() { final int count = 10; From 8c2917666cea703829527d440d2f8ce17804d39e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 16:24:21 +0300 Subject: [PATCH 319/361] Rework `QueryProcessor` from a utility class into the interface with several implementations to make the flow clearer. --- .../server/stand/AggregateQueryProcessor.java | 150 ++++++++++++++ .../server/stand/EntityQueryProcessor.java | 74 +++++++ .../server/stand/NoopQueryProcessor.java} | 22 +- .../spine3/server/stand/QueryProcessor.java | 189 +----------------- .../java/org/spine3/server/stand/Stand.java | 49 ++++- 5 files changed, 285 insertions(+), 199 deletions(-) create mode 100644 server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java create mode 100644 server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java rename server/src/{test/java/org/spine3/server/stand/QueryProcessorShould.java => main/java/org/spine3/server/stand/NoopQueryProcessor.java} (67%) diff --git a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java new file mode 100644 index 00000000000..9c59ce26dbe --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java @@ -0,0 +1,150 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import org.spine3.client.EntityFilters; +import org.spine3.client.EntityId; +import org.spine3.client.EntityIdFilter; +import org.spine3.client.Query; +import org.spine3.client.Target; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.storage.EntityStorageRecord; +import org.spine3.server.storage.StandStorage; + +import javax.annotation.Nullable; +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Processes the queries targeting {@link org.spine3.server.aggregate.Aggregate} state. + * + * @author Alex Tymchenko + */ +/* package */ class AggregateQueryProcessor implements QueryProcessor { + + private final StandStorage standStorage; + private final TypeUrl type; + + /* package */ AggregateQueryProcessor(StandStorage standStorage, TypeUrl type) { + this.standStorage = standStorage; + this.type = type; + } + + private final Function stateIdTransformer = new Function() { + @Nullable + @Override + public AggregateStateId apply(@Nullable EntityId input) { + checkNotNull(input); + + final Any rawId = input.getId(); + final Message unpackedId = AnyPacker.unpack(rawId); + final AggregateStateId stateId = AggregateStateId.of(unpackedId, type); + return stateId; + } + }; + + @Override + public ImmutableCollection process(Query query) { + + final ImmutableList.Builder resultBuilder = ImmutableList.builder(); + + ImmutableCollection stateRecords; + final Target target = query.getTarget(); + final FieldMask fieldMask = query.getFieldMask(); + final boolean shouldApplyFieldMask = !fieldMask.getPathsList() + .isEmpty(); + if (target.getIncludeAll()) { + stateRecords = shouldApplyFieldMask ? + standStorage.readAllByType(type, fieldMask) : + standStorage.readAllByType(type); + } else { + stateRecords = doFetchWithFilters(target, fieldMask); + } + + for (EntityStorageRecord record : stateRecords) { + final Any state = record.getState(); + resultBuilder.add(state); + } + + final ImmutableList result = resultBuilder.build(); + return result; + } + + private ImmutableCollection doFetchWithFilters(Target target, FieldMask fieldMask) { + ImmutableCollection result; + final EntityFilters filters = target.getFilters(); + final boolean shouldApplyFieldMask = !fieldMask.getPathsList() + .isEmpty(); + final boolean idsAreDefined = !filters.getIdFilter() + .getIdsList() + .isEmpty(); + if (idsAreDefined) { + final EntityIdFilter idFilter = filters.getIdFilter(); + final Collection stateIds = Collections2.transform(idFilter.getIdsList(), + stateIdTransformer); + if (stateIds.size() == 1) { + // no need to trigger bulk reading. + // may be more effective, as bulk reading implies additional time and performance expenses. + final AggregateStateId singleId = stateIds.iterator() + .next(); + final EntityStorageRecord singleResult = shouldApplyFieldMask ? + standStorage.read(singleId, fieldMask) : + standStorage.read(singleId); + result = ImmutableList.of(singleResult); + } else { + result = handleBulkRead(stateIds, fieldMask, shouldApplyFieldMask); + } + } else { + result = ImmutableList.of(); + } + return result; + } + + private ImmutableCollection handleBulkRead(Collection stateIds, + FieldMask fieldMask, + boolean applyFieldMask) { + ImmutableCollection result; + final Iterable bulkReadResults = applyFieldMask ? + standStorage.readBulk(stateIds, fieldMask) : + standStorage.readBulk(stateIds); + result = FluentIterable.from(bulkReadResults) + .filter(new Predicate() { + @Override + public boolean apply(@Nullable EntityStorageRecord input) { + return input != null; + } + }) + .toList(); + return result; + } +} diff --git a/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java new file mode 100644 index 00000000000..2ca55feef0b --- /dev/null +++ b/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java @@ -0,0 +1,74 @@ +/* + * + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +package org.spine3.server.stand; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Message; +import org.spine3.client.EntityFilters; +import org.spine3.client.Query; +import org.spine3.client.Target; +import org.spine3.protobuf.AnyPacker; +import org.spine3.server.entity.Entity; +import org.spine3.server.entity.EntityRepository; + +/** + * Processes the queries targeting {@link org.spine3.server.entity.Entity} objects. + * + * @author Alex Tymchenko + */ +/* package */ class EntityQueryProcessor implements QueryProcessor { + + private final EntityRepository repository; + + /* package */ EntityQueryProcessor(EntityRepository repository) { + this.repository = repository; + } + + @Override + public ImmutableCollection process(Query query) { + final ImmutableList.Builder resultBuilder = ImmutableList.builder(); + + final Target target = query.getTarget(); + final FieldMask fieldMask = query.getFieldMask(); + + final ImmutableCollection entities; + if (target.getIncludeAll() && fieldMask.getPathsList() + .isEmpty()) { + entities = repository.loadAll(); + } else { + final EntityFilters filters = target.getFilters(); + entities = repository.find(filters, fieldMask); + } + + for (Entity entity : entities) { + final Message state = entity.getState(); + final Any packedState = AnyPacker.pack(state); + resultBuilder.add(packedState); + } + + final ImmutableList result = resultBuilder.build(); + return result; + } +} diff --git a/server/src/test/java/org/spine3/server/stand/QueryProcessorShould.java b/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java similarity index 67% rename from server/src/test/java/org/spine3/server/stand/QueryProcessorShould.java rename to server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java index 08810d8d4d7..1c7eeef0fe3 100644 --- a/server/src/test/java/org/spine3/server/stand/QueryProcessorShould.java +++ b/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java @@ -21,19 +21,21 @@ */ package org.spine3.server.stand; -import org.junit.Test; - -import static org.junit.Assert.assertTrue; -import static org.spine3.test.Tests.hasPrivateUtilityConstructor; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import org.spine3.client.Query; /** + * An {@link QueryProcessor} implementation that always returns empty result. + * + *

        Used to define a processing result for {@link Query} which does not hit any of exposed state objects. + * * @author Alex Tymchenko */ -public class QueryProcessorShould { - - @Test - public void have_private_constructor() { - assertTrue(hasPrivateUtilityConstructor(QueryProcessor.class)); +/* package */ class NoopQueryProcessor implements QueryProcessor { + @Override + public ImmutableCollection process(Query query) { + return ImmutableList.of(); } - } diff --git a/server/src/main/java/org/spine3/server/stand/QueryProcessor.java b/server/src/main/java/org/spine3/server/stand/QueryProcessor.java index 705ce05dd68..55a006c9cdc 100644 --- a/server/src/main/java/org/spine3/server/stand/QueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/QueryProcessor.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without @@ -21,203 +20,23 @@ */ package org.spine3.server.stand; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; import com.google.protobuf.Any; -import com.google.protobuf.FieldMask; -import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; -import org.spine3.base.Queries; -import org.spine3.client.EntityFilters; -import org.spine3.client.EntityId; -import org.spine3.client.EntityIdFilter; import org.spine3.client.Query; -import org.spine3.client.Target; -import org.spine3.protobuf.AnyPacker; -import org.spine3.protobuf.TypeUrl; -import org.spine3.server.entity.Entity; -import org.spine3.server.entity.EntityRepository; -import org.spine3.server.storage.EntityStorageRecord; -import org.spine3.server.storage.StandStorage; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -import static com.google.common.base.Preconditions.checkNotNull; /** - * Query processing helper utility. + * Processes a query and returns the result. * * @author Alex Tymchenko */ -/* package */ -@SuppressWarnings("UtilityClass") -class QueryProcessor { - - private QueryProcessor() { - } +/* package */ interface QueryProcessor { /** * Performs query processing as a part of {@link Stand#execute(Query, StreamObserver)}. * - * @param query an instance of {@code Query} to process - * @param standStorage internal storage of {@link Stand} - * @param aggregateTypes {@link org.spine3.server.aggregate.Aggregate} types exposed by the {@code Stand} - * @param typeToRepositoryMap map of exposed types and related repositories registered in the {@code Stand} + * @param query an instance of {@code Query} to process * @return the query result */ - /* package */ - static ImmutableCollection processQuery(Query query, - StandStorage standStorage, - Set aggregateTypes, - Map> typeToRepositoryMap) { - final ImmutableList.Builder resultBuilder = ImmutableList.builder(); - - final TypeUrl typeUrl = Queries.typeOf(query); - checkNotNull(typeUrl, "Target type unknown"); - - final EntityRepository repository = typeToRepositoryMap.get(typeUrl); - if (repository != null) { - - // the target references an entity state - final ImmutableCollection entities = fetchFromEntityRepository(query, repository); - feedEntitiesToBuilder(resultBuilder, entities); - - } else if (aggregateTypes.contains(typeUrl)) { - - // the target relates to an {@code Aggregate} state - final ImmutableCollection stateRecords = fetchFromStandStorage(query, standStorage, typeUrl); - feedStateRecordsToBuilder(resultBuilder, stateRecords); - } - - final ImmutableList result = resultBuilder.build(); - return result; - } - - private static ImmutableCollection fetchFromEntityRepository( - Query query, - EntityRepository repository) { - - final ImmutableCollection result; - final Target target = query.getTarget(); - final FieldMask fieldMask = query.getFieldMask(); - - if (target.getIncludeAll() && fieldMask.getPathsList() - .isEmpty()) { - result = repository.loadAll(); - } else { - final EntityFilters filters = target.getFilters(); - result = repository.find(filters, fieldMask); - } - return result; - } - - private static void feedEntitiesToBuilder(ImmutableList.Builder resultBuilder, - ImmutableCollection all) { - for (Entity record : all) { - final Message state = record.getState(); - final Any packedState = AnyPacker.pack(state); - resultBuilder.add(packedState); - } - } - - private static void feedStateRecordsToBuilder(ImmutableList.Builder resultBuilder, - ImmutableCollection all) { - for (EntityStorageRecord record : all) { - final Any state = record.getState(); - resultBuilder.add(state); - } - } - - private static ImmutableCollection fetchFromStandStorage(Query query, - StandStorage standStorage, - final TypeUrl typeUrl) { - ImmutableCollection result; - final Target target = query.getTarget(); - final FieldMask fieldMask = query.getFieldMask(); - final boolean shouldApplyFieldMask = !fieldMask.getPathsList() - .isEmpty(); - if (target.getIncludeAll()) { - result = shouldApplyFieldMask ? - standStorage.readAllByType(typeUrl, fieldMask) : - standStorage.readAllByType(typeUrl); - } else { - result = doFetchWithFilters(standStorage, typeUrl, target, fieldMask); - } - - return result; - } - - private static ImmutableCollection doFetchWithFilters(StandStorage standStorage, - TypeUrl typeUrl, - Target target, - FieldMask fieldMask) { - ImmutableCollection result; - final EntityFilters filters = target.getFilters(); - final boolean shouldApplyFieldMask = !fieldMask.getPathsList() - .isEmpty(); - final boolean idsAreDefined = !filters.getIdFilter() - .getIdsList() - .isEmpty(); - if (idsAreDefined) { - final EntityIdFilter idFilter = filters.getIdFilter(); - final Collection stateIds = Collections2.transform(idFilter.getIdsList(), - aggregateStateIdTransformer(typeUrl)); - if (stateIds.size() == 1) { - // no need to trigger bulk reading. - // may be more effective, as bulk reading implies additional time and performance expenses. - final AggregateStateId singleId = stateIds.iterator() - .next(); - final EntityStorageRecord singleResult = shouldApplyFieldMask ? - standStorage.read(singleId, fieldMask) : - standStorage.read(singleId); - result = ImmutableList.of(singleResult); - } else { - result = handleBulkRead(standStorage, stateIds, fieldMask, shouldApplyFieldMask); - } - } else { - result = ImmutableList.of(); - } - return result; - } - - private static ImmutableCollection handleBulkRead(StandStorage standStorage, - Collection stateIds, - FieldMask fieldMask, - boolean applyFieldMask) { - ImmutableCollection result; - final Iterable bulkReadResults = applyFieldMask ? - standStorage.readBulk(stateIds, fieldMask) : - standStorage.readBulk(stateIds); - result = FluentIterable.from(bulkReadResults) - .filter(new Predicate() { - @Override - public boolean apply(@Nullable EntityStorageRecord input) { - return input != null; - } - }) - .toList(); - return result; - } - - private static Function aggregateStateIdTransformer(final TypeUrl typeUrl) { - return new Function() { - @Nullable - @Override - public AggregateStateId apply(@Nullable EntityId input) { - checkNotNull(input); - - final Any rawId = input.getId(); - final Message unpackedId = AnyPacker.unpack(rawId); - final AggregateStateId stateId = AggregateStateId.of(unpackedId, typeUrl); - return stateId; - } - }; - } + ImmutableCollection process(Query query); } diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index ced9d842d57..c9f3aab1606 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -28,6 +28,7 @@ import com.google.protobuf.Any; import com.google.protobuf.Message; import io.grpc.stub.StreamObserver; +import org.spine3.base.Queries; import org.spine3.base.Responses; import org.spine3.client.Query; import org.spine3.client.QueryResponse; @@ -49,6 +50,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; +import static com.google.common.base.Preconditions.checkNotNull; + /** * A container for storing the lastest {@link org.spine3.server.aggregate.Aggregate} states. * @@ -98,6 +101,11 @@ public class Stand implements AutoCloseable { */ private final Set knownAggregateTypes = Sets.newConcurrentHashSet(); + /** + * Used to return an empty result collection for {@link Query}. + */ + private static final QueryProcessor NOOP_PROCESSOR = new NoopQueryProcessor(); + private Stand(Builder builder) { storage = builder.getStorage(); callbackExecutor = builder.getCallbackExecutor(); @@ -233,10 +241,12 @@ public ImmutableSet getExposedAggregateTypes() { * @param responseObserver an observer to feed the query results to. */ public void execute(Query query, StreamObserver responseObserver) { - final ImmutableCollection readResult = QueryProcessor.processQuery(query, - storage, - knownAggregateTypes, - typeToRepositoryMap); + + final TypeUrl type = Queries.typeOf(query); + checkNotNull(type, "Query target type unknown"); + final QueryProcessor queryProcessor = processorFor(type); + + final ImmutableCollection readResult = queryProcessor.process(query); final QueryResponse response = QueryResponse.newBuilder() .addAllMessages(readResult) .setResponse(Responses.ok()) @@ -316,6 +326,37 @@ public interface EntityUpdateCallback { void onStateChanged(Any newEntityState); } + /** + * Factory method which determines a proper {@link QueryProcessor} implementation depending on {@link TypeUrl} + * of the incoming {@link Query#getTarget()}. + * + *

        As {@code Stand} accumulates the read-side updates from various repositories, the {@code Query} processing + * varies a lot. The target type of the incoming {@code Query} tells the {@code Stand} about the essence of the + * object queried. Thus making it possible to pick a proper strategy for data fetch. + * + * @param type the target type of the {@code Query} + * @return suitable implementation of {@code QueryProcessor} + */ + private QueryProcessor processorFor(TypeUrl type) { + final QueryProcessor result; + + final EntityRepository repository = typeToRepositoryMap.get(type); + if (repository != null) { + + // The query target is an {@code Entity}. + result = new EntityQueryProcessor(repository); + } else if (getExposedAggregateTypes().contains(type)) { + + // The query target is an {@code Aggregate} state. + result = new AggregateQueryProcessor(storage, type); + } else { + + // This type points to an objects, that are not exposed via the current instance of {@code Stand}. + result = NOOP_PROCESSOR; + } + return result; + } + public static class Builder { private StandStorage storage; private Executor callbackExecutor; From 2b03281a21e11b08e36eaf775afc094b0ea12759 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 16:49:47 +0300 Subject: [PATCH 320/361] Fix copyright header through the codebase: leave the only empty line at the beginning. --- client/src/main/java/org/spine3/base/Queries.java | 1 - client/src/test/java/org/spine3/base/QueriesShould.java | 1 - server/src/main/java/org/spine3/server/QueryService.java | 1 - .../src/main/java/org/spine3/server/SubscriptionService.java | 1 - .../java/org/spine3/server/stand/AggregateQueryProcessor.java | 1 - .../main/java/org/spine3/server/stand/AggregateStateId.java | 1 - .../java/org/spine3/server/stand/EntityQueryProcessor.java | 1 - .../main/java/org/spine3/server/stand/NoopQueryProcessor.java | 1 - server/src/main/java/org/spine3/server/stand/Stand.java | 1 - server/src/main/java/org/spine3/server/stand/StandFunnel.java | 1 - .../main/java/org/spine3/server/stand/SubscriptionRecord.java | 1 - .../java/org/spine3/server/stand/SubscriptionRegistry.java | 1 - server/src/main/java/org/spine3/server/stand/package-info.java | 1 - .../org/spine3/server/storage/BulkStorageOperationsMixin.java | 1 - .../src/main/java/org/spine3/server/storage/StandStorage.java | 1 - .../org/spine3/server/storage/memory/InMemoryStandStorage.java | 1 - .../main/java/org/spine3/server/transport/GrpcContainer.java | 1 - .../main/java/org/spine3/server/transport/package-info.java | 3 +-- server/src/test/java/org/spine3/server/QueryServiceShould.java | 1 - .../test/java/org/spine3/server/stand/StandFunnelShould.java | 1 - server/src/test/java/org/spine3/server/stand/StandShould.java | 1 - .../java/org/spine3/server/transport/GrpcContainerShould.java | 1 - server/src/test/java/org/spine3/testdata/TestStandFactory.java | 1 - 23 files changed, 1 insertion(+), 24 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index c282872865d..d1ecfaa0699 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java index ae80b6f846c..c78156690c7 100644 --- a/client/src/test/java/org/spine3/base/QueriesShould.java +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index 78a59aa4c4a..813d4d356dd 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index 79a7b94e3ef..d8bd58ce67a 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java index 9c59ce26dbe..43e8c921ccb 100644 --- a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/AggregateStateId.java b/server/src/main/java/org/spine3/server/stand/AggregateStateId.java index e3671c74ad0..725d4f09e71 100644 --- a/server/src/main/java/org/spine3/server/stand/AggregateStateId.java +++ b/server/src/main/java/org/spine3/server/stand/AggregateStateId.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java index 2ca55feef0b..4136e52ec3b 100644 --- a/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java index 1c7eeef0fe3..74cd765218a 100644 --- a/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index c9f3aab1606..221ff09ed1b 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 015b293f682..98d14795cce 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java index 2ddc32e5e71..aed2ec31774 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java index 3f04e7c404b..a5ce8d6e6f2 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/stand/package-info.java b/server/src/main/java/org/spine3/server/stand/package-info.java index f2ccb2aee2e..8e2f5fcf4cd 100644 --- a/server/src/main/java/org/spine3/server/stand/package-info.java +++ b/server/src/main/java/org/spine3/server/stand/package-info.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java index 510fa76743b..40300fc7eeb 100644 --- a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java +++ b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index 99814220ef9..b64e7fbd7a7 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 3a9dc795480..77e80b2b991 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java index 777e879c9ef..2d0273b82a6 100644 --- a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/main/java/org/spine3/server/transport/package-info.java b/server/src/main/java/org/spine3/server/transport/package-info.java index 7da22a828fb..f10c3f33e89 100644 --- a/server/src/main/java/org/spine3/server/transport/package-info.java +++ b/server/src/main/java/org/spine3/server/transport/package-info.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without @@ -28,4 +27,4 @@ @ParametersAreNonnullByDefault package org.spine3.server.transport; -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index 8caba5e909b..f50537d55af 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 183b5a47c73..2b8d13dc73c 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 6ff69545c08..cc13ded6c5d 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java index 7b811dbc2dd..ce762913243 100644 --- a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index 63477a00a7c..df119baf462 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -1,5 +1,4 @@ /* - * * Copyright 2016, TeamDev Ltd. All rights reserved. * * Redistribution and use in source and/or binary forms, with or without From 379ae6057dd2a85fb1de7d49ab10bfe1fc94957d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 16:56:09 +0300 Subject: [PATCH 321/361] Add `@Nullable` annotation to the `callback` and explain the data flow. --- .../server/stand/SubscriptionRecord.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java index aed2ec31774..bea2d2c5377 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java @@ -31,6 +31,7 @@ import org.spine3.client.Target; import org.spine3.protobuf.TypeUrl; +import javax.annotation.Nullable; import java.util.List; /** @@ -43,6 +44,14 @@ private final Subscription subscription; private final Target target; private final TypeUrl type; + + /** + * The {@code callback} is null after the creation and until the subscription is activated. + * + * @see SubscriptionRegistry#addSubscription(Target) + * @see SubscriptionRegistry#activate(Subscription, Stand.EntityUpdateCallback) + */ + @Nullable private Stand.EntityUpdateCallback callback = null; /* package */ SubscriptionRecord(Subscription subscription, Target target, TypeUrl type) { @@ -76,12 +85,10 @@ * @param entityState the entity state to match * @return {@code true} if this record matches all the given parameters, {@code false} otherwise. */ - /* package */ boolean matches( - TypeUrl type, - Object id, - // entityState will be later used for more advanced filtering - @SuppressWarnings("UnusedParameters") Any entityState - ) { + /* package */ boolean matches(TypeUrl type, + Object id, + // entityState will be later used for more advanced filtering + @SuppressWarnings("UnusedParameters") Any entityState) { final boolean result; final boolean typeMatches = this.type.equals(type); From 2250cf708828f2f4f6c21b0b65ccecc9c3785132 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 16:59:35 +0300 Subject: [PATCH 322/361] Align `/* package */` declaration properly in relation to its target. --- .../server/event/enrich/EventEnricher.java | 2 +- .../spine3/server/stand/SubscriptionRecord.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/spine3/server/event/enrich/EventEnricher.java b/server/src/main/java/org/spine3/server/event/enrich/EventEnricher.java index 1cb7908eee4..2eae622e809 100644 --- a/server/src/main/java/org/spine3/server/event/enrich/EventEnricher.java +++ b/server/src/main/java/org/spine3/server/event/enrich/EventEnricher.java @@ -268,7 +268,7 @@ private void checkDuplicate(EnrichmentFunction function) { * @see EnrichmentFunction */ @VisibleForTesting - /* package */ static class SameTransition implements Predicate { + /* package */ static class SameTransition implements Predicate { private final EnrichmentFunction function; diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java index bea2d2c5377..55ea410fb04 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java @@ -65,14 +65,14 @@ * * @param callback the callback to attach */ - /* package */ void activate(Stand.EntityUpdateCallback callback) { + /* package */ void activate(Stand.EntityUpdateCallback callback) { this.callback = callback; } /** * Checks whether this record has a callback attached. */ - /* package */ boolean isActive() { + /* package */ boolean isActive() { final boolean result = this.callback != null; return result; } @@ -85,10 +85,10 @@ * @param entityState the entity state to match * @return {@code true} if this record matches all the given parameters, {@code false} otherwise. */ - /* package */ boolean matches(TypeUrl type, - Object id, - // entityState will be later used for more advanced filtering - @SuppressWarnings("UnusedParameters") Any entityState) { + /* package */ boolean matches(TypeUrl type, + Object id, + // entityState will be later used for more advanced filtering + @SuppressWarnings("UnusedParameters") Any entityState) { final boolean result; final boolean typeMatches = this.type.equals(type); @@ -120,11 +120,11 @@ private static boolean matchByFilters(Object id, EntityFilters filters) { return result; } - /* package */ TypeUrl getType() { + /* package */ TypeUrl getType() { return type; } - /* package */ Stand.EntityUpdateCallback getCallback() { + /* package */ Stand.EntityUpdateCallback getCallback() { return callback; } From 035b1180b4da994dadbcedd38d110dc2403efc38 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 17:02:07 +0300 Subject: [PATCH 323/361] Aling the class-level documentation properly and supply it with the `@author` tag. --- .../java/org/spine3/server/stand/SubscriptionRegistry.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java index a5ce8d6e6f2..68a73ba1119 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java @@ -37,7 +37,10 @@ * Registry for subscription management. * *

        Provides a quick access to the subscription records by {@link TypeUrl}. + * *

        Responsible for {@link Subscription} object instantiation. + * + * @author Alex Tymchenko */ @Internal /* package */ final class SubscriptionRegistry { From ea22f01bd72f69bb5c3737174e1c80b10d2f2d81 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 11 Oct 2016 17:37:23 +0300 Subject: [PATCH 324/361] Extract some operations into separate methods and fix code style issues. --- .../storage/ProjectionStorageShould.java | 213 +++++++----------- 1 file changed, 80 insertions(+), 133 deletions(-) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index 839c75a660f..5bca2cec7ae 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -34,6 +34,7 @@ import org.spine3.test.projection.ProjectId; import org.spine3.test.projection.Task; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -88,19 +89,7 @@ public void return_null_if_no_event_time_in_storage() { @SuppressWarnings("MethodWithMultipleLoops") @Test public void read_all_messages() { - final int count = 5; - final List ids = new LinkedList<>(); - - for (int i = 0; i < count; i++) { - final I id = newId(); - final EntityStorageRecord record = EntityStorageRecord.newBuilder() - .setState(Any.getDefaultInstance()) - .setWhenModified(Timestamps.getCurrentTime()) - .setVersion(1) - .build(); - storage.write(id, record); - ids.add(id); - } + final List ids = fillStorage(5); final Map read = storage.readAll(); assertSize(ids.size(), read); @@ -112,37 +101,12 @@ public void read_all_messages() { @SuppressWarnings("MethodWithMultipleLoops") @Test public void read_all_messages_with_field_mask() { - final int count = 5; - final List ids = new LinkedList<>(); + final List ids = fillStorage(5); final String projectDescriptor = Project.getDescriptor() .getFullName(); @SuppressWarnings("DuplicateStringLiteralInspection") - final FieldMask fieldMask = FieldMask.newBuilder() - .addPaths(projectDescriptor + ".id") - .addPaths(projectDescriptor + ".name") - .build(); - - for (int i = 0; i < count; i++) { - final I id = newId(); - final ProjectId stateId = ProjectId.newBuilder() - .setId(id.toString()) - .build(); - final Project state = Project.newBuilder() - .setId(stateId) - .setName(String.format("project_%s", i)) - .setStatus(Project.Status.CREATED) - .build(); - final Any packedState = AnyPacker.pack(state); - - final EntityStorageRecord record = EntityStorageRecord.newBuilder() - .setState(packedState) - .setWhenModified(Timestamps.getCurrentTime()) - .setVersion(1) - .build(); - storage.write(id, record); - ids.add(id); - } + final FieldMask fieldMask = fields(projectDescriptor + ".id", projectDescriptor + ".name"); final Map read = storage.readAll(fieldMask); assertSize(ids.size(), read); @@ -157,132 +121,52 @@ public void read_all_messages_with_field_mask() { } @Test - public void retrieve_empty_map_if_store_is_empty() { - final Map noMask = storage.readAll(); + public void retrieve_empty_map_if_storage_is_empty() { + final Map noMaskEntiries = storage.readAll(); final FieldMask nonEmptyMask = FieldMask.newBuilder() .addPaths("invalid_path") .build(); - final Map masked = storage.readAll(nonEmptyMask); + final Map maskedEntries = storage.readAll(nonEmptyMask); - assertEmpty(noMask); - assertEmpty(masked); + assertEmpty(noMaskEntiries); + assertEmpty(maskedEntries); // Same type - assertEquals(noMask, masked); + assertEquals(noMaskEntiries, maskedEntries); } @SuppressWarnings({"MethodWithMultipleLoops", "BreakStatement"}) @Test public void perform_read_bulk_operations() { - final int count = 10; - final List ids = new LinkedList<>(); - - for (int i = 0; i < count; i++) { - final I id = newId(); - final ProjectId projectId = ProjectId.newBuilder() - .setId(id.toString()) - .build(); - final Project state = Project.newBuilder() - .setId(projectId) - .build(); - - final Any packedState = AnyPacker.pack(state); - - final EntityStorageRecord record = EntityStorageRecord.newBuilder() - .setState(packedState) - .setWhenModified(Timestamps.getCurrentTime()) - .setVersion(1) - .build(); - storage.write(id, record); - - // Request a subset of records - if (i % 2 == 0) { - ids.add(id); - } - } + // Get a subset of IDs + final List ids = fillStorage(10).subList(0, 5); final Iterable read = storage.readBulk(ids); assertSize(ids.size(), read); // Check data consistency for (EntityStorageRecord record : read) { - final Any packedState = record.getState(); - final Project state = AnyPacker.unpack(packedState); - final ProjectId id = state.getId(); - final String stringIdRepr = id.getId(); - - boolean isIdPresent = false; - for (I genericId : ids) { - isIdPresent = genericId.toString() - .equals(stringIdRepr); - if (isIdPresent) { - break; - } - } - assertTrue(isIdPresent); + checkProjectIdIsInList(record, ids); } } @SuppressWarnings({"MethodWithMultipleLoops", "BreakStatement"}) @Test public void perform_bulk_read_with_field_mask_operation() { - final int count = 10; - final List ids = new LinkedList<>(); + // Get a subset of IDs + final List ids = fillStorage(10).subList(0, 5); final String projectDescriptor = Project.getDescriptor() .getFullName(); - final FieldMask fieldMask = FieldMask.newBuilder() - .addPaths(projectDescriptor + ".id") - .addPaths(projectDescriptor + ".status") - .build(); - for (int i = 0; i < count; i++) { - final I id = newId(); - final ProjectId projectId = ProjectId.newBuilder() - .setId(id.toString()) - .build(); - final Project state = Project.newBuilder() - .setId(projectId) - .setName(String.format("project-number-%s", i)) - .setStatus(Project.Status.CREATED) - .addTask(Task.getDefaultInstance()) - .build(); - - final Any packedState = AnyPacker.pack(state); - - final EntityStorageRecord record = EntityStorageRecord.newBuilder() - .setState(packedState) - .setWhenModified(Timestamps.getCurrentTime()) - .setVersion(1) - .build(); - storage.write(id, record); - - // Request a subset of records - if (i % 2 == 0) { - ids.add(id); - } - } + final FieldMask fieldMask = fields(projectDescriptor + ".id", projectDescriptor + ".status"); final Iterable read = storage.readBulk(ids, fieldMask); assertSize(ids.size(), read); // Check data consistency for (EntityStorageRecord record : read) { - final Any packedState = record.getState(); - final Project state = AnyPacker.unpack(packedState); - final ProjectId id = state.getId(); - final String stringIdRepr = id.getId(); - - boolean isIdPresent = false; - for (I genericId : ids) { - isIdPresent = genericId.toString() - .equals(stringIdRepr); - if (isIdPresent) { - break; - } - } - assertTrue(isIdPresent); - + final Project state = checkProjectIdIsInList(record, ids); assertMatchesMask(state, fieldMask); } } @@ -305,6 +189,27 @@ public void write_and_read_last_event_time_several_times() { writeAndReadLastEventTimeTest(time2); } + private List fillStorage(int count) { + final List ids = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final I id = newId(); + final Project state = Given.project(id.toString(), String.format("project-%d", i)); + final Any packedState = AnyPacker.pack(state); + + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(packedState) + .setWhenModified(Timestamps.getCurrentTime()) + .setVersion(1) + .build(); + storage.write(id, record); + ids.add(id); + + } + + return ids; + } + private void writeAndReadLastEventTimeTest(Timestamp expected) { storage.writeLastHandledEventTime(expected); @@ -312,4 +217,46 @@ private void writeAndReadLastEventTimeTest(Timestamp expected) { assertEquals(expected, actual); } + + private static Project checkProjectIdIsInList(EntityStorageRecord project, List ids) { + final Any packedState = project.getState(); + final Project state = AnyPacker.unpack(packedState); + final ProjectId id = state.getId(); + final String stringIdRepr = id.getId(); + + boolean isIdPresent = false; + for (I genericId : ids) { + isIdPresent = genericId.toString() + .equals(stringIdRepr); + if (isIdPresent) { + break; + } + } + assertTrue(isIdPresent); + + return state; + } + + private static FieldMask fields(String... paths) { + final FieldMask mask = FieldMask.newBuilder() + .addAllPaths(Arrays.asList(paths)) + .build(); + return mask; + } + + private static class Given { + + private static Project project(String id, String name) { + final ProjectId projectId = ProjectId.newBuilder() + .setId(id) + .build(); + final Project project = Project.newBuilder() + .setId(projectId) + .setName(name) + .setStatus(Project.Status.CREATED) + .addTask(Task.getDefaultInstance()) + .build(); + return project; + } + } } From 550a3038d0c8a479bcd0a0c403100f1853f107ad Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Tue, 11 Oct 2016 17:44:36 +0300 Subject: [PATCH 325/361] Rename helper method. --- .../org/spine3/server/storage/ProjectionStorageShould.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index 5bca2cec7ae..da9b536459f 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -106,7 +106,7 @@ public void read_all_messages_with_field_mask() { final String projectDescriptor = Project.getDescriptor() .getFullName(); @SuppressWarnings("DuplicateStringLiteralInspection") - final FieldMask fieldMask = fields(projectDescriptor + ".id", projectDescriptor + ".name"); + final FieldMask fieldMask = maskForPaths(projectDescriptor + ".id", projectDescriptor + ".name"); final Map read = storage.readAll(fieldMask); assertSize(ids.size(), read); @@ -159,7 +159,7 @@ public void perform_bulk_read_with_field_mask_operation() { final String projectDescriptor = Project.getDescriptor() .getFullName(); - final FieldMask fieldMask = fields(projectDescriptor + ".id", projectDescriptor + ".status"); + final FieldMask fieldMask = maskForPaths(projectDescriptor + ".id", projectDescriptor + ".status"); final Iterable read = storage.readBulk(ids, fieldMask); assertSize(ids.size(), read); @@ -204,7 +204,6 @@ private List fillStorage(int count) { .build(); storage.write(id, record); ids.add(id); - } return ids; @@ -237,7 +236,7 @@ private static Project checkProjectIdIsInList(EntityStorageRecord project, L return state; } - private static FieldMask fields(String... paths) { + private static FieldMask maskForPaths(String... paths) { final FieldMask mask = FieldMask.newBuilder() .addAllPaths(Arrays.asList(paths)) .build(); From 03ab9c1e745e82fad8c534db636a3b00e2ed2b73 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 18:40:35 +0300 Subject: [PATCH 326/361] Review the `@Internal` and `@SPI` annotation usages. --- .../java/org/spine3/server/projection/ProjectionRepository.java | 2 ++ server/src/main/java/org/spine3/server/stand/StandFunnel.java | 2 ++ .../main/java/org/spine3/server/stand/SubscriptionRecord.java | 2 -- .../main/java/org/spine3/server/stand/SubscriptionRegistry.java | 2 -- .../src/main/java/org/spine3/server/storage/StandStorage.java | 2 ++ 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java index cba77e49928..08bfb88faa3 100644 --- a/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java +++ b/server/src/main/java/org/spine3/server/projection/ProjectionRepository.java @@ -27,6 +27,7 @@ import io.grpc.stub.StreamObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spine3.Internal; import org.spine3.base.Event; import org.spine3.base.EventContext; import org.spine3.base.Events; @@ -234,6 +235,7 @@ public void dispatch(Event event) { * * @param event the event to dispatch */ + @Internal /* package */ void internalDispatch(Event event) { final Message eventMessage = Events.getMessage(event); final EventContext context = event.getContext(); diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index 98d14795cce..abd23cdabf1 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.Any; +import org.spine3.Internal; import org.spine3.base.Command; import org.spine3.base.Event; @@ -45,6 +46,7 @@ * @see org.spine3.server.aggregate.AggregateRepository#dispatch(Command) * @see org.spine3.server.projection.ProjectionRepository#dispatch(Event) */ +@Internal public class StandFunnel { /** diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java index 55ea410fb04..f2d99852917 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java @@ -22,7 +22,6 @@ import com.google.common.base.Objects; import com.google.protobuf.Any; -import org.spine3.Internal; import org.spine3.base.Identifiers; import org.spine3.client.EntityFilters; import org.spine3.client.EntityId; @@ -39,7 +38,6 @@ * * @see SubscriptionRegistry */ -@Internal /* package */ final class SubscriptionRecord { private final Subscription subscription; private final Target target; diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java index 68a73ba1119..f84ce76f2a7 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java @@ -20,7 +20,6 @@ */ package org.spine3.server.stand; -import org.spine3.Internal; import org.spine3.base.Identifiers; import org.spine3.client.Subscription; import org.spine3.client.Target; @@ -42,7 +41,6 @@ * * @author Alex Tymchenko */ -@Internal /* package */ final class SubscriptionRegistry { private final Map> typeToAttrs = newHashMap(); private final Map subscriptionToAttrs = newHashMap(); diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index b64e7fbd7a7..9c3996ba0e5 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableCollection; import com.google.protobuf.Any; import com.google.protobuf.FieldMask; +import org.spine3.SPI; import org.spine3.protobuf.TypeUrl; import org.spine3.server.stand.AggregateStateId; import org.spine3.server.stand.Stand; @@ -36,6 +37,7 @@ * @see Any#getTypeUrl() * @see Stand */ +@SPI public abstract class StandStorage extends RecordStorage { protected StandStorage(boolean multitenant) { From 895b54085058e0cbfc7b1c052977f90fcf57e925 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 18:51:07 +0300 Subject: [PATCH 327/361] Rename the `...Storage` protected methods consistently (avoid `internal` in their names) and treat them as a part of SPI. --- .../server/entity/EntityRepository.java | 2 +- .../server/stand/AggregateQueryProcessor.java | 4 +- .../server/storage/AggregateStorage.java | 50 ++++++++++--------- .../storage/BulkStorageOperationsMixin.java | 6 +-- .../spine3/server/storage/EventStorage.java | 9 ++-- .../server/storage/ProjectionStorage.java | 4 +- .../spine3/server/storage/RecordStorage.java | 33 ++++++------ .../memory/InMemoryAggregateStorage.java | 2 +- .../storage/memory/InMemoryEventStorage.java | 4 +- .../memory/InMemoryProjectionStorage.java | 12 ++--- .../storage/memory/InMemoryRecordStorage.java | 16 +++--- .../storage/memory/InMemoryStandStorage.java | 16 +++--- .../org/spine3/server/stand/StandShould.java | 4 +- .../storage/AggregateStorageShould.java | 6 +-- .../server/storage/EventStorageShould.java | 4 +- 15 files changed, 87 insertions(+), 85 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index 4022a10eae3..b10766a350d 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -151,7 +151,7 @@ public ImmutableCollection loadAll(Iterable ids) { @CheckReturnValue public ImmutableCollection loadAll(Iterable ids, FieldMask fieldMask) { final RecordStorage storage = recordStorage(); - final Iterable entityStorageRecords = storage.readBulk(ids); + final Iterable entityStorageRecords = storage.readMultiple(ids); final Iterator idIterator = ids.iterator(); final Iterator recordIterator = entityStorageRecords.iterator(); diff --git a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java index 43e8c921ccb..455bafab197 100644 --- a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java @@ -134,8 +134,8 @@ private ImmutableCollection handleBulkRead(Collection result; final Iterable bulkReadResults = applyFieldMask ? - standStorage.readBulk(stateIds, fieldMask) : - standStorage.readBulk(stateIds); + standStorage.readMultiple(stateIds, fieldMask) : + standStorage.readMultiple(stateIds); result = FluentIterable.from(bulkReadResults) .filter(new Predicate() { @Override diff --git a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java index 6023c2dc52b..4d007c46434 100644 --- a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java +++ b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java @@ -49,7 +49,8 @@ @SPI public abstract class AggregateStorage extends AbstractStorage { - private static final String SNAPSHOT_TYPE_NAME = Snapshot.getDescriptor().getName(); + private static final String SNAPSHOT_TYPE_NAME = Snapshot.getDescriptor() + .getName(); protected AggregateStorage(boolean multitenant) { super(multitenant); @@ -79,7 +80,7 @@ public AggregateEvents read(I aggregateId) { break; case KIND_NOT_SET: throw new IllegalStateException("Event or snapshot missing in record: \"" + - shortDebugString(record) + '\"'); + shortDebugString(record) + '\"'); } } @@ -97,9 +98,9 @@ public AggregateEvents read(I aggregateId) { * *

        NOTE: does not rewrite any events. Several events can be associated with one aggregate ID. * - * @param id the ID for the record + * @param id the ID for the record * @param events events to store - * @throws IllegalStateException if the storage is closed + * @throws IllegalStateException if the storage is closed * @throws IllegalArgumentException if event list is empty */ @Override @@ -112,14 +113,14 @@ public void write(I id, AggregateEvents events) throws IllegalStateException, Il for (final Event event : eventList) { final AggregateStorageRecord record = toStorageRecord(event); - writeInternal(id, record); + writeRecord(id, record); } } /** * Writes an event to the storage by an aggregate ID. * - * @param id the aggregate ID + * @param id the aggregate ID * @param event the event to write * @throws IllegalStateException if the storage is closed */ @@ -129,14 +130,14 @@ public void writeEvent(I id, Event event) { checkNotNull(event); final AggregateStorageRecord record = toStorageRecord(event); - writeInternal(id, record); + writeRecord(id, record); } /** * Writes a {@code snapshot} by an {@code aggregateId} to the storage. * * @param aggregateId an ID of an aggregate of which the snapshot is made - * @param snapshot the snapshot of the aggregate + * @param snapshot the snapshot of the aggregate * @throws IllegalStateException if the storage is closed */ public void write(I aggregateId, Snapshot snapshot) { @@ -144,14 +145,15 @@ public void write(I aggregateId, Snapshot snapshot) { checkNotNull(aggregateId); checkNotNull(snapshot); - final AggregateStorageRecord record = AggregateStorageRecord.newBuilder() - .setTimestamp(checkIsPositive(snapshot.getTimestamp(), "Snapshot timestamp")) - .setEventType(SNAPSHOT_TYPE_NAME) - .setEventId("") // No event ID for snapshots because it's not a domain event. - .setVersion(snapshot.getVersion()) - .setSnapshot(snapshot) - .build(); - writeInternal(aggregateId, record); + final AggregateStorageRecord record = + AggregateStorageRecord.newBuilder() + .setTimestamp(checkIsPositive(snapshot.getTimestamp(), "Snapshot timestamp")) + .setEventType(SNAPSHOT_TYPE_NAME) + .setEventId("") // No event ID for snapshots because it's not a domain event. + .setVersion(snapshot.getVersion()) + .setSnapshot(snapshot) + .build(); + writeRecord(aggregateId, record); } /** @@ -168,7 +170,7 @@ public void write(I aggregateId, Snapshot snapshot) { * Reads a count of events which were saved to the storage after the last snapshot was created, * or a count of all events if there were no snapshots yet. * - * @param id an ID of an aggregate + * @param id an ID of an aggregate * @param eventCount an even count after the last snapshot * @throws IllegalStateException if the storage is closed */ @@ -190,11 +192,11 @@ private static AggregateStorageRecord toStorageRecord(Event event) { final Timestamp timestamp = checkIsPositive(context.getTimestamp(), "Event time"); final AggregateStorageRecord.Builder builder = AggregateStorageRecord.newBuilder() - .setEvent(event) - .setTimestamp(timestamp) - .setEventId(eventIdStr) - .setEventType(eventType) - .setVersion(context.getVersion()); + .setEvent(event) + .setTimestamp(timestamp) + .setEventId(eventIdStr) + .setEventType(eventType) + .setVersion(context.getVersion()); return builder.build(); } @@ -203,10 +205,10 @@ private static AggregateStorageRecord toStorageRecord(Event event) { /** * Writes the passed record into the storage. * - * @param id the aggregate ID + * @param id the aggregate ID * @param record the record to write */ - protected abstract void writeInternal(I id, AggregateStorageRecord record); + protected abstract void writeRecord(I id, AggregateStorageRecord record); /** * Creates iterator of aggregate event history with the reverse traversal. diff --git a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java index 40300fc7eeb..7806bf587ad 100644 --- a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java +++ b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java @@ -42,10 +42,10 @@ *

        The size of {@link Iterable} returned is always the same as the size of given IDs. * *

        In case there is no record for a particular ID, {@code null} will be present in the result instead. - * In this way {@code readBulk()} callees are able to track the absenсe of a certain element by comparing + * In this way {@code readMultiple()} callees are able to track the absenсe of a certain element by comparing * the input IDs and resulting {@code Iterable}. * - *

        E.g. {@code readBulk( Lists.newArrayList(idPresentInStorage, idNonPresentInStorage) )} will return + *

        E.g. {@code readMultiple( Lists.newArrayList(idPresentInStorage, idNonPresentInStorage) )} will return * an {@code Iterable} with two elements, first of which is non-null and the second is null. * * @param ids record IDs of interest @@ -53,7 +53,7 @@ * @throws IllegalStateException if the storage was closed before */ @CheckReturnValue - Iterable readBulk(Iterable ids); + Iterable readMultiple(Iterable ids); /** diff --git a/server/src/main/java/org/spine3/server/storage/EventStorage.java b/server/src/main/java/org/spine3/server/storage/EventStorage.java index 61ce76ff531..8af26613f19 100644 --- a/server/src/main/java/org/spine3/server/storage/EventStorage.java +++ b/server/src/main/java/org/spine3/server/storage/EventStorage.java @@ -30,6 +30,7 @@ import com.google.protobuf.Any; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; +import org.spine3.Internal; import org.spine3.SPI; import org.spine3.base.Event; import org.spine3.base.EventContext; @@ -80,7 +81,7 @@ public void write(EventId id, Event event) { checkNotNull(event); final EventStorageRecord record = toEventStorageRecord(id, event); - writeInternal(record); + writeRecord(record); } @Override @@ -88,7 +89,7 @@ public Event read(EventId id) { checkNotClosed(); checkNotNull(id); - final EventStorageRecord record = readInternal(id); + final EventStorageRecord record = readRecord(id); if (record == null) { return Event.getDefaultInstance(); } @@ -109,7 +110,7 @@ public Event read(EventId id) { * * @param record the record to write */ - protected abstract void writeInternal(EventStorageRecord record); + protected abstract void writeRecord(EventStorageRecord record); /** * Reads storage format record. @@ -118,7 +119,7 @@ public Event read(EventId id) { * @return the record instance of null if there's not record with such ID */ @Nullable - protected abstract EventStorageRecord readInternal(EventId eventId); + protected abstract EventStorageRecord readRecord(EventId eventId); /** Converts EventStorageRecord to Event. */ protected static Event toEvent(EventStorageRecord record) { diff --git a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java index 5eb928e80b8..952dd38c236 100644 --- a/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/ProjectionStorage.java @@ -44,7 +44,7 @@ public ProjectionStorage(boolean multitenant) { @Nullable @Override - protected EntityStorageRecord readInternal(I id) { + protected EntityStorageRecord readRecord(I id) { final RecordStorage storage = getRecordStorage(); final EntityStorageRecord record = storage.read(id); return record; @@ -52,7 +52,7 @@ protected EntityStorageRecord readInternal(I id) { @Override - protected void writeInternal(I id, EntityStorageRecord record) { + protected void writeRecord(I id, EntityStorageRecord record) { final RecordStorage storage = getRecordStorage(); storage.write(id, record); } diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index 240793f6bcd..7a19721d307 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -58,7 +58,7 @@ public EntityStorageRecord read(I id) { checkNotClosed(); checkNotNull(id); - final EntityStorageRecord record = readInternal(checkNotNull(id)); + final EntityStorageRecord record = readRecord(checkNotNull(id)); if (record == null) { return EntityStorageRecord.getDefaultInstance(); } @@ -97,18 +97,18 @@ public void write(I id, EntityStorageRecord record) { checkArgument(record.hasState(), "Record does not have state field."); checkNotClosed(); - writeInternal(id, record); + writeRecord(id, record); } /** * {@inheritDoc} */ @Override - public Iterable readBulk(Iterable ids) { + public Iterable readMultiple(Iterable ids) { checkNotClosed(); checkNotNull(ids); - return readBulkInternal(ids); + return readMultipleRecords(ids); } /** @@ -118,11 +118,11 @@ public Iterable readBulk(Iterable ids) { * @param fieldMask the mask to apply * @return the items with the given IDs and with the given {@code FieldMask} applied */ - public Iterable readBulk(Iterable ids, FieldMask fieldMask) { + public Iterable readMultiple(Iterable ids, FieldMask fieldMask) { checkNotClosed(); checkNotNull(ids); - return readBulkInternal(ids, fieldMask); + return readMultipleRecords(ids, fieldMask); } /** @@ -132,7 +132,7 @@ public Iterable readBulk(Iterable ids, FieldMask fieldMa public Map readAll() { checkNotClosed(); - return readAllInternal(); + return readAllRecords(); } /** @@ -144,7 +144,7 @@ public Map readAll() { public Map readAll(FieldMask fieldMask) { checkNotClosed(); - return readAllInternal(fieldMask); + return readAllRecords(fieldMask); } // @@ -158,19 +158,19 @@ public Map readAll(FieldMask fieldMask) { * @return a record instance or {@code null} if there is no record with this ID */ @Nullable - protected abstract EntityStorageRecord readInternal(I id); + protected abstract EntityStorageRecord readRecord(I id); - /** @see BulkStorageOperationsMixin#readBulk(java.lang.Iterable) */ - protected abstract Iterable readBulkInternal(Iterable ids); + /** @see BulkStorageOperationsMixin#readMultiple(java.lang.Iterable) */ + protected abstract Iterable readMultipleRecords(Iterable ids); - /** @see BulkStorageOperationsMixin#readBulk(java.lang.Iterable) */ - protected abstract Iterable readBulkInternal(Iterable ids, FieldMask fieldMask); + /** @see BulkStorageOperationsMixin#readMultiple(java.lang.Iterable) */ + protected abstract Iterable readMultipleRecords(Iterable ids, FieldMask fieldMask); /** @see BulkStorageOperationsMixin#readAll() */ - protected abstract Map readAllInternal(); + protected abstract Map readAllRecords(); /** @see BulkStorageOperationsMixin#readAll() */ - protected abstract Map readAllInternal(FieldMask fieldMask); + protected abstract Map readAllRecords(FieldMask fieldMask); /** * Writes a record into the storage. @@ -180,6 +180,5 @@ public Map readAll(FieldMask fieldMask) { * @param id an ID of the record * @param record a record to store */ - protected abstract void writeInternal(I id, EntityStorageRecord record); - + protected abstract void writeRecord(I id, EntityStorageRecord record); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryAggregateStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryAggregateStorage.java index 8a8a139af26..72236b8b8dd 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryAggregateStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryAggregateStorage.java @@ -60,7 +60,7 @@ protected static InMemoryAggregateStorage newInstance() { } @Override - protected void writeInternal(I id, AggregateStorageRecord record) { + protected void writeRecord(I id, AggregateStorageRecord record) { recordMap.put(id, record); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryEventStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryEventStorage.java index c60d4a6e905..461679f66cc 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryEventStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryEventStorage.java @@ -82,7 +82,7 @@ public Iterator iterator(EventStreamQuery query) { } @Override - protected void writeInternal(EventStorageRecord record) { + protected void writeRecord(EventStorageRecord record) { checkNotNull(record); final String eventId = record.getEventId(); checkState(!eventId.isEmpty(), "eventId cannot be empty"); @@ -92,7 +92,7 @@ protected void writeInternal(EventStorageRecord record) { @Nullable @Override - protected EventStorageRecord readInternal(EventId eventId) { + protected EventStorageRecord readRecord(EventId eventId) { final EventStorageRecord result = index.get(eventId.getUuid()); return result; } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java index 0199d6c8969..b83b09e9355 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryProjectionStorage.java @@ -75,24 +75,24 @@ public void close() throws Exception { } @Override - protected Iterable readBulkInternal(Iterable ids) { - final Iterable result = recordStorage.readBulk(ids); + protected Iterable readMultipleRecords(Iterable ids) { + final Iterable result = recordStorage.readMultiple(ids); return result; } @Override - protected Iterable readBulkInternal(Iterable ids, FieldMask fieldMask) { - return recordStorage.readBulk(ids, fieldMask); + protected Iterable readMultipleRecords(Iterable ids, FieldMask fieldMask) { + return recordStorage.readMultiple(ids, fieldMask); } @Override - protected Map readAllInternal() { + protected Map readAllRecords() { final Map result = recordStorage.readAll(); return result; } @Override - protected Map readAllInternal(FieldMask fieldMask) { + protected Map readAllRecords(FieldMask fieldMask) { final Map result = recordStorage.readAll(fieldMask); return result; } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 5e5060377f4..236ef61205d 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -58,7 +58,7 @@ protected InMemoryRecordStorage(boolean multitenant) { } @Override - protected Iterable readBulkInternal(final Iterable givenIds, FieldMask fieldMask) { + protected Iterable readMultipleRecords(final Iterable givenIds, FieldMask fieldMask) { final Map storage = getStorage(); // It is not possible to return an immutable collection, since {@code null} may be present in it. @@ -91,12 +91,12 @@ private EntityStorageRecord findAndApplyFieldMask(Map st } @Override - protected Iterable readBulkInternal(Iterable ids) { - return readBulkInternal(ids, FieldMask.getDefaultInstance()); + protected Iterable readMultipleRecords(Iterable ids) { + return readMultipleRecords(ids, FieldMask.getDefaultInstance()); } @Override - protected Map readAllInternal() { + protected Map readAllRecords() { final Map storage = getStorage(); final ImmutableMap result = ImmutableMap.copyOf(storage); @@ -104,10 +104,10 @@ protected Map readAllInternal() { } @Override - protected Map readAllInternal(FieldMask fieldMask) { + protected Map readAllRecords(FieldMask fieldMask) { if (fieldMask.getPathsList() .isEmpty()) { - return readAllInternal(); + return readAllRecords(); } final Map storage = getStorage(); @@ -153,12 +153,12 @@ private Map getStorage() { } @Override - protected EntityStorageRecord readInternal(I id) { + protected EntityStorageRecord readRecord(I id) { return getStorage().get(id); } @Override - protected void writeInternal(I id, EntityStorageRecord record) { + protected void writeRecord(I id, EntityStorageRecord record) { getStorage().put(id, record); } diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 77e80b2b991..45c2e327123 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -81,37 +81,37 @@ public boolean apply(@Nullable AggregateStateId stateId) { @Nullable @Override - protected EntityStorageRecord readInternal(AggregateStateId id) { + protected EntityStorageRecord readRecord(AggregateStateId id) { final EntityStorageRecord result = recordStorage.read(id); return result; } @Override - protected Iterable readBulkInternal(Iterable ids) { - final Iterable result = recordStorage.readBulk(ids); + protected Iterable readMultipleRecords(Iterable ids) { + final Iterable result = recordStorage.readMultiple(ids); return result; } @Override - protected Iterable readBulkInternal(Iterable ids, FieldMask fieldMask) { - final Iterable result = recordStorage.readBulk(ids, fieldMask); + protected Iterable readMultipleRecords(Iterable ids, FieldMask fieldMask) { + final Iterable result = recordStorage.readMultiple(ids, fieldMask); return result; } @Override - protected Map readAllInternal() { + protected Map readAllRecords() { final Map result = recordStorage.readAll(); return result; } @Override - protected Map readAllInternal(FieldMask fieldMask) { + protected Map readAllRecords(FieldMask fieldMask) { final Map result = recordStorage.readAll(fieldMask); return result; } @Override - protected void writeInternal(AggregateStateId id, EntityStorageRecord record) { + protected void writeRecord(AggregateStateId id, EntityStorageRecord record) { final TypeUrl recordType = TypeUrl.of(record.getState() .getTypeUrl()); checkState(id.getStateType().equals(recordType), "The typeUrl of the record does not correspond to id"); diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index cc13ded6c5d..8380737c8bb 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -834,7 +834,7 @@ private static void checkEmptyResultOnNonEmptyStorageForQueryTarget(Target custo when(standStorageMock.readAllByType(any(TypeUrl.class))).thenReturn(nonEmptyList); when(standStorageMock.read(any(AggregateStateId.class))).thenReturn(someRecord); when(standStorageMock.readAll()).thenReturn(Maps.newHashMap()); - when(standStorageMock.readBulk(ArgumentMatchers.anyIterable())).thenReturn(nonEmptyList); + when(standStorageMock.readMultiple(ArgumentMatchers.anyIterable())).thenReturn(nonEmptyList); final Stand stand = prepareStandWithAggregateRepo(standStorageMock); @@ -874,7 +874,7 @@ private static void setupExpectedBulkReadBehaviour(Map sam final ImmutableList records = recordsBuilder.build(); final Iterable matchingIds = argThat(idsMatcher(stateIds)); - when(standStorageMock.readBulk(matchingIds)).thenReturn(records); + when(standStorageMock.readMultiple(matchingIds)).thenReturn(records); } @SuppressWarnings("ConstantConditions") diff --git a/server/src/test/java/org/spine3/server/storage/AggregateStorageShould.java b/server/src/test/java/org/spine3/server/storage/AggregateStorageShould.java index 13ed3d836d4..c6a68090beb 100644 --- a/server/src/test/java/org/spine3/server/storage/AggregateStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/AggregateStorageShould.java @@ -159,7 +159,7 @@ public void write_and_read_event_by_Integer_id() { public void write_and_read_one_record() { final AggregateStorageRecord expected = Given.AggregateStorageRecord.create(getCurrentTime()); - storage.writeInternal(id, expected); + storage.writeRecord(id, expected); final Iterator iterator = storage.historyBackward(id); assertTrue(iterator.hasNext()); @@ -205,7 +205,7 @@ public void write_records_and_load_history_till_last_snapshot() { final Timestamp time2 = add(time1, delta); final Timestamp time3 = add(time2, delta); - storage.writeInternal(id, Given.AggregateStorageRecord.create(time1)); + storage.writeRecord(id, Given.AggregateStorageRecord.create(time1)); storage.write(id, newSnapshot(time2)); testWriteRecordsAndLoadHistory(time3); @@ -284,7 +284,7 @@ protected void testWriteRecordsAndLoadHistory(Timestamp firstRecordTime) { protected void writeAll(ProjectId id, Iterable records) { for (AggregateStorageRecord record : records) { - storage.writeInternal(id, record); + storage.writeRecord(id, record); } } diff --git a/server/src/test/java/org/spine3/server/storage/EventStorageShould.java b/server/src/test/java/org/spine3/server/storage/EventStorageShould.java index 9e1b44004d9..7df8bf0eef3 100644 --- a/server/src/test/java/org/spine3/server/storage/EventStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/EventStorageShould.java @@ -107,7 +107,7 @@ public void writeInternal_and_read_one_event() { final EventId id = EventId.newBuilder().setUuid(recordToStore.getEventId()).build(); final Event expected = toEvent(recordToStore); - storage.writeInternal(recordToStore); + storage.writeRecord(recordToStore); final Event actual = storage.read(id); assertEquals(expected, actual); @@ -400,7 +400,7 @@ private void givenSequentialRecords(long deltaSeconds, int deltaNanos) { protected void writeAll(EventStorageRecord... records) { for (EventStorageRecord r : records) { - storage.writeInternal(r); + storage.writeRecord(r); } } From 8b3534d5037fffe5495e9308883ac40a26c810cf Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 18:53:36 +0300 Subject: [PATCH 328/361] Update the Projection-related tests according to the storage method renaming. --- .../org/spine3/server/storage/ProjectionStorageShould.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index da9b536459f..119e3df8576 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -142,7 +142,7 @@ public void perform_read_bulk_operations() { // Get a subset of IDs final List ids = fillStorage(10).subList(0, 5); - final Iterable read = storage.readBulk(ids); + final Iterable read = storage.readMultiple(ids); assertSize(ids.size(), read); // Check data consistency @@ -161,7 +161,7 @@ public void perform_bulk_read_with_field_mask_operation() { .getFullName(); final FieldMask fieldMask = maskForPaths(projectDescriptor + ".id", projectDescriptor + ".status"); - final Iterable read = storage.readBulk(ids, fieldMask); + final Iterable read = storage.readMultiple(ids, fieldMask); assertSize(ids.size(), read); // Check data consistency @@ -217,6 +217,7 @@ private void writeAndReadLastEventTimeTest(Timestamp expected) { assertEquals(expected, actual); } + @SuppressWarnings("BreakStatement") private static Project checkProjectIdIsInList(EntityStorageRecord project, List ids) { final Any packedState = project.getState(); final Project state = AnyPacker.unpack(packedState); From b20a66845ddce15c9b1394ab5af4cce8c4ac50c4 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 19:05:02 +0300 Subject: [PATCH 329/361] Add the `todo` items to reflect the latest SPI method review results in the codebase. --- .../main/java/org/spine3/server/storage/AggregateStorage.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java index 4d007c46434..91620e055b0 100644 --- a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java +++ b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java @@ -164,6 +164,7 @@ public void write(I aggregateId, Snapshot snapshot) { * @return an even count after the last snapshot * @throws IllegalStateException if the storage is closed */ + // TODO[alex.tymchenko]: make it `protected` after this Storage is moved to `aggregate` package. public abstract int readEventCountAfterLastSnapshot(I id); /** @@ -174,6 +175,7 @@ public void write(I aggregateId, Snapshot snapshot) { * @param eventCount an even count after the last snapshot * @throws IllegalStateException if the storage is closed */ + // TODO[alex.tymchenko]: make it `protected` after this Storage is moved to `aggregate` package. public abstract void writeEventCountAfterLastSnapshot(I id, int eventCount); private static AggregateStorageRecord toStorageRecord(Event event) { From 0136ac095f5b92c9f5140de321ff2c0e4b7c1baa Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:32:45 +0300 Subject: [PATCH 330/361] Improve code readability: remove redundant empty lines and extract variables. --- .../spine3/server/transport/GrpcContainer.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java index 2d0273b82a6..4d0bc9bfa99 100644 --- a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -54,7 +54,6 @@ public class GrpcContainer { @Nullable private io.grpc.Server grpcServer; - public static Builder newBuilder() { return new Builder(); } @@ -64,7 +63,6 @@ protected GrpcContainer(Builder builder) { this.services = builder.getServices(); } - /** * Starts the service. * @@ -76,7 +74,6 @@ public void start() throws IOException { grpcServer.start(); } - /** * Returns {@code true} if the server is shut down or was not started at all, {@code false} otherwise. * @@ -94,7 +91,6 @@ public void shutdown() { grpcServer = null; } - /** Waits for the service to become terminated. */ public void awaitTermination() { checkState(grpcServer != null, SERVER_NOT_STARTED_MSG); @@ -105,7 +101,6 @@ public void awaitTermination() { } } - /** * Check if the given gRPC service is scheduled for the deployment in this container. * @@ -119,7 +114,6 @@ public boolean isScheduledForDeployment(BindableService service) { final String nameOfInterest = service.bindService() .getServiceDescriptor() .getName(); - boolean serviceIsPresent = false; for (ServerServiceDefinition serverServiceDefinition : services) { final String scheduledServiceName = serverServiceDefinition.getServiceDescriptor() @@ -140,7 +134,10 @@ public boolean isScheduledForDeployment(BindableService service) { * @return {@code true}, if the service is available for interaction within this container; {@code false} otherwise */ public boolean isLive(BindableService service) { - return !isShutdown() && isScheduledForDeployment(service); + final boolean inShutdownState = isShutdown(); + final boolean scheduledForDeployment = isScheduledForDeployment(service); + final boolean result = !inShutdownState && scheduledForDeployment; + return result; } /** @@ -180,7 +177,6 @@ public void run() { return builder.build(); } - public static class Builder { private int port = ConnectionConstants.DEFAULT_CLIENT_SERVICE_PORT; @@ -212,8 +208,5 @@ public ImmutableSet getServices() { public GrpcContainer build() { return new GrpcContainer(this); } - } - - } From ae2540777ef6f51c7fd7833f7fce27d1f030846e Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:33:52 +0300 Subject: [PATCH 331/361] Align class-level JavaDocs properly. --- .../src/main/java/org/spine3/server/transport/GrpcContainer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java index 4d0bc9bfa99..7b53c511c8e 100644 --- a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -40,6 +40,7 @@ * Wrapping container for gRPC server. * *

        Maintains and deploys several of gRPC services within a single server. + * *

        Uses {@link ServerServiceDefinition}s of each service. * * @author Alex Tymchenko From 782365b5a9b6d6832814d42d2e4020c153eed044 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:36:06 +0300 Subject: [PATCH 332/361] Remove excessive empty lines. --- .../src/test/java/org/spine3/server/CommandServiceShould.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/CommandServiceShould.java b/server/src/test/java/org/spine3/server/CommandServiceShould.java index af3e1a6d676..bc5962c80a1 100644 --- a/server/src/test/java/org/spine3/server/CommandServiceShould.java +++ b/server/src/test/java/org/spine3/server/CommandServiceShould.java @@ -20,7 +20,6 @@ package org.spine3.server; - import com.google.common.collect.Sets; import com.google.protobuf.StringValue; import io.grpc.stub.StreamObserver; @@ -152,7 +151,6 @@ public void deploy_to_grpc_container() throws IOException { } - /* * Stub repositories and aggregates ***************************************************/ From a1cefab973d51f59fb379a8aed5ad6e6536f1c1b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:37:37 +0300 Subject: [PATCH 333/361] Remove excessive empty lines. --- .../test/java/org/spine3/server/QueryServiceShould.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index f50537d55af..4f788e52811 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -50,7 +50,6 @@ import static org.mockito.Mockito.when; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; - /** * @author Alex Tymchenko */ @@ -77,21 +76,18 @@ public void setUp() { boundedContexts.add(projectsContext); - // Create Customers Bounded Context with one repository. customersContext = newBoundedContext(spy(TestStandFactory.create())); final Given.CustomerAggregateRepository customerRepo = new Given.CustomerAggregateRepository(customersContext); customersContext.register(customerRepo); boundedContexts.add(customersContext); - final QueryService.Builder builder = QueryService.newBuilder(); for (BoundedContext context : boundedContexts) { builder.addBoundedContext(context); } - service = spy(builder.build()); } @@ -138,7 +134,6 @@ public void fail_to_create_with_no_bounded_context() { .build(); } - @Test public void return_error_if_query_failed_to_execute() { when(projectDetailsRepository.loadAll()).thenThrow(RuntimeException.class); @@ -147,7 +142,6 @@ public void return_error_if_query_failed_to_execute() { checkFailureResponse(responseObserver); } - private static void checkOkResponse(TestQueryResponseObserver responseObserver) { final QueryResponse responseHandled = responseObserver.getResponseHandled(); assertNotNull(responseHandled); @@ -163,7 +157,6 @@ private static void checkFailureResponse(TestQueryResponseObserver responseObser assertNotNull(responseObserver.getThrowable()); } - /* * Stub repositories and projections ***************************************************/ From f881b83e9c17c6a28200bc298e603a0b8329a56f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:38:12 +0300 Subject: [PATCH 334/361] Remove excessive empty line. --- server/src/test/java/org/spine3/server/QueryServiceShould.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index 4f788e52811..3de70c3c520 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -185,7 +185,6 @@ public ProjectDetails(ProjectId id) { public void on(ProjectCreated event, EventContext context) { // Do nothing. } - } private static class TestQueryResponseObserver implements StreamObserver { From b7a891fa89f945f1a04a6727b15af8458275f33a Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:39:53 +0300 Subject: [PATCH 335/361] Remove excessive empty lines. --- .../server/SubscriptionServiceShould.java | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index b0c795cbdd5..d7a35ec628d 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -63,7 +63,7 @@ public void initialize_properly_with_one_bounded_context() { final BoundedContext singleBoundedContext = newBoundedContext("Single", newSimpleStand()); final SubscriptionService.Builder builder = SubscriptionService.newBuilder() - .addBoundedContext(singleBoundedContext); + .addBoundedContext(singleBoundedContext); final SubscriptionService subscriptionService = builder.build(); assertNotNull(subscriptionService); @@ -79,12 +79,10 @@ public void initialize_properly_with_several_bounded_contexts() { final BoundedContext secondBoundedContext = newBoundedContext("Second", newSimpleStand()); final BoundedContext thirdBoundedContext = newBoundedContext("Third", newSimpleStand()); - final SubscriptionService.Builder builder = SubscriptionService.newBuilder() - .addBoundedContext(firstBoundedContext) - .addBoundedContext(secondBoundedContext) - .addBoundedContext(thirdBoundedContext); - + .addBoundedContext(firstBoundedContext) + .addBoundedContext(secondBoundedContext) + .addBoundedContext(thirdBoundedContext); final SubscriptionService service = builder.build(); assertNotNull(service); @@ -101,14 +99,12 @@ public void be_able_to_remove_bounded_context_from_builder() { final BoundedContext secondBoundedContext = newBoundedContext("Also removed", newSimpleStand()); final BoundedContext thirdBoundedContext = newBoundedContext("The one to stay", newSimpleStand()); - final SubscriptionService.Builder builder = SubscriptionService.newBuilder() - .addBoundedContext(firstBoundedContext) - .addBoundedContext(secondBoundedContext) - .addBoundedContext(thirdBoundedContext) - .removeBoundedContext(secondBoundedContext) - .removeBoundedContext(firstBoundedContext); - + .addBoundedContext(firstBoundedContext) + .addBoundedContext(secondBoundedContext) + .addBoundedContext(thirdBoundedContext) + .removeBoundedContext(secondBoundedContext) + .removeBoundedContext(firstBoundedContext); final SubscriptionService subscriptionService = builder.build(); assertNotNull(subscriptionService); @@ -121,7 +117,8 @@ public void be_able_to_remove_bounded_context_from_builder() { @Test(expected = IllegalStateException.class) public void fail_to_initialize_from_empty_builder() { - SubscriptionService.newBuilder().build(); + SubscriptionService.newBuilder() + .build(); } /* @@ -136,13 +133,11 @@ public void subscribe_to_topic() { final SubscriptionService subscriptionService = SubscriptionService.newBuilder() .addBoundedContext(boundedContext) .build(); - final String type = boundedContext.getStand() .getExposedTypes() .iterator() .next() .getTypeName(); - final Target target = getProjectQueryTarget(); assertEquals(type, target.getType()); @@ -159,7 +154,6 @@ public void subscribe_to_topic() { assertTrue(observer.streamFlowValue.isInitialized()); assertEquals(observer.streamFlowValue.getType(), type); - assertNull(observer.throwable); assertTrue(observer.isCompleted); } @@ -171,7 +165,6 @@ public void activate_subscription() { final SubscriptionService subscriptionService = SubscriptionService.newBuilder() .addBoundedContext(boundedContext) .build(); - final Target target = getProjectQueryTarget(); final Topic topic = Topic.newBuilder() @@ -188,9 +181,14 @@ public void activate_subscription() { subscriptionService.activate(subscriptionObserver.streamFlowValue, activationObserver); // Post update to Stand directly - final ProjectId projectId = ProjectId.newBuilder().setId("some-id").build(); - final Message projectState = Project.newBuilder().setId(projectId).build(); - boundedContext.getStand().update(projectId, AnyPacker.pack(projectState)); + final ProjectId projectId = ProjectId.newBuilder() + .setId("some-id") + .build(); + final Message projectState = Project.newBuilder() + .setId(projectId) + .build(); + boundedContext.getStand() + .update(projectId, AnyPacker.pack(projectState)); // isCompleted set to false since we don't expect activationObserver::onCompleted to be called. activationObserver.verifyState(false); @@ -222,9 +220,14 @@ public void cancel_subscription_on_topic() { subscriptionService.cancel(subscribeObserver.streamFlowValue, new MemoizeStreamObserver()); // Post update to Stand - final ProjectId projectId = ProjectId.newBuilder().setId("some-other-id").build(); - final Message projectState = Project.newBuilder().setId(projectId).build(); - boundedContext.getStand().update(projectId, AnyPacker.pack(projectState)); + final ProjectId projectId = ProjectId.newBuilder() + .setId("some-other-id") + .build(); + final Message projectState = Project.newBuilder() + .setId(projectId) + .build(); + boundedContext.getStand() + .update(projectId, AnyPacker.pack(projectState)); // The update must not be handled by the observer verify(activateSubscription, never()).onNext(any(SubscriptionUpdate.class)); @@ -232,7 +235,8 @@ public void cancel_subscription_on_topic() { } private static BoundedContext setupBoundedContextForAggregateRepo() { - final Stand stand = Stand.newBuilder().build(); + final Stand stand = Stand.newBuilder() + .build(); final BoundedContext boundedContext = newBoundedContext(stand); @@ -246,7 +250,8 @@ private static Target getProjectQueryTarget() { } private static Stand newSimpleStand() { - return Stand.newBuilder().build(); + return Stand.newBuilder() + .build(); } private static class MemoizeStreamObserver implements StreamObserver { @@ -279,6 +284,5 @@ private void verifyState(boolean isCompleted) { assertNull(throwable); assertEquals(this.isCompleted, isCompleted); } - } } From 4c11fea78e097a58eed311748c94b0728efd293b Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:46:25 +0300 Subject: [PATCH 336/361] Fix copyright header by removing the trailing empty line. --- client/src/main/java/org/spine3/base/Queries.java | 1 - client/src/test/java/org/spine3/base/QueriesShould.java | 1 - server/src/main/java/org/spine3/server/QueryService.java | 1 - server/src/main/java/org/spine3/server/SubscriptionService.java | 1 - .../java/org/spine3/server/stand/AggregateQueryProcessor.java | 1 - .../src/main/java/org/spine3/server/stand/AggregateStateId.java | 1 - .../main/java/org/spine3/server/stand/EntityQueryProcessor.java | 1 - .../main/java/org/spine3/server/stand/NoopQueryProcessor.java | 1 - server/src/main/java/org/spine3/server/stand/QueryProcessor.java | 1 - server/src/main/java/org/spine3/server/stand/Stand.java | 1 - server/src/main/java/org/spine3/server/stand/StandFunnel.java | 1 - .../main/java/org/spine3/server/stand/SubscriptionRecord.java | 1 - .../main/java/org/spine3/server/stand/SubscriptionRegistry.java | 1 - server/src/main/java/org/spine3/server/stand/package-info.java | 1 - .../org/spine3/server/storage/BulkStorageOperationsMixin.java | 1 - server/src/main/java/org/spine3/server/storage/StandStorage.java | 1 - .../org/spine3/server/storage/memory/InMemoryStandStorage.java | 1 - .../src/main/java/org/spine3/server/transport/GrpcContainer.java | 1 - .../src/main/java/org/spine3/server/transport/package-info.java | 1 - server/src/test/java/org/spine3/server/QueryServiceShould.java | 1 - .../src/test/java/org/spine3/server/stand/StandFunnelShould.java | 1 - server/src/test/java/org/spine3/server/stand/StandShould.java | 1 - .../java/org/spine3/server/transport/GrpcContainerShould.java | 1 - server/src/test/java/org/spine3/testdata/TestStandFactory.java | 1 - 24 files changed, 24 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Queries.java b/client/src/main/java/org/spine3/base/Queries.java index d1ecfaa0699..14cf708d807 100644 --- a/client/src/main/java/org/spine3/base/Queries.java +++ b/client/src/main/java/org/spine3/base/Queries.java @@ -16,7 +16,6 @@ * 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 org.spine3.base; diff --git a/client/src/test/java/org/spine3/base/QueriesShould.java b/client/src/test/java/org/spine3/base/QueriesShould.java index c78156690c7..0a711325774 100644 --- a/client/src/test/java/org/spine3/base/QueriesShould.java +++ b/client/src/test/java/org/spine3/base/QueriesShould.java @@ -16,7 +16,6 @@ * 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 org.spine3.base; diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index 813d4d356dd..b0cb1364c49 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -16,7 +16,6 @@ * 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 org.spine3.server; diff --git a/server/src/main/java/org/spine3/server/SubscriptionService.java b/server/src/main/java/org/spine3/server/SubscriptionService.java index d8bd58ce67a..b62c9f867e1 100644 --- a/server/src/main/java/org/spine3/server/SubscriptionService.java +++ b/server/src/main/java/org/spine3/server/SubscriptionService.java @@ -16,7 +16,6 @@ * 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 org.spine3.server; diff --git a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java index 455bafab197..43a3981d92f 100644 --- a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/AggregateStateId.java b/server/src/main/java/org/spine3/server/stand/AggregateStateId.java index 725d4f09e71..2696d039254 100644 --- a/server/src/main/java/org/spine3/server/stand/AggregateStateId.java +++ b/server/src/main/java/org/spine3/server/stand/AggregateStateId.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java index 4136e52ec3b..c99de0bcb5f 100644 --- a/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/EntityQueryProcessor.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java index 74cd765218a..f67eb2d7250 100644 --- a/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/NoopQueryProcessor.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/QueryProcessor.java b/server/src/main/java/org/spine3/server/stand/QueryProcessor.java index 55a006c9cdc..80fc5cb028d 100644 --- a/server/src/main/java/org/spine3/server/stand/QueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/QueryProcessor.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 221ff09ed1b..2911c9f97c6 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/StandFunnel.java b/server/src/main/java/org/spine3/server/stand/StandFunnel.java index abd23cdabf1..3292ec0b338 100644 --- a/server/src/main/java/org/spine3/server/stand/StandFunnel.java +++ b/server/src/main/java/org/spine3/server/stand/StandFunnel.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java index f2d99852917..5c23c0ca4ee 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRecord.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java index f84ce76f2a7..40adecdd743 100644 --- a/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java +++ b/server/src/main/java/org/spine3/server/stand/SubscriptionRegistry.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/main/java/org/spine3/server/stand/package-info.java b/server/src/main/java/org/spine3/server/stand/package-info.java index 8e2f5fcf4cd..c2d66c7cf5f 100644 --- a/server/src/main/java/org/spine3/server/stand/package-info.java +++ b/server/src/main/java/org/spine3/server/stand/package-info.java @@ -16,7 +16,6 @@ * 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. - * */ /** diff --git a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java index 7806bf587ad..4c858c40a97 100644 --- a/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java +++ b/server/src/main/java/org/spine3/server/storage/BulkStorageOperationsMixin.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.storage; diff --git a/server/src/main/java/org/spine3/server/storage/StandStorage.java b/server/src/main/java/org/spine3/server/storage/StandStorage.java index 9c3996ba0e5..ff384fb1314 100644 --- a/server/src/main/java/org/spine3/server/storage/StandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/StandStorage.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.storage; diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java index 45c2e327123..1bb8182bb97 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryStandStorage.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.storage.memory; diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java index 7b53c511c8e..abd34e859a3 100644 --- a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.transport; diff --git a/server/src/main/java/org/spine3/server/transport/package-info.java b/server/src/main/java/org/spine3/server/transport/package-info.java index f10c3f33e89..7d4da5aeb98 100644 --- a/server/src/main/java/org/spine3/server/transport/package-info.java +++ b/server/src/main/java/org/spine3/server/transport/package-info.java @@ -16,7 +16,6 @@ * 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. - * */ /** diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index 3de70c3c520..37e208b7c73 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -16,7 +16,6 @@ * 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 org.spine3.server; diff --git a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java index 2b8d13dc73c..9aee7a378eb 100644 --- a/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandFunnelShould.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 8380737c8bb..2c2a62386a5 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.stand; diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java index ce762913243..620e5a8b158 100644 --- a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -16,7 +16,6 @@ * 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 org.spine3.server.transport; diff --git a/server/src/test/java/org/spine3/testdata/TestStandFactory.java b/server/src/test/java/org/spine3/testdata/TestStandFactory.java index df119baf462..afeed42daee 100644 --- a/server/src/test/java/org/spine3/testdata/TestStandFactory.java +++ b/server/src/test/java/org/spine3/testdata/TestStandFactory.java @@ -16,7 +16,6 @@ * 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 org.spine3.testdata; From 755436fa1ab43164554277b4b8719cd16dd81456 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:49:37 +0300 Subject: [PATCH 337/361] Make the customer names more neutral for tests. --- .../src/test/java/org/spine3/server/stand/StandShould.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 2c2a62386a5..ab8540c9254 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -674,12 +674,12 @@ private static Customer getSampleCustomer() { .setNumber((int) UUID.randomUUID() .getLeastSignificantBits())) .setName(PersonName.newBuilder() - .setGivenName("Socrates") + .setGivenName("John") .build()) .addNicknames(PersonName.newBuilder() - .setGivenName("Philosopher")) + .setGivenName("Johnny")) .addNicknames(PersonName.newBuilder() - .setGivenName("Wise guy")) + .setGivenName("Big Guy")) .build(); } From 79feace02a8d7d9d65c588486181c9f32cb56fba Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Tue, 11 Oct 2016 20:51:29 +0300 Subject: [PATCH 338/361] Suppress a minor warning in the test codebase and explain why. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 1 + .../java/org/spine3/server/storage/ProjectionStorageShould.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index ab8540c9254..91836987714 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -569,6 +569,7 @@ public void select_entity_singleton_by_id_and_apply_field_masks() { .build()); final String customerDescriptor = Customer.getDescriptor() .getFullName(); + @SuppressWarnings("DuplicateStringLiteralInspection") // clashes with non-related tests. final String[] paths = {customerDescriptor + ".id", customerDescriptor + ".name"}; final FieldMask fieldMask = FieldMask.newBuilder() .addAllPaths(Arrays.asList(paths)) diff --git a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java index 119e3df8576..825bd2b64c3 100644 --- a/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/ProjectionStorageShould.java @@ -105,7 +105,7 @@ public void read_all_messages_with_field_mask() { final String projectDescriptor = Project.getDescriptor() .getFullName(); - @SuppressWarnings("DuplicateStringLiteralInspection") + @SuppressWarnings("DuplicateStringLiteralInspection") // clashes with non-related tests. final FieldMask fieldMask = maskForPaths(projectDescriptor + ".id", projectDescriptor + ".name"); final Map read = storage.readAll(fieldMask); From ba08bbba1d14b12dca7f83d4bdb90cfe06929629 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 11:45:37 +0300 Subject: [PATCH 339/361] Add read-all tests for record storage. --- .../server/storage/RecordStorageShould.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java index 39272553426..07250ab573d 100644 --- a/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java @@ -20,13 +20,24 @@ package org.spine3.server.storage; +import com.google.common.base.Supplier; import com.google.protobuf.FieldMask; import org.junit.Test; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.Timestamps; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.Task; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.UUID; import static org.junit.Assert.assertNotNull; import static org.spine3.test.Verify.assertEmpty; +import static org.spine3.test.Verify.assertSize; /** * @author Dmytro Dashenkov @@ -46,5 +57,47 @@ public void retrieve_empty_map_if_storage_is_empty() { assertEmpty(empty); } + @Test + public void retrieve_all_records() { + final RecordStorage storage = createStorage(); + final Collection ids = fill(storage, 10, new Supplier() { + @Override + public String get() { + return UUID.randomUUID() + .toString(); + } + }); + + final Map allRecords = storage.readAll(); + assertSize(ids.size(), allRecords); + + } + + protected static List fill(RecordStorage storage, int count, Supplier idSupplier) { + final List ids = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final T genericId = idSupplier.get(); + final ProjectId id = ProjectId.newBuilder() + .setId(genericId.toString()) + .build(); + final Project project = Project.newBuilder() + .setId(id) + .setStatus(Project.Status.CREATED) + .setName(String.format("test-project-%s", i)) + .addTask(Task.getDefaultInstance()) + .build(); + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(AnyPacker.pack(project)) + .setWhenModified(Timestamps.getCurrentTime()) + .setVersion(1) + .build(); + storage.write(genericId, record); + ids.add(genericId); + } + + return ids; + } + protected abstract RecordStorage createStorage(); } From 8a157f2705bd663bb980ee60911c18fe551258da Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 12:03:45 +0300 Subject: [PATCH 340/361] Extract stand storage tests into separate class. --- .../server/storage/RecordStorageShould.java | 53 --------- .../server/storage/StandStorageShould.java | 108 ++++++++++++++++++ .../memory/InMemoryStandStorageShould.java | 37 ++++++ 3 files changed, 145 insertions(+), 53 deletions(-) create mode 100644 server/src/test/java/org/spine3/server/storage/StandStorageShould.java create mode 100644 server/src/test/java/org/spine3/server/storage/memory/InMemoryStandStorageShould.java diff --git a/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java index 07250ab573d..39272553426 100644 --- a/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/RecordStorageShould.java @@ -20,24 +20,13 @@ package org.spine3.server.storage; -import com.google.common.base.Supplier; import com.google.protobuf.FieldMask; import org.junit.Test; -import org.spine3.protobuf.AnyPacker; -import org.spine3.protobuf.Timestamps; -import org.spine3.test.projection.Project; -import org.spine3.test.projection.ProjectId; -import org.spine3.test.projection.Task; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.UUID; import static org.junit.Assert.assertNotNull; import static org.spine3.test.Verify.assertEmpty; -import static org.spine3.test.Verify.assertSize; /** * @author Dmytro Dashenkov @@ -57,47 +46,5 @@ public void retrieve_empty_map_if_storage_is_empty() { assertEmpty(empty); } - @Test - public void retrieve_all_records() { - final RecordStorage storage = createStorage(); - final Collection ids = fill(storage, 10, new Supplier() { - @Override - public String get() { - return UUID.randomUUID() - .toString(); - } - }); - - final Map allRecords = storage.readAll(); - assertSize(ids.size(), allRecords); - - } - - protected static List fill(RecordStorage storage, int count, Supplier idSupplier) { - final List ids = new LinkedList<>(); - - for (int i = 0; i < count; i++) { - final T genericId = idSupplier.get(); - final ProjectId id = ProjectId.newBuilder() - .setId(genericId.toString()) - .build(); - final Project project = Project.newBuilder() - .setId(id) - .setStatus(Project.Status.CREATED) - .setName(String.format("test-project-%s", i)) - .addTask(Task.getDefaultInstance()) - .build(); - final EntityStorageRecord record = EntityStorageRecord.newBuilder() - .setState(AnyPacker.pack(project)) - .setWhenModified(Timestamps.getCurrentTime()) - .setVersion(1) - .build(); - storage.write(genericId, record); - ids.add(genericId); - } - - return ids; - } - protected abstract RecordStorage createStorage(); } diff --git a/server/src/test/java/org/spine3/server/storage/StandStorageShould.java b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java new file mode 100644 index 00000000000..68114bee2ad --- /dev/null +++ b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.storage; + +import com.google.common.base.Supplier; +import com.google.protobuf.Any; +import org.junit.Test; +import org.spine3.protobuf.AnyPacker; +import org.spine3.protobuf.Timestamps; +import org.spine3.protobuf.TypeUrl; +import org.spine3.server.stand.AggregateStateId; +import org.spine3.test.projection.Project; +import org.spine3.test.projection.ProjectId; +import org.spine3.test.projection.Task; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.spine3.test.Verify.assertContains; +import static org.spine3.test.Verify.assertSize; + +/** + * @author Dmytro Dashenkov + */ +public abstract class StandStorageShould { + + protected abstract StandStorage createStorage(); + + @Test + public void retrieve_all_records() { + final StandStorage storage = createStorage(); + final List> ids = fill(storage, 10, new Supplier>() { + @SuppressWarnings("unchecked") + @Override + public AggregateStateId get() { + final ProjectId projectId = ProjectId.newBuilder() + .setId(UUID.randomUUID() + .toString()) + .build(); + return AggregateStateId.of(projectId, TypeUrl.of(Project.class)); + } + }); + + final Map allRecords = storage.readAll(); + + checkIds(ids, allRecords.values()); + } + + protected static List> fill(StandStorage storage, + int count, + Supplier> idSupplier) { + final List> ids = new LinkedList<>(); + + for (int i = 0; i < count; i++) { + final AggregateStateId genericId = idSupplier.get(); + final ProjectId id = genericId.getAggregateId(); + final Project project = Project.newBuilder() + .setId(id) + .setStatus(Project.Status.CREATED) + .setName(String.format("test-project-%s", i)) + .addTask(Task.getDefaultInstance()) + .build(); + final EntityStorageRecord record = EntityStorageRecord.newBuilder() + .setState(AnyPacker.pack(project)) + .setWhenModified(Timestamps.getCurrentTime()) + .setVersion(1) + .build(); + storage.write(genericId, record); + ids.add(genericId); + } + + return ids; + } + + protected void checkIds(List> ids, Collection records) { + assertSize(ids.size(), records); + + for (EntityStorageRecord record : records) { + final Any packedState = record.getState(); + final Project state = AnyPacker.unpack(packedState); + final ProjectId id = state.getId(); + + assertContains(id, ids); + } + } + +} diff --git a/server/src/test/java/org/spine3/server/storage/memory/InMemoryStandStorageShould.java b/server/src/test/java/org/spine3/server/storage/memory/InMemoryStandStorageShould.java new file mode 100644 index 00000000000..801816d0e7c --- /dev/null +++ b/server/src/test/java/org/spine3/server/storage/memory/InMemoryStandStorageShould.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016, TeamDev Ltd. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.spine3.server.storage.memory; + +import org.spine3.server.storage.StandStorage; +import org.spine3.server.storage.StandStorageShould; + +/** + * @author Dmytro Dashenkov + */ +public class InMemoryStandStorageShould extends StandStorageShould { + + @Override + protected StandStorage createStorage() { + return InMemoryStandStorage.newBuilder() + .setMultitenant(false) + .build(); + } +} From c3d6558e4eed8be2304803fe8f40a1a52f8baf18 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 12:10:15 +0300 Subject: [PATCH 341/361] Fix ids check. --- .../server/storage/StandStorageShould.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/storage/StandStorageShould.java b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java index 68114bee2ad..1db8719b282 100644 --- a/server/src/test/java/org/spine3/server/storage/StandStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java @@ -20,7 +20,9 @@ package org.spine3.server.storage; +import com.google.common.base.Function; import com.google.common.base.Supplier; +import com.google.common.collect.Collections2; import com.google.protobuf.Any; import org.junit.Test; import org.spine3.protobuf.AnyPacker; @@ -31,6 +33,7 @@ import org.spine3.test.projection.ProjectId; import org.spine3.test.projection.Task; +import javax.annotation.Nullable; import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -96,12 +99,23 @@ protected static List> fill(StandStorage storage, protected void checkIds(List> ids, Collection records) { assertSize(ids.size(), records); + final Collection projectIds = Collections2.transform(ids, new Function, ProjectId>() { + @Nullable + @Override + public ProjectId apply(@Nullable AggregateStateId input) { + if (input == null) { + return null; + } + return input.getAggregateId(); + } + }); + for (EntityStorageRecord record : records) { final Any packedState = record.getState(); final Project state = AnyPacker.unpack(packedState); final ProjectId id = state.getId(); - assertContains(id, ids); + assertContains(id, projectIds); } } From e45dc75a477a68e2a624441a5a629c6aa2fc99c3 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 12:25:36 +0300 Subject: [PATCH 342/361] Add readMultiple(ids) test. --- .../server/storage/StandStorageShould.java | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/server/src/test/java/org/spine3/server/storage/StandStorageShould.java b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java index 1db8719b282..e84c1089d4a 100644 --- a/server/src/test/java/org/spine3/server/storage/StandStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java @@ -50,34 +50,45 @@ public abstract class StandStorageShould { protected abstract StandStorage createStorage(); + protected static final Supplier> DEFAULT_ID_SUPPLIER = new Supplier>() { + @SuppressWarnings("unchecked") + @Override + public AggregateStateId get() { + final ProjectId projectId = ProjectId.newBuilder() + .setId(UUID.randomUUID() + .toString()) + .build(); + return AggregateStateId.of(projectId, TypeUrl.of(Project.class)); + } + }; + @Test public void retrieve_all_records() { final StandStorage storage = createStorage(); - final List> ids = fill(storage, 10, new Supplier>() { - @SuppressWarnings("unchecked") - @Override - public AggregateStateId get() { - final ProjectId projectId = ProjectId.newBuilder() - .setId(UUID.randomUUID() - .toString()) - .build(); - return AggregateStateId.of(projectId, TypeUrl.of(Project.class)); - } - }); + final List ids = fill(storage, 10, DEFAULT_ID_SUPPLIER); final Map allRecords = storage.readAll(); - checkIds(ids, allRecords.values()); } - protected static List> fill(StandStorage storage, - int count, - Supplier> idSupplier) { - final List> ids = new LinkedList<>(); + @Test + public void retrieve_records_by_ids() { + final StandStorage storage = createStorage(); + // Use a subset of IDs + final List ids = fill(storage, 10, DEFAULT_ID_SUPPLIER).subList(0, 5); + + final Collection records = (Collection) storage.readMultiple(ids); + checkIds(ids, records); + } + + protected static List fill(StandStorage storage, + int count, + Supplier> idSupplier) { + final List ids = new LinkedList<>(); for (int i = 0; i < count; i++) { - final AggregateStateId genericId = idSupplier.get(); - final ProjectId id = genericId.getAggregateId(); + final AggregateStateId genericId = idSupplier.get(); + final ProjectId id = (ProjectId) genericId.getAggregateId(); final Project project = Project.newBuilder() .setId(id) .setStatus(Project.Status.CREATED) @@ -96,17 +107,17 @@ protected static List> fill(StandStorage storage, return ids; } - protected void checkIds(List> ids, Collection records) { + protected void checkIds(List ids, Collection records) { assertSize(ids.size(), records); - final Collection projectIds = Collections2.transform(ids, new Function, ProjectId>() { + final Collection projectIds = Collections2.transform(ids, new Function() { @Nullable @Override - public ProjectId apply(@Nullable AggregateStateId input) { + public ProjectId apply(@Nullable AggregateStateId input) { if (input == null) { return null; } - return input.getAggregateId(); + return (ProjectId) input.getAggregateId(); } }); From 9b2d4da98955b29539b12f5a656e2fa6239edfee Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 13:11:20 +0300 Subject: [PATCH 343/361] Add Builder methods test. --- .../server/transport/GrpcContainerShould.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java index ce762913243..10949ac6158 100644 --- a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -20,20 +20,29 @@ */ package org.spine3.server.transport; +import io.grpc.BindableService; import io.grpc.Server; +import io.grpc.ServerServiceDefinition; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.spine3.test.Verify.assertSize; /** * @author Alex Tymchenko @@ -42,14 +51,14 @@ public class GrpcContainerShould { private Server server; private GrpcContainer grpcContainer; - @Before public void setUp() { final GrpcContainer.Builder builder = GrpcContainer.newBuilder(); grpcContainer = spy(builder.build()); server = mock(Server.class); - doReturn(server).when(grpcContainer).createGrpcServer(); + doReturn(server).when(grpcContainer) + .createGrpcServer(); } @After @@ -66,6 +75,38 @@ public void start_server() throws IOException { verify(server).start(); } + @SuppressWarnings("MagicNumber") + @Test + public void add_and_remove_parameters_form_builder() { + final GrpcContainer.Builder builder = GrpcContainer.newBuilder() + .setPort(8080) + .setPort(60); + assertEquals(60, builder.getPort()); + + int count = 3; + final List definitions = new ArrayList<>(count); + + for (int i = 0; i < count; i++) { + final BindableService mockService = mock(BindableService.class); + final ServerServiceDefinition mockDefinition = ServerServiceDefinition + .builder(String.format("service-%s", i)) + .build(); + when(mockService.bindService()).thenReturn(mockDefinition); + definitions.add(mockDefinition); + + builder.addService(mockService); + } + + count--; + builder.removeService(definitions.get(count)); + + final Set serviceSet = builder.getServices(); + assertSize(count, serviceSet); + + final GrpcContainer container = builder.build(); + assertNotNull(container); + } + @Test public void throw_exception_if_started_already() throws IOException { grpcContainer.start(); From 3d02a988ad12e45e6441352d4621500ef081f6d2 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 13:41:15 +0300 Subject: [PATCH 344/361] Add test for adding shutdown hook. --- .../server/transport/GrpcContainer.java | 40 ++++++++++--------- .../server/transport/GrpcContainerShould.java | 23 +++++++++++ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java index 2d0273b82a6..f2edf0ccc03 100644 --- a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -150,24 +150,7 @@ public boolean isLive(BindableService service) { */ public void addShutdownHook() { Runtime.getRuntime() - .addShutdownHook(new Thread(new Runnable() { - // Use stderr here since the logger may have been reset by its JVM shutdown hook. - @SuppressWarnings("UseOfSystemOutOrSystemErr") - @Override - public void run() { - final String serverClass = GrpcContainer.this.getClass() - .getName(); - try { - if (!isShutdown()) { - System.err.println("Shutting down " + serverClass + " since JVM is shutting down..."); - shutdown(); - System.err.println(serverClass + " shut down."); - } - } catch (RuntimeException e) { - e.printStackTrace(System.err); - } - } - })); + .addShutdownHook(new Thread(getShutdownOperation())); } @VisibleForTesting @@ -180,6 +163,27 @@ public void run() { return builder.build(); } + @VisibleForTesting + /* package */ Runnable getShutdownOperation() { + return new Runnable() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + @SuppressWarnings("UseOfSystemOutOrSystemErr") + @Override + public void run() { + final String serverClass = GrpcContainer.this.getClass() + .getName(); + try { + if (!isShutdown()) { + System.err.println("Shutting down " + serverClass + " since JVM is shutting down..."); + shutdown(); + System.err.println(serverClass + " shut down."); + } + } catch (RuntimeException e) { + e.printStackTrace(System.err); + } + } + }; + } public static class Builder { diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java index 10949ac6158..67e8c2161a0 100644 --- a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -28,6 +28,7 @@ import org.junit.Test; import java.io.IOException; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -37,6 +38,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -107,6 +109,27 @@ public void add_and_remove_parameters_form_builder() { assertNotNull(container); } + @Test + public void add_shutdown_hook_to_runtime() throws NoSuchFieldException, IllegalAccessException, IOException { + final Class runtimeClass = Runtime.class; + // private static Runtime currentRuntime + final Field currentRuntimeValue = runtimeClass.getDeclaredField("currentRuntime"); + currentRuntimeValue.setAccessible(true); + final Runtime runtimeSpy = (Runtime) spy(currentRuntimeValue.get(null)); + currentRuntimeValue.set(null, runtimeSpy); + + final GrpcContainer container = spy(GrpcContainer.newBuilder() + .setPort(8080) + .build()); + container.addShutdownHook(); + verify(runtimeSpy).addShutdownHook(any(Thread.class)); + + container.start(); + container.getShutdownOperation() + .run(); + verify(container).shutdown(); + } + @Test public void throw_exception_if_started_already() throws IOException { grpcContainer.start(); From 371b5f3c52230087d770cd11aeb0face9405f195 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 14:45:17 +0300 Subject: [PATCH 345/361] Fix naming. --- .../java/org/spine3/server/transport/GrpcContainer.java | 4 ++-- .../org/spine3/server/transport/GrpcContainerShould.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java index f2edf0ccc03..0aff2550eee 100644 --- a/server/src/main/java/org/spine3/server/transport/GrpcContainer.java +++ b/server/src/main/java/org/spine3/server/transport/GrpcContainer.java @@ -150,7 +150,7 @@ public boolean isLive(BindableService service) { */ public void addShutdownHook() { Runtime.getRuntime() - .addShutdownHook(new Thread(getShutdownOperation())); + .addShutdownHook(new Thread(getOnShutdownCallback())); } @VisibleForTesting @@ -164,7 +164,7 @@ public void addShutdownHook() { } @VisibleForTesting - /* package */ Runnable getShutdownOperation() { + /* package */ Runnable getOnShutdownCallback() { return new Runnable() { // Use stderr here since the logger may have been reset by its JVM shutdown hook. @SuppressWarnings("UseOfSystemOutOrSystemErr") diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java index 67e8c2161a0..e8a3294ac5f 100644 --- a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -79,7 +79,7 @@ public void start_server() throws IOException { @SuppressWarnings("MagicNumber") @Test - public void add_and_remove_parameters_form_builder() { + public void add_and_remove_parameters_from_builder() { final GrpcContainer.Builder builder = GrpcContainer.newBuilder() .setPort(8080) .setPort(60); @@ -110,7 +110,7 @@ public void add_and_remove_parameters_form_builder() { } @Test - public void add_shutdown_hook_to_runtime() throws NoSuchFieldException, IllegalAccessException, IOException { + public void stop_properly_upon_application_shutdown() throws NoSuchFieldException, IllegalAccessException, IOException { final Class runtimeClass = Runtime.class; // private static Runtime currentRuntime final Field currentRuntimeValue = runtimeClass.getDeclaredField("currentRuntime"); @@ -125,7 +125,7 @@ public void add_shutdown_hook_to_runtime() throws NoSuchFieldException, IllegalA verify(runtimeSpy).addShutdownHook(any(Thread.class)); container.start(); - container.getShutdownOperation() + container.getOnShutdownCallback() .run(); verify(container).shutdown(); } From 7e97ee87d103deb690d9a57cca6b40c8907e73e0 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 14:49:03 +0300 Subject: [PATCH 346/361] Fix minor issues. --- .../org/spine3/server/storage/StandStorageShould.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/spine3/server/storage/StandStorageShould.java b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java index e84c1089d4a..3b762b228bf 100644 --- a/server/src/test/java/org/spine3/server/storage/StandStorageShould.java +++ b/server/src/test/java/org/spine3/server/storage/StandStorageShould.java @@ -25,6 +25,7 @@ import com.google.common.collect.Collections2; import com.google.protobuf.Any; import org.junit.Test; +import org.spine3.base.Identifiers; import org.spine3.protobuf.AnyPacker; import org.spine3.protobuf.Timestamps; import org.spine3.protobuf.TypeUrl; @@ -38,7 +39,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.UUID; import static org.spine3.test.Verify.assertContains; import static org.spine3.test.Verify.assertSize; @@ -50,13 +50,13 @@ public abstract class StandStorageShould { protected abstract StandStorage createStorage(); - protected static final Supplier> DEFAULT_ID_SUPPLIER = new Supplier>() { + protected static final Supplier> DEFAULT_ID_SUPPLIER + = new Supplier>() { @SuppressWarnings("unchecked") @Override public AggregateStateId get() { final ProjectId projectId = ProjectId.newBuilder() - .setId(UUID.randomUUID() - .toString()) + .setId(Identifiers.newUuid()) .build(); return AggregateStateId.of(projectId, TypeUrl.of(Project.class)); } From e88cb1df82d8fcfd117f6a8fe11456728ab5a4f5 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 15:10:31 +0300 Subject: [PATCH 347/361] Add detailed comment on signature of field accessed from reflect API. --- .../java/org/spine3/server/transport/GrpcContainerShould.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java index e8a3294ac5f..1a6bc54c7c5 100644 --- a/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java +++ b/server/src/test/java/org/spine3/server/transport/GrpcContainerShould.java @@ -112,7 +112,8 @@ public void add_and_remove_parameters_from_builder() { @Test public void stop_properly_upon_application_shutdown() throws NoSuchFieldException, IllegalAccessException, IOException { final Class runtimeClass = Runtime.class; - // private static Runtime currentRuntime + // Field signature: private static Runtime currentRuntime + // Origin class: {@code java.lang.Runtime}. final Field currentRuntimeValue = runtimeClass.getDeclaredField("currentRuntime"); currentRuntimeValue.setAccessible(true); final Runtime runtimeSpy = (Runtime) spy(currentRuntimeValue.get(null)); From 0d398e2e44b2b04e2fa1bebebf415c3928e19e6d Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 15:30:38 +0300 Subject: [PATCH 348/361] Use another multiline ternary operation formatting approach: `condition (\n) ? thenA (\n) : elseB`. --- .../java/org/spine3/protobuf/Timestamps.java | 6 +++--- .../main/java/org/spine3/protobuf/TypeUrl.java | 6 +++--- .../java/org/spine3/server/entity/Entity.java | 12 ++++++------ .../event/enrich/EventMessageEnricher.java | 6 +++--- .../event/enrich/ReferenceValidator.java | 6 +++--- .../server/stand/AggregateQueryProcessor.java | 18 +++++++++--------- .../spine3/server/storage/EventStorage.java | 13 ++++++------- .../server/validate/MessageFieldValidator.java | 6 +++--- .../server/validate/NumberFieldValidator.java | 12 ++++++------ 9 files changed, 42 insertions(+), 43 deletions(-) diff --git a/client/src/main/java/org/spine3/protobuf/Timestamps.java b/client/src/main/java/org/spine3/protobuf/Timestamps.java index bef01a178d8..e66d9c30840 100644 --- a/client/src/main/java/org/spine3/protobuf/Timestamps.java +++ b/client/src/main/java/org/spine3/protobuf/Timestamps.java @@ -203,9 +203,9 @@ public static int compare(@Nullable Timestamp t1, @Nullable Timestamp t2) { return 1; } int result = Long.compare(t1.getSeconds(), t2.getSeconds()); - result = (result == 0) ? - Integer.compare(t1.getNanos(), t2.getNanos()) : - result; + result = (result == 0) + ? Integer.compare(t1.getNanos(), t2.getNanos()) + : result; return result; } diff --git a/client/src/main/java/org/spine3/protobuf/TypeUrl.java b/client/src/main/java/org/spine3/protobuf/TypeUrl.java index 75bb0abbd25..f8eae45933e 100644 --- a/client/src/main/java/org/spine3/protobuf/TypeUrl.java +++ b/client/src/main/java/org/spine3/protobuf/TypeUrl.java @@ -122,9 +122,9 @@ public static TypeUrl of(EnumDescriptor descriptor) { @Internal public static TypeUrl of(String typeUrlOrName) { checkNotEmptyOrBlank(typeUrlOrName, "type URL or name"); - final TypeUrl typeUrl = isTypeUrl(typeUrlOrName) ? - ofTypeUrl(typeUrlOrName) : - ofTypeName(typeUrlOrName); + final TypeUrl typeUrl = isTypeUrl(typeUrlOrName) + ? ofTypeUrl(typeUrlOrName) + : ofTypeName(typeUrlOrName); return typeUrl; } diff --git a/server/src/main/java/org/spine3/server/entity/Entity.java b/server/src/main/java/org/spine3/server/entity/Entity.java index fc696e33b2b..e4f86da0029 100644 --- a/server/src/main/java/org/spine3/server/entity/Entity.java +++ b/server/src/main/java/org/spine3/server/entity/Entity.java @@ -143,9 +143,9 @@ private S createDefaultState() { */ @CheckReturnValue public S getState() { - final S result = state == null ? - getDefaultState() : - state; + final S result = state == null + ? getDefaultState() + : state; return result; } @@ -246,9 +246,9 @@ public I getId() { @CheckReturnValue @Nonnull public Timestamp whenModified() { - final Timestamp result = whenModified == null ? - Timestamp.getDefaultInstance() : - whenModified; + final Timestamp result = whenModified == null + ? Timestamp.getDefaultInstance() + : whenModified; return result; } diff --git a/server/src/main/java/org/spine3/server/event/enrich/EventMessageEnricher.java b/server/src/main/java/org/spine3/server/event/enrich/EventMessageEnricher.java index d80930e41e1..451eae0a3be 100644 --- a/server/src/main/java/org/spine3/server/event/enrich/EventMessageEnricher.java +++ b/server/src/main/java/org/spine3/server/event/enrich/EventMessageEnricher.java @@ -127,9 +127,9 @@ private void setFields(Message.Builder builder, S eventMsg) { private Object getSrcFieldValue(FieldDescriptor srcField, S eventMsg) { final boolean isContextField = srcField.getContainingType().equals(EventContext.getDescriptor()); - final Object result = isContextField ? - getContext().getField(srcField) : - eventMsg.getField(srcField); + final Object result = isContextField + ? getContext().getField(srcField) + : eventMsg.getField(srcField); return result; } } diff --git a/server/src/main/java/org/spine3/server/event/enrich/ReferenceValidator.java b/server/src/main/java/org/spine3/server/event/enrich/ReferenceValidator.java index 79d88f044d5..eb25531a569 100644 --- a/server/src/main/java/org/spine3/server/event/enrich/ReferenceValidator.java +++ b/server/src/main/java/org/spine3/server/event/enrich/ReferenceValidator.java @@ -113,9 +113,9 @@ private static FieldDescriptor findField(String fieldNameFull, Descriptor srcMes * if the field name contains {@link ReferenceValidator#CONTEXT_REFERENCE}. */ private Descriptor getSrcMessage(String fieldName) { - final Descriptor msg = fieldName.contains(CONTEXT_REFERENCE) ? - EventContext.getDescriptor() : - eventDescriptor; + final Descriptor msg = fieldName.contains(CONTEXT_REFERENCE) + ? EventContext.getDescriptor() + : eventDescriptor; return msg; } diff --git a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java index 43a3981d92f..f2dc30c692c 100644 --- a/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java +++ b/server/src/main/java/org/spine3/server/stand/AggregateQueryProcessor.java @@ -82,9 +82,9 @@ public ImmutableCollection process(Query query) { final boolean shouldApplyFieldMask = !fieldMask.getPathsList() .isEmpty(); if (target.getIncludeAll()) { - stateRecords = shouldApplyFieldMask ? - standStorage.readAllByType(type, fieldMask) : - standStorage.readAllByType(type); + stateRecords = shouldApplyFieldMask + ? standStorage.readAllByType(type, fieldMask) + : standStorage.readAllByType(type); } else { stateRecords = doFetchWithFilters(target, fieldMask); } @@ -115,9 +115,9 @@ private ImmutableCollection doFetchWithFilters(Target targe // may be more effective, as bulk reading implies additional time and performance expenses. final AggregateStateId singleId = stateIds.iterator() .next(); - final EntityStorageRecord singleResult = shouldApplyFieldMask ? - standStorage.read(singleId, fieldMask) : - standStorage.read(singleId); + final EntityStorageRecord singleResult = shouldApplyFieldMask + ? standStorage.read(singleId, fieldMask) + : standStorage.read(singleId); result = ImmutableList.of(singleResult); } else { result = handleBulkRead(stateIds, fieldMask, shouldApplyFieldMask); @@ -132,9 +132,9 @@ private ImmutableCollection handleBulkRead(Collection result; - final Iterable bulkReadResults = applyFieldMask ? - standStorage.readMultiple(stateIds, fieldMask) : - standStorage.readMultiple(stateIds); + final Iterable bulkReadResults = applyFieldMask + ? standStorage.readMultiple(stateIds, fieldMask) + : standStorage.readMultiple(stateIds); result = FluentIterable.from(bulkReadResults) .filter(new Predicate() { @Override diff --git a/server/src/main/java/org/spine3/server/storage/EventStorage.java b/server/src/main/java/org/spine3/server/storage/EventStorage.java index 8af26613f19..60cbf907ff3 100644 --- a/server/src/main/java/org/spine3/server/storage/EventStorage.java +++ b/server/src/main/java/org/spine3/server/storage/EventStorage.java @@ -30,7 +30,6 @@ import com.google.protobuf.Any; import com.google.protobuf.Message; import com.google.protobuf.Timestamp; -import org.spine3.Internal; import org.spine3.SPI; import org.spine3.base.Event; import org.spine3.base.EventContext; @@ -235,13 +234,13 @@ private static class MatchFilter implements Predicate { private MatchFilter(EventFilter filter) { final String eventType = filter.getEventType(); - this.eventTypeUrl = eventType.isEmpty() ? - null : - TypeUrl.of(eventType); + this.eventTypeUrl = eventType.isEmpty() + ? null + : TypeUrl.of(eventType); final List aggregateIdList = filter.getAggregateIdList(); - this.aggregateIds = aggregateIdList.isEmpty() ? - null : - aggregateIdList; + this.aggregateIds = aggregateIdList.isEmpty() + ? null + : aggregateIdList; } @Override diff --git a/server/src/main/java/org/spine3/server/validate/MessageFieldValidator.java b/server/src/main/java/org/spine3/server/validate/MessageFieldValidator.java index c6240930a1a..be1cc396e74 100644 --- a/server/src/main/java/org/spine3/server/validate/MessageFieldValidator.java +++ b/server/src/main/java/org/spine3/server/validate/MessageFieldValidator.java @@ -132,9 +132,9 @@ private void validateTimestamps() { * @return {@code true} if the time is valid according to {@code whenExpected} parameter, {@code false} otherwise */ private static boolean isTimeInvalid(Timestamp timeToCheck, Time whenExpected, Timestamp now) { - final boolean isValid = (whenExpected == FUTURE) ? - isLaterThan(timeToCheck, /*than*/ now) : - isLaterThan(now, /*than*/ timeToCheck); + final boolean isValid = (whenExpected == FUTURE) + ? isLaterThan(timeToCheck, /*than*/ now) + : isLaterThan(now, /*than*/ timeToCheck); final boolean isInvalid = !isValid; return isInvalid; } diff --git a/server/src/main/java/org/spine3/server/validate/NumberFieldValidator.java b/server/src/main/java/org/spine3/server/validate/NumberFieldValidator.java index db518a6013a..f0247aca674 100644 --- a/server/src/main/java/org/spine3/server/validate/NumberFieldValidator.java +++ b/server/src/main/java/org/spine3/server/validate/NumberFieldValidator.java @@ -131,9 +131,9 @@ private boolean notFitToDecimalMin(V value) { } final V min = toNumber(minAsString); final int comparisonResult = value.compareTo(min); - final boolean fits = isMinDecimalInclusive ? - comparisonResult >= 0 : - comparisonResult > 0; + final boolean fits = isMinDecimalInclusive + ? comparisonResult >= 0 + : comparisonResult > 0; final boolean notFit = !fits; return notFit; } @@ -144,9 +144,9 @@ private boolean notFitToDecimalMax(V value) { return false; } final V max = toNumber(maxAsString); - final boolean fits = isMaxDecimalInclusive ? - value.compareTo(max) <= 0 : - value.compareTo(max) < 0; + final boolean fits = isMaxDecimalInclusive + ? value.compareTo(max) <= 0 + : value.compareTo(max) < 0; final boolean notFit = !fits; return notFit; } From a42031a5c224fd005d7190168c7c0de41e2d1f90 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 15:33:46 +0300 Subject: [PATCH 349/361] Move the `checkIsPositive` to the group of related `Precondition` checks; do not ignore the result of this method and use the timestamp returned. --- .../main/java/org/spine3/server/storage/AggregateStorage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java index 91620e055b0..63496eeed29 100644 --- a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java +++ b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java @@ -144,10 +144,11 @@ public void write(I aggregateId, Snapshot snapshot) { checkNotClosed(); checkNotNull(aggregateId); checkNotNull(snapshot); + final Timestamp timestamp = checkIsPositive(snapshot.getTimestamp(), "Snapshot timestamp"); final AggregateStorageRecord record = AggregateStorageRecord.newBuilder() - .setTimestamp(checkIsPositive(snapshot.getTimestamp(), "Snapshot timestamp")) + .setTimestamp(timestamp) .setEventType(SNAPSHOT_TYPE_NAME) .setEventId("") // No event ID for snapshots because it's not a domain event. .setVersion(snapshot.getVersion()) From 37eaef217779986a315b7b33cf515eaefeaa9526 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 15:35:23 +0300 Subject: [PATCH 350/361] Rename `checkIsPositive` to `checkPositive` for timestamp Preconditions-style validation. --- .../main/java/org/spine3/base/Commands.java | 4 +-- .../java/org/spine3/validate/Validate.java | 2 +- .../org/spine3/validate/ValidateShould.java | 32 +++++++++---------- .../spine3/server/aggregate/Aggregate.java | 4 +-- .../server/command/CommandValidator.java | 4 +-- .../server/storage/AggregateStorage.java | 6 ++-- .../spine3/server/storage/EventStorage.java | 4 +-- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/client/src/main/java/org/spine3/base/Commands.java b/client/src/main/java/org/spine3/base/Commands.java index 0af4c384d34..d29c1cfc3e0 100644 --- a/client/src/main/java/org/spine3/base/Commands.java +++ b/client/src/main/java/org/spine3/base/Commands.java @@ -47,7 +47,7 @@ import static org.spine3.base.CommandContext.newBuilder; import static org.spine3.base.Identifiers.idToString; import static org.spine3.protobuf.Timestamps.getCurrentTime; -import static org.spine3.validate.Validate.checkIsPositive; +import static org.spine3.validate.Validate.checkPositive; import static org.spine3.validate.Validate.checkNotEmptyOrBlank; import static org.spine3.validate.Validate.isNotDefault; @@ -287,7 +287,7 @@ public static Command setSchedulingTime(Command command, Timestamp schedulingTim */ @Internal public static Command setSchedule(Command command, Duration delay, Timestamp schedulingTime) { - checkIsPositive(schedulingTime, "command scheduling time"); + checkPositive(schedulingTime, "command scheduling time"); final CommandContext context = command.getContext(); final CommandContext.Schedule scheduleUpdated = context.getSchedule() .toBuilder() diff --git a/client/src/main/java/org/spine3/validate/Validate.java b/client/src/main/java/org/spine3/validate/Validate.java index f5a2a66e487..325edb680f0 100644 --- a/client/src/main/java/org/spine3/validate/Validate.java +++ b/client/src/main/java/org/spine3/validate/Validate.java @@ -187,7 +187,7 @@ public static String checkNotEmptyOrBlank(String stringToCheck, String fieldName * @return the passed timestamp * @throws IllegalArgumentException if any of the requirements are not met */ - public static Timestamp checkIsPositive(Timestamp timestamp, String nameToLog) { + public static Timestamp checkPositive(Timestamp timestamp, String nameToLog) { checkNotNull(timestamp, nameToLog + " is null."); checkArgument(timestamp.getSeconds() > 0, nameToLog + " must have a positive number of seconds."); checkArgument(timestamp.getNanos() >= 0, nameToLog + " must not have a negative number of nanoseconds."); diff --git a/client/src/test/java/org/spine3/validate/ValidateShould.java b/client/src/test/java/org/spine3/validate/ValidateShould.java index acbf3aea744..1fd88bb7c2a 100644 --- a/client/src/test/java/org/spine3/validate/ValidateShould.java +++ b/client/src/test/java/org/spine3/validate/ValidateShould.java @@ -81,34 +81,34 @@ public void return_non_default_value_on_check() { @Test(expected = IllegalArgumentException.class) public void throw_exception_if_timestamp_seconds_value_is_zero() { - Validate.checkIsPositive(Timestamp.newBuilder() - .setSeconds(0) - .setNanos(5) - .build(), ""); + Validate.checkPositive(Timestamp.newBuilder() + .setSeconds(0) + .setNanos(5) + .build(), ""); } @Test(expected = IllegalArgumentException.class) public void throw_exception_if_timestamp_seconds_value_is_negative() { - Validate.checkIsPositive(Timestamp.newBuilder() - .setSeconds(-5) - .setNanos(5) - .build(), ""); + Validate.checkPositive(Timestamp.newBuilder() + .setSeconds(-5) + .setNanos(5) + .build(), ""); } @Test(expected = IllegalArgumentException.class) public void throw_exception_if_timestamp_nanos_value_is_negative() { - Validate.checkIsPositive(Timestamp.newBuilder() - .setSeconds(5) - .setNanos(-5) - .build(), ""); + Validate.checkPositive(Timestamp.newBuilder() + .setSeconds(5) + .setNanos(-5) + .build(), ""); } @Test public void do_not_throw_exception_if_timestamp_seconds_and_nanos_are_positive() { - Validate.checkIsPositive(Timestamp.newBuilder() - .setSeconds(5) - .setNanos(5) - .build(), ""); + Validate.checkPositive(Timestamp.newBuilder() + .setSeconds(5) + .setNanos(5) + .build(), ""); } @Test(expected = NullPointerException.class) diff --git a/server/src/main/java/org/spine3/server/aggregate/Aggregate.java b/server/src/main/java/org/spine3/server/aggregate/Aggregate.java index 0026b573643..9f7714afc81 100644 --- a/server/src/main/java/org/spine3/server/aggregate/Aggregate.java +++ b/server/src/main/java/org/spine3/server/aggregate/Aggregate.java @@ -52,7 +52,7 @@ import static org.spine3.protobuf.Timestamps.getCurrentTime; import static org.spine3.server.reflect.Classes.getHandledMessageClasses; import static org.spine3.util.Exceptions.wrappedCause; -import static org.spine3.validate.Validate.checkIsPositive; +import static org.spine3.validate.Validate.checkPositive; /** * Abstract base for aggregates. @@ -430,7 +430,7 @@ private void applyEventOrSnapshot(Message eventOrSnapshot) throws InvocationTarg */ @CheckReturnValue protected EventContext createEventContext(Message event, CommandContext commandContext, Timestamp whenModified) { - checkIsPositive(whenModified, "Aggregate modification time"); + checkPositive(whenModified, "Aggregate modification time"); final EventId eventId = generateId(); final EventContext.Builder builder = EventContext.newBuilder() .setEventId(eventId) diff --git a/server/src/main/java/org/spine3/server/command/CommandValidator.java b/server/src/main/java/org/spine3/server/command/CommandValidator.java index 3bd51bbf4ad..7d04a035ce7 100644 --- a/server/src/main/java/org/spine3/server/command/CommandValidator.java +++ b/server/src/main/java/org/spine3/server/command/CommandValidator.java @@ -35,7 +35,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static org.spine3.base.Identifiers.EMPTY_ID; import static org.spine3.base.Identifiers.idToString; -import static org.spine3.validate.Validate.checkIsPositive; +import static org.spine3.validate.Validate.checkPositive; import static org.spine3.validate.Validate.checkValid; import static org.spine3.validate.Validate.isDefault; @@ -121,7 +121,7 @@ public static void checkCommand(Command command) { checkArgument(command.hasContext(), "Command context must be set."); final CommandContext context = command.getContext(); checkValid(context.getCommandId()); - checkIsPositive(context.getTimestamp(), "Command time"); + checkPositive(context.getTimestamp(), "Command time"); final Message commandMessage = Commands.getMessage(command); final Optional targetId = GetTargetIdFromCommand.asOptional(commandMessage); if (targetId.isPresent()) { // else - consider the command is not for an entity diff --git a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java index 63496eeed29..19b3609c860 100644 --- a/server/src/main/java/org/spine3/server/storage/AggregateStorage.java +++ b/server/src/main/java/org/spine3/server/storage/AggregateStorage.java @@ -37,7 +37,7 @@ import static com.google.protobuf.TextFormat.shortDebugString; import static org.spine3.base.Identifiers.idToString; import static org.spine3.protobuf.TypeUrl.ofEnclosed; -import static org.spine3.validate.Validate.checkIsPositive; +import static org.spine3.validate.Validate.checkPositive; import static org.spine3.validate.Validate.checkNotEmptyOrBlank; /** @@ -144,7 +144,7 @@ public void write(I aggregateId, Snapshot snapshot) { checkNotClosed(); checkNotNull(aggregateId); checkNotNull(snapshot); - final Timestamp timestamp = checkIsPositive(snapshot.getTimestamp(), "Snapshot timestamp"); + final Timestamp timestamp = checkPositive(snapshot.getTimestamp(), "Snapshot timestamp"); final AggregateStorageRecord record = AggregateStorageRecord.newBuilder() @@ -192,7 +192,7 @@ private static AggregateStorageRecord toStorageRecord(Event event) { final String eventType = ofEnclosed(eventMsg).getTypeName(); checkNotEmptyOrBlank(eventType, "Event type"); - final Timestamp timestamp = checkIsPositive(context.getTimestamp(), "Event time"); + final Timestamp timestamp = checkPositive(context.getTimestamp(), "Event time"); final AggregateStorageRecord.Builder builder = AggregateStorageRecord.newBuilder() .setEvent(event) diff --git a/server/src/main/java/org/spine3/server/storage/EventStorage.java b/server/src/main/java/org/spine3/server/storage/EventStorage.java index 60cbf907ff3..b7a06b1536a 100644 --- a/server/src/main/java/org/spine3/server/storage/EventStorage.java +++ b/server/src/main/java/org/spine3/server/storage/EventStorage.java @@ -47,7 +47,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static org.spine3.base.Identifiers.idToString; import static org.spine3.protobuf.TypeUrl.ofEnclosed; -import static org.spine3.validate.Validate.checkIsPositive; +import static org.spine3.validate.Validate.checkPositive; import static org.spine3.validate.Validate.checkNotEmptyOrBlank; import static org.spine3.validate.Validate.checkValid; @@ -154,7 +154,7 @@ protected static Iterator toEventIterator(Iterator re checkNotEmptyOrBlank(eventType, "event type"); final String producerId = idToString(Events.getProducer(context)); checkNotEmptyOrBlank(producerId, "producer ID"); - final Timestamp timestamp = checkIsPositive(context.getTimestamp(), "event time"); + final Timestamp timestamp = checkPositive(context.getTimestamp(), "event time"); final EventStorageRecord.Builder builder = EventStorageRecord.newBuilder() .setTimestamp(timestamp) .setEventType(eventType) From 59428158e8a70dc0ecc5a348d6396af1b6146ebb Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 15:38:16 +0300 Subject: [PATCH 351/361] Remove an empty line. --- server/src/main/java/org/spine3/server/entity/FieldMasks.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 2c1d352888a..01e355aa05a 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -198,7 +198,6 @@ private static Class getBuilderForType(TypeUrl ty } return builderClass; - } private static Logger log() { From b34a4cac8ce3605e7078d5820880f0e4331bc380 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 15:53:41 +0300 Subject: [PATCH 352/361] Resolve `FieldMasks` issues: * avoid comment-based warning suppressions; * avoid method duplication in the public API. * improve JavaDoc formatting and phrasing. --- .../server/entity/EntityRepository.java | 2 +- .../org/spine3/server/entity/FieldMasks.java | 52 +++++++------------ .../spine3/server/storage/RecordStorage.java | 2 +- .../storage/memory/InMemoryRecordStorage.java | 6 ++- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/EntityRepository.java b/server/src/main/java/org/spine3/server/entity/EntityRepository.java index b10766a350d..cb888dc0ecf 100644 --- a/server/src/main/java/org/spine3/server/entity/EntityRepository.java +++ b/server/src/main/java/org/spine3/server/entity/EntityRepository.java @@ -249,7 +249,7 @@ private E toEntity(I id, EntityStorageRecord record) { private E toEntity(I id, EntityStorageRecord record, FieldMask fieldMask) { final E entity = create(id); @SuppressWarnings("unchecked") - final M state = (M) FieldMasks.applyIfValid(fieldMask, unpack(record.getState()), getEntityStateType()); + final M state = (M) FieldMasks.applyMask(fieldMask, unpack(record.getState()), getEntityStateType()); entity.setState(state, record.getVersion(), record.getWhenModified()); return entity; } diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 01e355aa05a..60de15d8791 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -65,14 +65,13 @@ private FieldMasks() { * * @param mask {@code FieldMask} to apply to each item of the input {@link Collection}. * @param messages {@link Message}s to filter. - * @param type Type of the {@link Message}s. + * @param type type of the {@link Message}s. * @return messages with the {@code FieldMask} applied */ @Nonnull - public static Collection applyMask( - FieldMask mask, - Collection messages, - TypeUrl type) { + public static Collection applyMask(FieldMask mask, + Collection messages, + TypeUrl type) { final List filtered = new LinkedList<>(); final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(type); @@ -101,17 +100,26 @@ public static Collection apply } /** - * Applies the given {@code FieldMask} to a single {@link Message}. + * Applies the {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. * *

        In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and * do not affect the execution result. * - * @param mask {@code FieldMask} instance to apply. - * @param message The {@link Message} to apply given {@code FieldMask} to. - * @param type Type of the {@link Message}. - * @return A {@link Message} of the same type as the given one with only selected fields. + * @param mask the {@code FieldMask} to apply. + * @param message the {@link Message} to apply given mask to. + * @param typeUrl type of given {@link Message}. + * @return the message of the same type as the given one with only selected fields if the {@code mask} is valid, + * original message otherwise. */ - public static M applyMask(FieldMask mask, M message, TypeUrl type) { + public static M applyMask(FieldMask mask, M message, TypeUrl typeUrl) { + if (!mask.getPathsList() + .isEmpty()) { + return doApply(mask, message, typeUrl); + } + return message; + } + + private static M doApply(FieldMask mask, M message, TypeUrl type) { final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(type); @@ -134,26 +142,6 @@ public static M applyMask(FieldMa } } - /** - * Applies the {@code FieldMask} to the given {@link Message} the {@code mask} parameter is valid. - * - *

        In case the {@code FieldMask} instance contains invalid field declarations, they are ignored and - * do not affect the execution result. - * - * @param mask The {@code FieldMask} to apply. - * @param message The {@link Message} to apply given mask to. - * @param typeUrl Type of given {@link Message}. - * @return A {@link Message} of the same type as the given one with only selected fields - * if the {@code mask} is valid, {@code message} itself otherwise. - */ - public static M applyIfValid(FieldMask mask, M message, TypeUrl typeUrl) { - if (!mask.getPathsList() - .isEmpty()) { - return applyMask(mask, message, typeUrl); - } - return message; - } - private static M messageForFilter( ProtocolStringList filter, Constructor builderConstructor, Message wholeMessage) @@ -174,13 +162,13 @@ private static M messageForFilter return result; } + @SuppressWarnings("unchecked") // We assume that {@code KnownTypes#getClassName(TypeUrl) works properly. @Nullable private static Class getBuilderForType(TypeUrl typeUrl) { Class builderClass; final String className = KnownTypes.getClassName(typeUrl) .value(); try { - //noinspection unchecked builderClass = (Class) Class.forName(className) .getClasses()[0]; } catch (ClassNotFoundException e) { diff --git a/server/src/main/java/org/spine3/server/storage/RecordStorage.java b/server/src/main/java/org/spine3/server/storage/RecordStorage.java index 7a19721d307..c274d6bd744 100644 --- a/server/src/main/java/org/spine3/server/storage/RecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/RecordStorage.java @@ -81,7 +81,7 @@ public EntityStorageRecord read(I id, FieldMask fieldMask) { final TypeUrl type = TypeUrl.of(state.getTypeUrl()); final Message stateAsMessage = AnyPacker.unpack(state); - final Message maskedState = FieldMasks.applyIfValid(fieldMask, stateAsMessage, type); + final Message maskedState = FieldMasks.applyMask(fieldMask, stateAsMessage, type); final Any packedState = AnyPacker.pack(maskedState); builder.setState(packedState); diff --git a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java index 236ef61205d..91ad9240b22 100644 --- a/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java +++ b/server/src/main/java/org/spine3/server/storage/memory/InMemoryRecordStorage.java @@ -71,7 +71,9 @@ protected Iterable readMultipleRecords(final Iterable gi return result; } - private EntityStorageRecord findAndApplyFieldMask(Map storage, I givenId, FieldMask fieldMask) { + private EntityStorageRecord findAndApplyFieldMask(Map storage, + I givenId, + FieldMask fieldMask) { EntityStorageRecord matchingResult = null; for (I recordId : storage.keySet()) { if (recordId.equals(givenId)) { @@ -80,7 +82,7 @@ private EntityStorageRecord findAndApplyFieldMask(Map st final Any state = matchingRecord.getState(); final TypeUrl typeUrl = TypeUrl.of(state.getTypeUrl()); final Message wholeState = AnyPacker.unpack(state); - final Message maskedState = FieldMasks.applyIfValid(fieldMask, wholeState, typeUrl); + final Message maskedState = FieldMasks.applyMask(fieldMask, wholeState, typeUrl); final Any processed = AnyPacker.pack(maskedState); matchingRecord.setState(processed); From 5ac35fb1550b745e3e1457f4d1e95e6aaafc1853 Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 16:07:16 +0300 Subject: [PATCH 353/361] Remove an empty line. --- server/src/test/java/org/spine3/server/CommandServiceShould.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/CommandServiceShould.java b/server/src/test/java/org/spine3/server/CommandServiceShould.java index bc5962c80a1..8d695d0ee0e 100644 --- a/server/src/test/java/org/spine3/server/CommandServiceShould.java +++ b/server/src/test/java/org/spine3/server/CommandServiceShould.java @@ -148,7 +148,6 @@ public void deploy_to_grpc_container() throws IOException { grpcContainer.shutdown(); } } - } /* From 58c1cfd4eee81eb641c14fb874b07012978c7ddd Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 16:08:16 +0300 Subject: [PATCH 354/361] Remove an empty line. --- server/src/test/java/org/spine3/server/stand/StandShould.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/stand/StandShould.java b/server/src/test/java/org/spine3/server/stand/StandShould.java index 91836987714..297629768ef 100644 --- a/server/src/test/java/org/spine3/server/stand/StandShould.java +++ b/server/src/test/java/org/spine3/server/stand/StandShould.java @@ -113,7 +113,6 @@ public void initialize_with_empty_builder() { .isEmpty()); assertTrue("Exposed aggregate types must be empty after the initialization", stand.getExposedAggregateTypes() .isEmpty()); - } @Test From 1bea96b5832cefef2354a83976ef0868e33e44da Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 16:14:23 +0300 Subject: [PATCH 355/361] Simplify the `Stand#update()` signature, mark it as `@VisibleForTesting` and explain why. --- server/src/main/java/org/spine3/server/stand/Stand.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 2911c9f97c6..88223602236 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -19,6 +19,7 @@ */ package org.spine3.server.stand; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -134,7 +135,8 @@ public static Builder newBuilder() { * @param id the entity identifier * @param entityState the entity state */ - public void update(final Object id, final Any entityState) { + @VisibleForTesting // Used in the integration tests from other packages to trigger the entity updates. + public void update(Object id, Any entityState) { final String typeUrlString = entityState.getTypeUrl(); final TypeUrl typeUrl = TypeUrl.of(typeUrlString); From aede0ca30de815ee5bc55dbf8f9f4891adc7eb8f Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 16:19:38 +0300 Subject: [PATCH 356/361] Expose`Stand#update()` as package-local method; use StandFunnel API for integration testing instead. --- server/src/main/java/org/spine3/server/stand/Stand.java | 4 +--- .../java/org/spine3/server/SubscriptionServiceShould.java | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/spine3/server/stand/Stand.java b/server/src/main/java/org/spine3/server/stand/Stand.java index 88223602236..bdd44151319 100644 --- a/server/src/main/java/org/spine3/server/stand/Stand.java +++ b/server/src/main/java/org/spine3/server/stand/Stand.java @@ -19,7 +19,6 @@ */ package org.spine3.server.stand; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -135,8 +134,7 @@ public static Builder newBuilder() { * @param id the entity identifier * @param entityState the entity state */ - @VisibleForTesting // Used in the integration tests from other packages to trigger the entity updates. - public void update(Object id, Any entityState) { + /* package */ void update(Object id, Any entityState) { final String typeUrlString = entityState.getTypeUrl(); final TypeUrl typeUrl = TypeUrl.of(typeUrlString); diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index d7a35ec628d..59420012ab5 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -187,8 +187,8 @@ public void activate_subscription() { final Message projectState = Project.newBuilder() .setId(projectId) .build(); - boundedContext.getStand() - .update(projectId, AnyPacker.pack(projectState)); + boundedContext.getStandFunnel() + .post(projectId, AnyPacker.pack(projectState)); // isCompleted set to false since we don't expect activationObserver::onCompleted to be called. activationObserver.verifyState(false); @@ -226,8 +226,8 @@ public void cancel_subscription_on_topic() { final Message projectState = Project.newBuilder() .setId(projectId) .build(); - boundedContext.getStand() - .update(projectId, AnyPacker.pack(projectState)); + boundedContext.getStandFunnel() + .post(projectId, AnyPacker.pack(projectState)); // The update must not be handled by the observer verify(activateSubscription, never()).onNext(any(SubscriptionUpdate.class)); From 1689163e05f7f16c0e9d548e23e9ee4939ee625c Mon Sep 17 00:00:00 2001 From: Alex Tymchenko Date: Wed, 12 Oct 2016 16:23:22 +0300 Subject: [PATCH 357/361] Do not call `onCompleted()` after `onError()`. --- server/src/main/java/org/spine3/server/QueryService.java | 1 - server/src/test/java/org/spine3/server/QueryServiceShould.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/QueryService.java b/server/src/main/java/org/spine3/server/QueryService.java index b0cb1364c49..3111f110332 100644 --- a/server/src/main/java/org/spine3/server/QueryService.java +++ b/server/src/main/java/org/spine3/server/QueryService.java @@ -69,7 +69,6 @@ public void read(Query query, StreamObserver responseObserver) { } catch (@SuppressWarnings("OverlyBroadCatchBlock") Exception e) { log().error("Error processing query", e); responseObserver.onError(e); - responseObserver.onCompleted(); } } diff --git a/server/src/test/java/org/spine3/server/QueryServiceShould.java b/server/src/test/java/org/spine3/server/QueryServiceShould.java index 37e208b7c73..66fc3611e7a 100644 --- a/server/src/test/java/org/spine3/server/QueryServiceShould.java +++ b/server/src/test/java/org/spine3/server/QueryServiceShould.java @@ -40,6 +40,7 @@ import java.util.Set; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -152,7 +153,7 @@ private static void checkOkResponse(TestQueryResponseObserver responseObserver) private static void checkFailureResponse(TestQueryResponseObserver responseObserver) { final QueryResponse responseHandled = responseObserver.getResponseHandled(); assertNull(responseHandled); - assertTrue(responseObserver.isCompleted()); + assertFalse(responseObserver.isCompleted()); assertNotNull(responseObserver.getThrowable()); } From 4e3b72b6a3e0b8ca8e63f9d207a00f36d0dbf4b0 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 17:35:30 +0300 Subject: [PATCH 358/361] Remove redundant check and fix typo. --- server/src/main/java/org/spine3/server/entity/FieldMasks.java | 2 +- .../test/java/org/spine3/server/entity/FieldMasksShould.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/spine3/server/entity/FieldMasks.java b/server/src/main/java/org/spine3/server/entity/FieldMasks.java index 60de15d8791..3a1a8076d2a 100644 --- a/server/src/main/java/org/spine3/server/entity/FieldMasks.java +++ b/server/src/main/java/org/spine3/server/entity/FieldMasks.java @@ -123,7 +123,7 @@ private static M doApply(FieldMas final ProtocolStringList filter = mask.getPathsList(); final Class builderClass = getBuilderForType(type); - if (filter.isEmpty() || builderClass == null) { + if (builderClass == null) { return message; } diff --git a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java index 6b8fbdbacef..429f4fabe91 100644 --- a/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java +++ b/server/src/test/java/org/spine3/server/entity/FieldMasksShould.java @@ -139,7 +139,7 @@ public void apply_only_non_empty_mask_to_collection() { } @Test(expected = IllegalArgumentException.class) - public void fail_to_mask_message_if_passed_type_dees_not_match() { + public void fail_to_mask_message_if_passed_type_does_not_match() { final FieldMask mask = Given.fieldMask(Project.ID_FIELD_NUMBER); final Project origin = Given.newProject("some-string"); From 0dae52ecc8eaabfc255739544a68ea409675e513 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 18:19:17 +0300 Subject: [PATCH 359/361] Add exception handling test for SubscriptionService#subscribe. --- .../server/SubscriptionServiceShould.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index 59420012ab5..a5851bd2de3 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -45,6 +45,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.spine3.test.Verify.assertInstanceOf; import static org.spine3.test.Verify.assertSize; import static org.spine3.testdata.TestBoundedContextFactory.newBoundedContext; @@ -158,6 +159,22 @@ public void subscribe_to_topic() { assertTrue(observer.isCompleted); } + @Test + public void handle_subscription_process_exceptions_and_call_observer_error_callback() { + final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + final MemoizeStreamObserver observer = new MemoizeStreamObserver<>(); + // Causes NPE + subscriptionService.subscribe(null, observer); + assertNull(observer.streamFlowValue); + assertFalse(observer.isCompleted); + assertNotNull(observer.throwable); + assertInstanceOf(NullPointerException.class, observer.throwable); + } + @Test public void activate_subscription() { final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); From d78ea1b47169152805b5c86c64429b3889f75c14 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 18:22:29 +0300 Subject: [PATCH 360/361] Add exception handling test for SubscriptionService#activate. --- .../spine3/server/SubscriptionServiceShould.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index a5851bd2de3..6e04c8e57f9 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -211,6 +211,22 @@ public void activate_subscription() { activationObserver.verifyState(false); } + @Test + public void handle_activation_process_exceptions_and_call_observer_error_callback() { + final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + final MemoizeStreamObserver observer = new MemoizeStreamObserver<>(); + // Causes NPE + subscriptionService.activate(null, observer); + assertNull(observer.streamFlowValue); + assertFalse(observer.isCompleted); + assertNotNull(observer.throwable); + assertInstanceOf(NullPointerException.class, observer.throwable); + } + @Test public void cancel_subscription_on_topic() { final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); From cd221cd1128f4f0efb836d84b88298fdba053552 Mon Sep 17 00:00:00 2001 From: Dmytro Dashenkov Date: Wed, 12 Oct 2016 18:31:44 +0300 Subject: [PATCH 361/361] Add exception handling test for SubscriptionService#cancel. --- .../server/SubscriptionServiceShould.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java index 6e04c8e57f9..0dface7177a 100644 --- a/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java +++ b/server/src/test/java/org/spine3/server/SubscriptionServiceShould.java @@ -187,7 +187,6 @@ public void activate_subscription() { final Topic topic = Topic.newBuilder() .setTarget(target) .build(); - // Subscribe on the topic final MemoizeStreamObserver subscriptionObserver = new MemoizeStreamObserver<>(); subscriptionService.subscribe(topic, subscriptionObserver); @@ -267,6 +266,36 @@ public void cancel_subscription_on_topic() { verify(activateSubscription, never()).onCompleted(); } + @Test + public void handle_cancellation_process_exceptions_and_call_observer_error_callback() { + final BoundedContext boundedContext = setupBoundedContextForAggregateRepo(); + + final SubscriptionService subscriptionService = SubscriptionService.newBuilder() + .addBoundedContext(boundedContext) + .build(); + final Target target = getProjectQueryTarget(); + final Topic topic = Topic.newBuilder() + .setTarget(target) + .build(); + final MemoizeStreamObserver subscriptionObserver = new MemoizeStreamObserver<>(); + subscriptionService.subscribe(topic, subscriptionObserver); + + final String failureMessage = "Execution breaking exception"; + final MemoizeStreamObserver observer = new MemoizeStreamObserver() { + @Override + public void onNext(Response value) { + super.onNext(value); + throw new RuntimeException(failureMessage); + } + }; + subscriptionService.cancel(subscriptionObserver.streamFlowValue, observer); + assertNotNull(observer.streamFlowValue); + assertFalse(observer.isCompleted); + assertNotNull(observer.throwable); + assertInstanceOf(RuntimeException.class, observer.throwable); + assertEquals(observer.throwable.getMessage(), failureMessage); + } + private static BoundedContext setupBoundedContextForAggregateRepo() { final Stand stand = Stand.newBuilder() .build();