Skip to content

Commit f6609ee

Browse files
authored
Merge pull request #2695 from ozangunalp/client_config_interceptor
ClientCustomizer
2 parents cf057ef + 5235ea3 commit f6609ee

File tree

62 files changed

+826
-260
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+826
-260
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.smallrye.reactive.messaging;
2+
3+
import jakarta.enterprise.inject.spi.Prioritized;
4+
5+
import org.eclipse.microprofile.config.Config;
6+
7+
/**
8+
* A customizer that can be used to modify the configuration used to create a messaging client.
9+
*
10+
* @param <T> the type of the configuration object
11+
*/
12+
public interface ClientCustomizer<T> extends Prioritized {
13+
14+
/**
15+
* The default priority for config customizers.
16+
*/
17+
int CLIENT_CONFIG_CUSTOMIZER_DEFAULT_PRIORITY = 100;
18+
19+
/**
20+
* Customize the given configuration object.
21+
*
22+
* @param channel the channel name
23+
* @param channelConfig the channel configuration
24+
* @param config the configuration object
25+
* @return the modified configuration object, or {@code null} to skip this customizer
26+
*/
27+
T customize(String channel, Config channelConfig, T config);
28+
29+
@Override
30+
default int getPriority() {
31+
return CLIENT_CONFIG_CUSTOMIZER_DEFAULT_PRIORITY;
32+
}
33+
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Client Customizers
2+
3+
Client customizers allow to customize the client instance created by the connector.
4+
Only connectors which create their own client instance support customizers.
5+
6+
To define a client customizer, you need to provide a CDI bean that implements the `ClientCustomizer<T>` interface,
7+
parameterized with the config type:
8+
9+
``` java
10+
{{ insert('customizers/MyClientCustomizer.java') }}
11+
```
12+
13+
Connectors which support client customizers will discover all beans and call the `customize` method,
14+
with the _channel name_, the _channel configuration_ and the configuration that'll be used to create the client instance.
15+
If the customizer returns `null` it'll be skipped.
16+
17+
If you have multiple customizers, customizers can override the `getPriority` method to define the order in which they are called.
18+
19+
Currently, the following core connectors support client customizers:
20+
21+
- Kafka: `ClientCustomizer<Map<String, Object>>`
22+
- RabbitMQ: `ClientCustomizer<RabbitMQOptions>`
23+
- AMQP 1.0: `ClientCustomizer<AmqpClientOptions>`
24+
- MQTT: `ClientCustomizer<MqttClientSessionOptions>`
25+
- Pulsar: `ClientCustomizer<ClientBuilder>`, `ClientCustomizer<ConsumerBuilder<?>>`, `ClientCustomizer<ProducerBuilder<?>>`
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package customizers;
2+
3+
public class ClientConfig {
4+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package customizers;
2+
3+
import jakarta.enterprise.context.ApplicationScoped;
4+
5+
import org.eclipse.microprofile.config.Config;
6+
7+
import io.smallrye.reactive.messaging.ClientCustomizer;
8+
9+
@ApplicationScoped
10+
public class MyClientCustomizer implements ClientCustomizer<ClientConfig> {
11+
@Override
12+
public ClientConfig customize(String channel, Config channelConfig, ClientConfig config) {
13+
// customize the client configuration
14+
return config;
15+
}
16+
17+
}

smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpClientHelper.java

Lines changed: 11 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,15 @@
66

77
import java.util.Optional;
88

9-
import javax.net.ssl.SSLContext;
10-
119
import jakarta.enterprise.inject.Instance;
1210
import jakarta.enterprise.inject.literal.NamedLiteral;
1311

14-
import io.netty.handler.ssl.ApplicationProtocolConfig;
15-
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
16-
import io.netty.handler.ssl.JdkSslContext;
17-
import io.netty.handler.ssl.SslContext;
1812
import io.smallrye.common.annotation.Identifier;
13+
import io.smallrye.reactive.messaging.ClientCustomizer;
14+
import io.smallrye.reactive.messaging.providers.helpers.ConfigUtils;
1915
import io.smallrye.reactive.messaging.providers.i18n.ProviderLogging;
2016
import io.vertx.amqp.AmqpClientOptions;
21-
import io.vertx.core.net.JdkSSLEngineOptions;
22-
import io.vertx.core.spi.tls.SslContextFactory;
2317
import io.vertx.mutiny.amqp.AmqpClient;
24-
import io.vertx.mutiny.core.Vertx;
2518

2619
public class AmqpClientHelper {
2720

@@ -30,25 +23,22 @@ private AmqpClientHelper() {
3023
}
3124

3225
static AmqpClient createClient(AmqpConnector connector, AmqpConnectorCommonConfiguration config,
33-
Instance<AmqpClientOptions> amqpClientOptions, Instance<SSLContext> clientSslContexts) {
34-
AmqpClient client;
26+
Instance<AmqpClientOptions> amqpClientOptions,
27+
Instance<ClientCustomizer<AmqpClientOptions>> configCustomizers) {
3528
Optional<String> clientOptionsName = config.getClientOptionsName();
36-
Optional<String> clientSslContextName = config.getClientSslContextName();
37-
if (clientOptionsName.isPresent() && clientSslContextName.isPresent()) {
38-
throw ProviderLogging.log.cannotSpecifyBothClientOptionsNameAndClientSslContextName();
39-
}
40-
Vertx vertx = connector.getVertx();
29+
AmqpClientOptions options;
4130
if (clientOptionsName.isPresent()) {
42-
client = createClientFromClientOptionsBean(vertx, amqpClientOptions, clientOptionsName.get(), config);
31+
options = createClientFromClientOptionsBean(amqpClientOptions, clientOptionsName.get(), config);
4332
} else {
44-
SSLContext sslContext = getClientSslContext(clientSslContexts, clientSslContextName);
45-
client = getClient(vertx, config, sslContext);
33+
options = getOptionsFromChannel(config);
4634
}
35+
AmqpClientOptions clientOptions = ConfigUtils.customize(config.config(), configCustomizers, options);
36+
AmqpClient client = AmqpClient.create(connector.getVertx(), clientOptions);
4737
connector.addClient(client);
4838
return client;
4939
}
5040

51-
static AmqpClient createClientFromClientOptionsBean(Vertx vertx, Instance<AmqpClientOptions> instance,
41+
static AmqpClientOptions createClientFromClientOptionsBean(Instance<AmqpClientOptions> instance,
5242
String optionsBeanName, AmqpConnectorCommonConfiguration config) {
5343
Instance<AmqpClientOptions> options = instance.select(Identifier.Literal.of(optionsBeanName));
5444
if (options.isUnsatisfied()) {
@@ -67,7 +57,7 @@ static AmqpClient createClientFromClientOptionsBean(Vertx vertx, Instance<AmqpCl
6757
// In case of conflict, use the channel config.
6858
AmqpClientOptions customizerOptions = options.get();
6959
merge(customizerOptions, config);
70-
return AmqpClient.create(vertx, customizerOptions);
60+
return customizerOptions;
7161
}
7262

7363
/**
@@ -183,47 +173,4 @@ static AmqpClientOptions getOptionsFromChannel(AmqpConnectorCommonConfiguration
183173
return options;
184174
}
185175

186-
static AmqpClient getClient(Vertx vertx, AmqpConnectorCommonConfiguration config, SSLContext sslContext) {
187-
try {
188-
AmqpClientOptions options = getOptionsFromChannel(config);
189-
if (sslContext != null) {
190-
options.setSslEngineOptions(new JdkSSLEngineOptions() {
191-
@Override
192-
public SslContextFactory sslContextFactory() {
193-
return new SslContextFactory() {
194-
@Override
195-
public SslContext create() {
196-
return new JdkSslContext(
197-
sslContext,
198-
true,
199-
null,
200-
IdentityCipherSuiteFilter.INSTANCE,
201-
ApplicationProtocolConfig.DISABLED,
202-
io.netty.handler.ssl.ClientAuth.NONE,
203-
null,
204-
false);
205-
}
206-
};
207-
}
208-
});
209-
}
210-
return AmqpClient.create(vertx, options);
211-
} catch (Exception e) {
212-
log.unableToCreateClient(e);
213-
throw ex.illegalStateUnableToCreateClient(e);
214-
}
215-
}
216-
217-
private static SSLContext getClientSslContext(Instance<SSLContext> clientSslContexts,
218-
Optional<String> clientSslContextName) {
219-
if (clientSslContextName.isPresent()) {
220-
Instance<SSLContext> context = clientSslContexts
221-
.select(Identifier.Literal.of(clientSslContextName.get()));
222-
if (context.isUnsatisfied()) {
223-
throw ProviderLogging.log.couldFindSslContextWithIdentifier(clientSslContextName.get());
224-
}
225-
return context.get();
226-
}
227-
return null;
228-
}
229176
}

smallrye-reactive-messaging-amqp/src/main/java/io/smallrye/reactive/messaging/amqp/AmqpConnector.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import java.util.concurrent.atomic.AtomicReference;
1919
import java.util.stream.Collectors;
2020

21-
import javax.net.ssl.SSLContext;
22-
2321
import jakarta.annotation.Priority;
2422
import jakarta.enterprise.context.ApplicationScoped;
2523
import jakarta.enterprise.context.BeforeDestroyed;
@@ -37,6 +35,7 @@
3735
import io.smallrye.mutiny.Multi;
3836
import io.smallrye.mutiny.Uni;
3937
import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor;
38+
import io.smallrye.reactive.messaging.ClientCustomizer;
4039
import io.smallrye.reactive.messaging.amqp.fault.AmqpAccept;
4140
import io.smallrye.reactive.messaging.amqp.fault.AmqpFailStop;
4241
import io.smallrye.reactive.messaging.amqp.fault.AmqpFailureHandler;
@@ -119,7 +118,7 @@ public class AmqpConnector implements InboundConnector, OutboundConnector, Healt
119118

120119
@Inject
121120
@Any
122-
private Instance<SSLContext> clientSslContexts;
121+
private Instance<ClientCustomizer<AmqpClientOptions>> configCustomizers;
123122

124123
@Inject
125124
private Instance<OpenTelemetry> openTelemetryInstance;
@@ -221,7 +220,7 @@ public Flow.Publisher<? extends Message<?>> getPublisher(Config config) {
221220
.setCapabilities(getClientCapabilities(ic))
222221
.setSelector(ic.getSelector().orElse(null));
223222

224-
AmqpClient client = AmqpClientHelper.createClient(this, ic, clientOptions, clientSslContexts);
223+
AmqpClient client = AmqpClientHelper.createClient(this, ic, clientOptions, configCustomizers);
225224

226225
Context root = Context.newInstance(((VertxInternal) getVertx().getDelegate()).createEventLoopContext());
227226
ConnectionHolder holder = new ConnectionHolder(client, ic, getVertx(), root);
@@ -265,7 +264,7 @@ public Flow.Subscriber<? extends Message<?>> getSubscriber(Config config) {
265264
opened.put(oc.getChannel(), false);
266265

267266
AtomicReference<AmqpSender> sender = new AtomicReference<>();
268-
AmqpClient client = AmqpClientHelper.createClient(this, oc, clientOptions, clientSslContexts);
267+
AmqpClient client = AmqpClientHelper.createClient(this, oc, clientOptions, configCustomizers);
269268
String link = oc.getLinkName().orElseGet(oc::getChannel);
270269
ConnectionHolder holder = new ConnectionHolder(client, oc, getVertx(), null);
271270

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.smallrye.reactive.messaging.amqp;
2+
3+
import static io.smallrye.reactive.messaging.amqp.i18n.AMQPExceptions.ex;
4+
import static io.smallrye.reactive.messaging.amqp.i18n.AMQPLogging.log;
5+
6+
import java.util.Optional;
7+
8+
import javax.net.ssl.SSLContext;
9+
10+
import jakarta.enterprise.context.ApplicationScoped;
11+
import jakarta.enterprise.inject.Any;
12+
import jakarta.enterprise.inject.Instance;
13+
import jakarta.inject.Inject;
14+
15+
import org.eclipse.microprofile.config.Config;
16+
17+
import io.netty.handler.ssl.ApplicationProtocolConfig;
18+
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
19+
import io.netty.handler.ssl.JdkSslContext;
20+
import io.netty.handler.ssl.SslContext;
21+
import io.smallrye.reactive.messaging.ClientCustomizer;
22+
import io.smallrye.reactive.messaging.providers.helpers.CDIUtils;
23+
import io.vertx.amqp.AmqpClientOptions;
24+
import io.vertx.core.net.JdkSSLEngineOptions;
25+
import io.vertx.core.spi.tls.SslContextFactory;
26+
27+
@ApplicationScoped
28+
public class SslContextClientCustomizer implements ClientCustomizer<AmqpClientOptions> {
29+
30+
@Inject
31+
@Any
32+
private Instance<SSLContext> clientSslContexts;
33+
34+
@Override
35+
public AmqpClientOptions customize(String channel, Config channelConfig, AmqpClientOptions config) {
36+
AmqpConnectorCommonConfiguration commonConfiguration = new AmqpConnectorCommonConfiguration(channelConfig);
37+
Optional<String> clientSslContextName = commonConfiguration.getClientSslContextName();
38+
if (clientSslContextName.isPresent()) {
39+
SSLContext sslContext = CDIUtils.getInstanceById(clientSslContexts, clientSslContextName.get(), () -> null);
40+
if (sslContext != null) {
41+
try {
42+
config.setSslEngineOptions(new JdkSSLEngineOptions() {
43+
@Override
44+
public SslContextFactory sslContextFactory() {
45+
return new SslContextFactory() {
46+
@Override
47+
public SslContext create() {
48+
return new JdkSslContext(
49+
sslContext,
50+
true,
51+
null,
52+
IdentityCipherSuiteFilter.INSTANCE,
53+
ApplicationProtocolConfig.DISABLED,
54+
io.netty.handler.ssl.ClientAuth.NONE,
55+
null,
56+
false);
57+
}
58+
};
59+
}
60+
});
61+
} catch (Exception e) {
62+
log.unableToCreateClient(e);
63+
throw ex.illegalStateUnableToCreateClient(e);
64+
}
65+
}
66+
}
67+
return config;
68+
}
69+
}

smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/KafkaConnector.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.opentelemetry.api.trace.SpanBuilder;
2929
import io.opentelemetry.api.trace.Tracer;
3030
import io.smallrye.mutiny.Multi;
31+
import io.smallrye.reactive.messaging.ClientCustomizer;
3132
import io.smallrye.reactive.messaging.annotations.ConnectorAttribute;
3233
import io.smallrye.reactive.messaging.annotations.ConnectorAttribute.Direction;
3334
import io.smallrye.reactive.messaging.connector.InboundConnector;
@@ -150,6 +151,10 @@ public SpanBuilder spanBuilder(final String spanName) {
150151
@Any
151152
Instance<DeserializationFailureHandler<?>> deserializationFailureHandlers;
152153

154+
@Inject
155+
@Any
156+
Instance<ClientCustomizer<Map<String, Object>>> configCustomizers;
157+
153158
@Inject
154159
@Any
155160
Instance<SerializationFailureHandler<?>> serializationFailureHandlers;
@@ -216,7 +221,7 @@ public Flow.Publisher<? extends Message<?>> getPublisher(Config config) {
216221
KafkaSource<Object, Object> source = new KafkaSource<>(vertx, group, ic, openTelemetryInstance,
217222
commitHandlerFactories, failureHandlerFactories,
218223
consumerRebalanceListeners,
219-
kafkaCDIEvents, deserializationFailureHandlers, -1);
224+
kafkaCDIEvents, configCustomizers, deserializationFailureHandlers, -1);
220225
sources.add(source);
221226
boolean broadcast = ic.getBroadcast();
222227
Multi<? extends Message<?>> stream;
@@ -238,7 +243,7 @@ public Flow.Publisher<? extends Message<?>> getPublisher(Config config) {
238243
KafkaSource<Object, Object> source = new KafkaSource<>(vertx, group, ic, openTelemetryInstance,
239244
commitHandlerFactories, failureHandlerFactories,
240245
consumerRebalanceListeners,
241-
kafkaCDIEvents, deserializationFailureHandlers, i);
246+
kafkaCDIEvents, configCustomizers, deserializationFailureHandlers, i);
242247
sources.add(source);
243248
if (!ic.getBatch()) {
244249
streams.add(source.getStream());
@@ -273,7 +278,7 @@ public Flow.Subscriber<? extends Message<?>> getSubscriber(Config config) {
273278
log.deprecatedConfig("health-readiness-timeout", "health-topic-verification-timeout");
274279
}
275280
KafkaSink sink = new KafkaSink(oc, kafkaCDIEvents, openTelemetryInstance,
276-
serializationFailureHandlers, producerInterceptors);
281+
configCustomizers, serializationFailureHandlers, producerInterceptors);
277282
sinks.add(sink);
278283
return sink.getSink();
279284
}

smallrye-reactive-messaging-kafka/src/main/java/io/smallrye/reactive/messaging/kafka/fault/KafkaDeadLetterQueue.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.smallrye.common.annotation.Identifier;
3434
import io.smallrye.mutiny.Uni;
3535
import io.smallrye.mutiny.operators.multi.processors.UnicastProcessor;
36+
import io.smallrye.reactive.messaging.ClientCustomizer;
3637
import io.smallrye.reactive.messaging.SubscriberDecorator;
3738
import io.smallrye.reactive.messaging.kafka.IncomingKafkaRecord;
3839
import io.smallrye.reactive.messaging.kafka.KafkaCDIEvents;
@@ -85,6 +86,10 @@ public static class Factory implements KafkaFailureHandler.Factory {
8586
@Any
8687
Instance<SerializationFailureHandler<?>> serializationFailureHandlers;
8788

89+
@Inject
90+
@Any
91+
Instance<ClientCustomizer<Map<String, Object>>> configCustomizers;
92+
8893
@Inject
8994
@Any
9095
Instance<ProducerInterceptor<?, ?>> producerInterceptors;
@@ -132,7 +137,7 @@ public KafkaFailureHandler create(KafkaConnectorIncomingConfiguration config,
132137

133138
UnicastProcessor<Message<?>> processor = UnicastProcessor.create();
134139
KafkaSink kafkaSink = new KafkaSink(producerConfig, kafkaCDIEvents, openTelemetryInstance,
135-
serializationFailureHandlers, producerInterceptors);
140+
configCustomizers, serializationFailureHandlers, producerInterceptors);
136141
wireOutgoingConnectorToUpstream(processor, kafkaSink.getSink(), subscriberDecorators,
137142
producerConfig.getChannel() + "-" + CHANNEL_DLQ_SUFFIX);
138143
return new KafkaDeadLetterQueue(config.getChannel(), deadQueueTopic, kafkaSink, processor);

0 commit comments

Comments
 (0)