2020import java .time .temporal .ChronoUnit ;
2121import java .util .HashMap ;
2222import java .util .Map ;
23+ import java .util .Objects ;
24+ import java .util .Optional ;
2325import java .util .function .Consumer ;
2426
2527import org .jspecify .annotations .Nullable ;
@@ -65,7 +67,6 @@ public class GrpcClientProperties implements EnvironmentAware, VirtualTargets {
6567 private Environment environment ;
6668
6769 GrpcClientProperties () {
68- this .defaultChannel .setAddress ("static://localhost:9090" );
6970 this .environment = new StandardEnvironment ();
7071 }
7172
@@ -93,17 +94,19 @@ public void setEnvironment(Environment environment) {
9394 /**
9495 * Gets the configured channel with the given name. If no channel is configured for
9596 * the specified name then one is created using the default channel as a template.
97+ * Named channels inherit settings from the default channel, with explicitly
98+ * configured values taking precedence.
9699 * @param name the name of the channel
97- * @return the configured channel if found, or a newly created channel using the
98- * default channel as a template
100+ * @return the configured channel if found (merged with defaults) , or a newly created
101+ * channel using the default channel as a template
99102 */
100103 public ChannelConfig getChannel (String name ) {
101104 if ("default" .equals (name )) {
102105 return this .defaultChannel ;
103106 }
104107 ChannelConfig channel = this .channels .get (name );
105108 if (channel != null ) {
106- return channel ;
109+ return this . defaultChannel . mergeWith ( channel ) ;
107110 }
108111 channel = this .defaultChannel .copy ();
109112 String address = name ;
@@ -137,10 +140,32 @@ public String getTarget(String authority) {
137140 */
138141 public static class ChannelConfig {
139142
143+ private static final String DEFAULT_ADDRESS = "static://localhost:9090" ;
144+
145+ private static final String DEFAULT_LOAD_BALANCING_POLICY = "round_robin" ;
146+
147+ private static final boolean DEFAULT_ENABLE_KEEP_ALIVE = false ;
148+
149+ private static final Duration DEFAULT_IDLE_TIMEOUT = Duration .ofSeconds (20 );
150+
151+ private static final Duration DEFAULT_KEEP_ALIVE_TIME = Duration .ofMinutes (5 );
152+
153+ private static final Duration DEFAULT_KEEP_ALIVE_TIMEOUT = Duration .ofSeconds (20 );
154+
155+ private static final boolean DEFAULT_KEEP_ALIVE_WITHOUT_CALLS = false ;
156+
157+ private static final DataSize DEFAULT_MAX_INBOUND_MESSAGE_SIZE = DataSize .ofBytes (4194304 );
158+
159+ private static final DataSize DEFAULT_MAX_INBOUND_METADATA_SIZE = DataSize .ofBytes (8192 );
160+
161+ private static final NegotiationType DEFAULT_NEGOTIATION_TYPE = NegotiationType .PLAINTEXT ;
162+
163+ private static final boolean DEFAULT_SECURE = true ;
164+
140165 /**
141166 * The target address uri to connect to.
142167 */
143- private String address = "static://localhost:9090" ;
168+ private @ Nullable String address ;
144169
145170 /**
146171 * The default deadline for RPCs performed on this channel.
@@ -150,63 +175,63 @@ public static class ChannelConfig {
150175 /**
151176 * The load balancing policy the channel should use.
152177 */
153- private String defaultLoadBalancingPolicy = "round_robin" ;
178+ private @ Nullable String defaultLoadBalancingPolicy ;
154179
155180 /**
156181 * Whether keep alive is enabled on the channel.
157182 */
158- private boolean enableKeepAlive ;
183+ private @ Nullable Boolean enableKeepAlive ;
159184
160185 private final Health health = new Health ();
161186
162187 /**
163188 * The duration without ongoing RPCs before going to idle mode.
164189 */
165190 @ DurationUnit (ChronoUnit .SECONDS )
166- private Duration idleTimeout = Duration . ofSeconds ( 20 ) ;
191+ private @ Nullable Duration idleTimeout ;
167192
168193 /**
169194 * The delay before sending a keepAlive. Note that shorter intervals increase the
170195 * network burden for the server and this value can not be lower than
171196 * 'permitKeepAliveTime' on the server.
172197 */
173198 @ DurationUnit (ChronoUnit .SECONDS )
174- private Duration keepAliveTime = Duration . ofMinutes ( 5 ) ;
199+ private @ Nullable Duration keepAliveTime ;
175200
176201 /**
177202 * The default timeout for a keepAlives ping request.
178203 */
179204 @ DurationUnit (ChronoUnit .SECONDS )
180- private Duration keepAliveTimeout = Duration . ofSeconds ( 20 ) ;
205+ private @ Nullable Duration keepAliveTimeout ;
181206
182207 /**
183208 * Whether a keepAlive will be performed when there are no outstanding RPC on a
184209 * connection.
185210 */
186- private boolean keepAliveWithoutCalls ;
211+ private @ Nullable Boolean keepAliveWithoutCalls ;
187212
188213 /**
189214 * Maximum message size allowed to be received by the channel (default 4MiB). Set
190215 * to '-1' to use the highest possible limit (not recommended).
191216 */
192- private DataSize maxInboundMessageSize = DataSize . ofBytes ( 4194304 ) ;
217+ private @ Nullable DataSize maxInboundMessageSize ;
193218
194219 /**
195220 * Maximum metadata size allowed to be received by the channel (default 8KiB). Set
196221 * to '-1' to use the highest possible limit (not recommended).
197222 */
198- private DataSize maxInboundMetadataSize = DataSize . ofBytes ( 8192 ) ;
223+ private @ Nullable DataSize maxInboundMetadataSize ;
199224
200225 /**
201226 * The negotiation type for the channel.
202227 */
203- private NegotiationType negotiationType = NegotiationType . PLAINTEXT ;
228+ private @ Nullable NegotiationType negotiationType ;
204229
205230 /**
206231 * Flag to say that strict SSL checks are not enabled (so the remote certificate
207232 * could be anonymous).
208233 */
209- private boolean secure = true ;
234+ private @ Nullable Boolean secure ;
210235
211236 /**
212237 * Map representation of the service config to use for the channel.
@@ -221,7 +246,7 @@ public static class ChannelConfig {
221246 private @ Nullable String userAgent ;
222247
223248 public String getAddress () {
224- return this .address ;
249+ return Objects . requireNonNullElse ( this .address , DEFAULT_ADDRESS ) ;
225250 }
226251
227252 public void setAddress (final String address ) {
@@ -237,15 +262,15 @@ public void setDefaultDeadline(@Nullable Duration defaultDeadline) {
237262 }
238263
239264 public String getDefaultLoadBalancingPolicy () {
240- return this .defaultLoadBalancingPolicy ;
265+ return Objects . requireNonNullElse ( this .defaultLoadBalancingPolicy , DEFAULT_LOAD_BALANCING_POLICY ) ;
241266 }
242267
243268 public void setDefaultLoadBalancingPolicy (final String defaultLoadBalancingPolicy ) {
244269 this .defaultLoadBalancingPolicy = defaultLoadBalancingPolicy ;
245270 }
246271
247272 public boolean isEnableKeepAlive () {
248- return this .enableKeepAlive ;
273+ return Objects . requireNonNullElse ( this .enableKeepAlive , DEFAULT_ENABLE_KEEP_ALIVE ) ;
249274 }
250275
251276 public void setEnableKeepAlive (boolean enableKeepAlive ) {
@@ -257,39 +282,39 @@ public Health getHealth() {
257282 }
258283
259284 public Duration getIdleTimeout () {
260- return this .idleTimeout ;
285+ return Objects . requireNonNullElse ( this .idleTimeout , DEFAULT_IDLE_TIMEOUT ) ;
261286 }
262287
263288 public void setIdleTimeout (Duration idleTimeout ) {
264289 this .idleTimeout = idleTimeout ;
265290 }
266291
267292 public Duration getKeepAliveTime () {
268- return this .keepAliveTime ;
293+ return Objects . requireNonNullElse ( this .keepAliveTime , DEFAULT_KEEP_ALIVE_TIME ) ;
269294 }
270295
271296 public void setKeepAliveTime (Duration keepAliveTime ) {
272297 this .keepAliveTime = keepAliveTime ;
273298 }
274299
275300 public Duration getKeepAliveTimeout () {
276- return this .keepAliveTimeout ;
301+ return Objects . requireNonNullElse ( this .keepAliveTimeout , DEFAULT_KEEP_ALIVE_TIMEOUT ) ;
277302 }
278303
279304 public void setKeepAliveTimeout (Duration keepAliveTimeout ) {
280305 this .keepAliveTimeout = keepAliveTimeout ;
281306 }
282307
283308 public boolean isKeepAliveWithoutCalls () {
284- return this .keepAliveWithoutCalls ;
309+ return Objects . requireNonNullElse ( this .keepAliveWithoutCalls , DEFAULT_KEEP_ALIVE_WITHOUT_CALLS ) ;
285310 }
286311
287312 public void setKeepAliveWithoutCalls (boolean keepAliveWithoutCalls ) {
288313 this .keepAliveWithoutCalls = keepAliveWithoutCalls ;
289314 }
290315
291316 public DataSize getMaxInboundMessageSize () {
292- return this .maxInboundMessageSize ;
317+ return Objects . requireNonNullElse ( this .maxInboundMessageSize , DEFAULT_MAX_INBOUND_MESSAGE_SIZE ) ;
293318 }
294319
295320 public void setMaxInboundMessageSize (final DataSize maxInboundMessageSize ) {
@@ -298,7 +323,7 @@ public void setMaxInboundMessageSize(final DataSize maxInboundMessageSize) {
298323 }
299324
300325 public DataSize getMaxInboundMetadataSize () {
301- return this .maxInboundMetadataSize ;
326+ return Objects . requireNonNullElse ( this .maxInboundMetadataSize , DEFAULT_MAX_INBOUND_METADATA_SIZE ) ;
302327 }
303328
304329 public void setMaxInboundMetadataSize (DataSize maxInboundMetadataSize ) {
@@ -319,15 +344,15 @@ else if (maxSize != null && maxSize.toBytes() == -1) {
319344 }
320345
321346 public NegotiationType getNegotiationType () {
322- return this .negotiationType ;
347+ return Objects . requireNonNullElse ( this .negotiationType , DEFAULT_NEGOTIATION_TYPE ) ;
323348 }
324349
325350 public void setNegotiationType (NegotiationType negotiationType ) {
326351 this .negotiationType = negotiationType ;
327352 }
328353
329354 public boolean isSecure () {
330- return this .secure ;
355+ return Objects . requireNonNullElse ( this .secure , DEFAULT_SECURE ) ;
331356 }
332357
333358 public void setSecure (boolean secure ) {
@@ -375,6 +400,41 @@ ChannelConfig copy() {
375400 return copy ;
376401 }
377402
403+ /**
404+ * Merges this channel configuration with another configuration. Non-null values
405+ * in the other configuration take precedence over values in this configuration.
406+ * @param other the configuration to merge with (non-null values override this)
407+ * @return a new merged configuration
408+ */
409+ ChannelConfig mergeWith (ChannelConfig other ) {
410+ ChannelConfig merged = this .copy ();
411+ merged .address = Optional .ofNullable (other .address ).orElse (merged .address );
412+ merged .defaultDeadline = Optional .ofNullable (other .defaultDeadline ).orElse (merged .defaultDeadline );
413+ merged .defaultLoadBalancingPolicy = Optional .ofNullable (other .defaultLoadBalancingPolicy )
414+ .orElse (merged .defaultLoadBalancingPolicy );
415+ merged .enableKeepAlive = Optional .ofNullable (other .enableKeepAlive ).orElse (merged .enableKeepAlive );
416+ merged .idleTimeout = Optional .ofNullable (other .idleTimeout ).orElse (merged .idleTimeout );
417+ merged .keepAliveTime = Optional .ofNullable (other .keepAliveTime ).orElse (merged .keepAliveTime );
418+ merged .keepAliveTimeout = Optional .ofNullable (other .keepAliveTimeout ).orElse (merged .keepAliveTimeout );
419+ merged .keepAliveWithoutCalls = Optional .ofNullable (other .keepAliveWithoutCalls )
420+ .orElse (merged .keepAliveWithoutCalls );
421+ merged .maxInboundMessageSize = Optional .ofNullable (other .maxInboundMessageSize )
422+ .orElse (merged .maxInboundMessageSize );
423+ merged .maxInboundMetadataSize = Optional .ofNullable (other .maxInboundMetadataSize )
424+ .orElse (merged .maxInboundMetadataSize );
425+ merged .negotiationType = Optional .ofNullable (other .negotiationType ).orElse (merged .negotiationType );
426+ merged .secure = Optional .ofNullable (other .secure ).orElse (merged .secure );
427+ merged .userAgent = Optional .ofNullable (other .userAgent ).orElse (merged .userAgent );
428+
429+ merged .health .mergeWith (other .health );
430+ merged .ssl .mergeWith (other .ssl );
431+
432+ if (!other .serviceConfig .isEmpty ()) {
433+ merged .serviceConfig .putAll (other .serviceConfig );
434+ }
435+ return merged ;
436+ }
437+
378438 /**
379439 * Extracts the service configuration from the client properties, respecting the
380440 * yaml lists (e.g. `retryPolicy`).
@@ -390,15 +450,15 @@ public static class Health {
390450 /**
391451 * Whether to enable client-side health check for the channel.
392452 */
393- private boolean enabled ;
453+ private @ Nullable Boolean enabled ;
394454
395455 /**
396456 * Name of the service to check health on.
397457 */
398458 private @ Nullable String serviceName ;
399459
400460 public boolean isEnabled () {
401- return this .enabled ;
461+ return Objects . requireNonNullElse ( this .enabled , false ) ;
402462 }
403463
404464 public void setEnabled (boolean enabled ) {
@@ -422,6 +482,16 @@ void copyValuesFrom(Health other) {
422482 this .serviceName = other .serviceName ;
423483 }
424484
485+ /**
486+ * Merges this health configuration with another. Non-null values in the other
487+ * configuration take precedence.
488+ * @param other the configuration to merge with
489+ */
490+ void mergeWith (Health other ) {
491+ this .enabled = Optional .ofNullable (other .enabled ).orElse (this .enabled );
492+ this .serviceName = Optional .ofNullable (other .serviceName ).orElse (this .serviceName );
493+ }
494+
425495 }
426496
427497 public static class Ssl {
@@ -466,6 +536,16 @@ void copyValuesFrom(Ssl other) {
466536 this .bundle = other .bundle ;
467537 }
468538
539+ /**
540+ * Merges this SSL configuration with another. Non-null values in the other
541+ * configuration take precedence.
542+ * @param other the configuration to merge with
543+ */
544+ void mergeWith (Ssl other ) {
545+ this .enabled = Optional .ofNullable (other .enabled ).orElse (this .enabled );
546+ this .bundle = Optional .ofNullable (other .bundle ).orElse (this .bundle );
547+ }
548+
469549 }
470550
471551 }
0 commit comments