3636import  io .grpc .ServerBuilder ;
3737import  io .grpc .ServerServiceDefinition ;
3838import  io .grpc .inprocess .InProcessServerBuilder ;
39+ import  io .spine .annotation .Experimental ;
3940import  io .spine .client .ConnectionConstants ;
4041import  org .checkerframework .checker .nullness .qual .Nullable ;
4142
4243import  java .io .IOException ;
4344import  java .util .Optional ;
4445import  java .util .Set ;
4546import  java .util .concurrent .Executor ;
47+ import  java .util .function .Function ;
4648
4749import  static  com .google .common .base .Preconditions .checkNotNull ;
4850import  static  com .google .common .base .Preconditions .checkState ;
51+ import  static  io .spine .server .GrpcContainer .ConfigureServer .doNothing ;
4952import  static  java .lang .String .format ;
5053import  static  java .util .Objects .requireNonNull ;
5154
@@ -70,6 +73,7 @@ public final class GrpcContainer {
7073    private  final  @ Nullable  Integer  port ;
7174    private  final  @ Nullable  String  serverName ;
7275    private  final  ImmutableSet <ServerServiceDefinition > services ;
76+     private  final  ConfigureServer  configureServer ;
7377
7478    private  @ Nullable  Server  grpcServer ;
7579
@@ -106,6 +110,9 @@ private GrpcContainer(Builder builder) {
106110        this .port  = builder .port ().orElse (null );
107111        this .serverName  = builder .serverName ().orElse (null );
108112        this .services  = builder .services ();
113+         this .configureServer  = builder .configureServer  == null 
114+                                ? doNothing ()
115+                                : builder .configureServer ;
109116    }
110117
111118    /** 
@@ -284,6 +291,7 @@ private Server createGrpcServer(@Nullable Executor executor) {
284291        for  (ServerServiceDefinition  service  : services ) {
285292            builder .addService (service );
286293        }
294+         builder  = configureServer .apply (builder );
287295        return  builder .build ();
288296    }
289297
@@ -324,7 +332,8 @@ private static ServerBuilder<?> builderAtPort(Integer port, @Nullable Executor e
324332     * Injects a server to this container. 
325333     * 
326334     * <p>All calls to {@link #createGrpcServer(Executor)} will resolve to the given server 
327-      * instance. 
335+      * instance. The server instance is used as-is, no other 
336+      * {@linkplain Builder#withServer(ConfigureServer) configuration methods} have any effect on it. 
328337     * 
329338     * <p>A test-only method. 
330339     */ 
@@ -380,6 +389,7 @@ private void println(@FormatString String msgFormat, Object... arg) {
380389    public  static  final  class  Builder  extends  ConnectionBuilder  {
381390
382391        private  final  Set <ServerServiceDefinition > services  = Sets .newHashSet ();
392+         private  @ Nullable  ConfigureServer  configureServer ;
383393
384394        private  Builder (@ Nullable  Integer  port , @ Nullable  String  serverName ) {
385395            super (port , serverName );
@@ -405,18 +415,52 @@ public int getPort() {
405415            return  port ().orElse (0 );
406416        }
407417
418+         /** 
419+          * Adds a gRPC service to deploy within the container being built. 
420+          * 
421+          * @return this instance of {@code Builder}, for call chaining 
422+          */ 
408423        @ CanIgnoreReturnValue 
409424        public  Builder  addService (BindableService  service ) {
425+             checkNotNull (service );
410426            services .add (service .bindService ());
411427            return  this ;
412428        }
413429
430+         /** 
431+          * Removes the {@linkplain #addService(BindableService) previously added} 
432+          * gRPC service. 
433+          * 
434+          * <p>If the service under the given definition was not added previously, 
435+          * this method does nothing. 
436+          * 
437+          * @return this instance of {@code Builder}, for call chaining 
438+          */ 
414439        @ CanIgnoreReturnValue 
415440        public  Builder  removeService (ServerServiceDefinition  service ) {
416441            services .remove (service );
417442            return  this ;
418443        }
419444
445+         /** 
446+          * Sets an additional configuration action for the gRPC {@link Server} instance, 
447+          * created for this {@code GrpcContainer} to run on top of. This configuration is applied 
448+          * right before the {@linkplain #start() server is started}. 
449+          * 
450+          * <p>Allows the direct access to gRPC {@link ServerBuilder}'s API. 
451+          * 
452+          * <p>Please note this API is experimental. 
453+          * 
454+          * @return this instance of {@code Builder}, for call chaining 
455+          * @see ConfigureServer 
456+          */ 
457+         @ Experimental 
458+         @ CanIgnoreReturnValue 
459+         public  Builder  withServer (ConfigureServer  action ) {
460+             this .configureServer  = checkNotNull (action );
461+             return  this ;
462+         }
463+ 
420464        /** 
421465         * Obtains the services already added to the builder. 
422466         * 
@@ -438,4 +482,40 @@ public GrpcContainer build() {
438482            return  new  GrpcContainer (this );
439483        }
440484    }
485+ 
486+     /** 
487+      * Allows to configure the gRPC's {@link Server} instance, 
488+      * on top of which this {@code GrpcContainer} will operate. 
489+      * 
490+      * <p>It is expected that the obtained builder of gRPC server is used to perform 
491+      * some fine-grained tuning of its features. The same instance of {@link ServerBuilder} 
492+      * should be returned. 
493+      * 
494+      * <p>Example. 
495+      * 
496+      * <pre> 
497+      * 
498+      * GrpcContainer container = 
499+      *     GrpcContainer.atPort(1654) 
500+      * {@literal                 .withServer((server) -> server.maxInboundMessageSize(16_000_000)) } 
501+      *                  // ... 
502+      *                  .build(); 
503+      * 
504+      * </pre> 
505+      * 
506+      * <p>Please note this interface is a part of experimental API. 
507+      * 
508+      * @see Builder#withServer(ConfigureServer) 
509+      */ 
510+     @ Experimental 
511+     @ FunctionalInterface 
512+     public  interface  ConfigureServer  extends  Function <ServerBuilder <?>, ServerBuilder <?>> {
513+ 
514+         /** 
515+          * Returns an instance which does nothing and returns the same {@code ServerBuilder}. 
516+          */ 
517+         static  ConfigureServer  doNothing () {
518+             return  builder  -> builder ;
519+         }
520+     }
441521}
0 commit comments