Skip to content

Commit 1218da9

Browse files
committed
Allow named channels to inherit configuration from default-channel
Named channels now inherit settings from default-channel configuration. Explicitly configured values in named channels take precedence over defaults. Implements mergeWith() method in ChannelConfig that merges configurations using Optional.ofNullable().orElse() pattern for null-safe value selection. Adds ChannelInheritanceAPI tests to verify inheritance behavior. [resolves #345] Signed-off-by: Oleksandr Shevchenko <shevchenko.olexandr96@gmail.com> Signed-off-by: Oleksandr Shevchenko <oleksandr.shevchenko@datarobot.com>
1 parent cad5b72 commit 1218da9

File tree

2 files changed

+216
-31
lines changed

2 files changed

+216
-31
lines changed

spring-grpc-client-spring-boot-autoconfigure/src/main/java/org/springframework/boot/grpc/client/autoconfigure/GrpcClientProperties.java

Lines changed: 108 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.time.temporal.ChronoUnit;
2121
import java.util.HashMap;
2222
import java.util.Map;
23+
import java.util.Objects;
24+
import java.util.Optional;
2325
import java.util.function.Consumer;
2426

2527
import 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

Comments
 (0)