Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion server/src/main/java/io/spine/server/GrpcContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@
import io.grpc.ServerBuilder;
import io.grpc.ServerServiceDefinition;
import io.grpc.inprocess.InProcessServerBuilder;
import io.spine.annotation.Experimental;
import io.spine.client.ConnectionConstants;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.io.IOException;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Function;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static io.spine.server.GrpcContainer.ConfigureServer.doNothing;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;

Expand All @@ -70,6 +73,7 @@ public final class GrpcContainer {
private final @Nullable Integer port;
private final @Nullable String serverName;
private final ImmutableSet<ServerServiceDefinition> services;
private final ConfigureServer configureServer;

private @Nullable Server grpcServer;

Expand Down Expand Up @@ -106,6 +110,9 @@ private GrpcContainer(Builder builder) {
this.port = builder.port().orElse(null);
this.serverName = builder.serverName().orElse(null);
this.services = builder.services();
this.configureServer = builder.configureServer == null
? doNothing()
: builder.configureServer;
}

/**
Expand Down Expand Up @@ -284,6 +291,7 @@ private Server createGrpcServer(@Nullable Executor executor) {
for (ServerServiceDefinition service : services) {
builder.addService(service);
}
builder = configureServer.apply(builder);
return builder.build();
}

Expand Down Expand Up @@ -324,7 +332,8 @@ private static ServerBuilder<?> builderAtPort(Integer port, @Nullable Executor e
* Injects a server to this container.
*
* <p>All calls to {@link #createGrpcServer(Executor)} will resolve to the given server
* instance.
* instance. The server instance is used as-is, no other
* {@linkplain Builder#withServer(ConfigureServer) configuration methods} have any effect on it.
*
* <p>A test-only method.
*/
Expand Down Expand Up @@ -380,6 +389,7 @@ private void println(@FormatString String msgFormat, Object... arg) {
public static final class Builder extends ConnectionBuilder {

private final Set<ServerServiceDefinition> services = Sets.newHashSet();
private @Nullable ConfigureServer configureServer;

private Builder(@Nullable Integer port, @Nullable String serverName) {
super(port, serverName);
Expand All @@ -405,18 +415,52 @@ public int getPort() {
return port().orElse(0);
}

/**
* Adds a gRPC service to deploy within the container being built.
*
* @return this instance of {@code Builder}, for call chaining
*/
@CanIgnoreReturnValue
public Builder addService(BindableService service) {
checkNotNull(service);
services.add(service.bindService());
return this;
}

/**
* Removes the {@linkplain #addService(BindableService) previously added}
* gRPC service.
*
* <p>If the service under the given definition was not added previously,
* this method does nothing.
*
* @return this instance of {@code Builder}, for call chaining
*/
@CanIgnoreReturnValue
public Builder removeService(ServerServiceDefinition service) {
services.remove(service);
return this;
}

/**
* Sets an additional configuration action for the gRPC {@link Server} instance,
* created for this {@code GrpcContainer} to run on top of. This configuration is applied
* right before the {@linkplain #start() server is started}.
*
* <p>Allows the direct access to gRPC {@link ServerBuilder}'s API.
*
* <p>Please note this API is experimental.
*
* @return this instance of {@code Builder}, for call chaining
* @see ConfigureServer
*/
@Experimental
@CanIgnoreReturnValue
public Builder withServer(ConfigureServer action) {
this.configureServer = checkNotNull(action);
return this;
}

/**
* Obtains the services already added to the builder.
*
Expand All @@ -438,4 +482,40 @@ public GrpcContainer build() {
return new GrpcContainer(this);
}
}

/**
* Allows to configure the gRPC's {@link Server} instance,
* on top of which this {@code GrpcContainer} will operate.
*
* <p>It is expected that the obtained builder of gRPC server is used to perform
* some fine-grained tuning of its features. The same instance of {@link ServerBuilder}
* should be returned.
*
* <p>Example.
*
* <pre>
*
* GrpcContainer container =
* GrpcContainer.atPort(1654)
* {@literal .withServer((server) -> server.maxInboundMessageSize(16_000_000)) }
* // ...
* .build();
*
* </pre>
*
* <p>Please note this interface is a part of experimental API.
*
* @see Builder#withServer(ConfigureServer)
*/
@Experimental
@FunctionalInterface
public interface ConfigureServer extends Function<ServerBuilder<?>, ServerBuilder<?>> {

/**
* Returns an instance which does nothing and returns the same {@code ServerBuilder}.
*/
static ConfigureServer doNothing() {
return builder -> builder;
}
}
}
33 changes: 33 additions & 0 deletions server/src/test/java/io/spine/server/GrpcContainerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.List;
import java.util.Set;

import static com.google.common.truth.Truth.assertThat;
Expand Down Expand Up @@ -109,6 +110,38 @@ void startServerWithExecutor() throws IOException {
.isNotNull();
}

@Test
@DisplayName("configure underlying gRPC server")
void configureUnderlyingGrpcServer() {
int port = 1654;
CommandService service = CommandService.newBuilder()
.build();
GrpcContainer container =
GrpcContainer.atPort(port)
.withServer((server) -> server.addService(service))
.build();
try {
container.start();
Server server = container.grpcServer();
assertThat(server)
.isNotNull();

List<ServerServiceDefinition> deployedServices = server.getServices();
assertThat(deployedServices)
.hasSize(1);

String actualName = deployedServices.get(0)
.getServiceDescriptor()
.getName();
assertThat(actualName).contains(service.getClass()
.getSimpleName());
} catch (IOException e) {
fail(e);
} finally {
container.shutdown();
}
}

@Test
@DisplayName("shutdown server")
void shutdownItself() throws IOException {
Expand Down
2 changes: 1 addition & 1 deletion version.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
/**
* Version of this library.
*/
val coreJava = "1.8.2-SNAPSHOT.1"
val coreJava = "1.8.2"

/**
* Versions of the Spine libraries that `core-java` depends on.
Expand Down