I'm really new to Camel concepts, our need is to create some identical routes, identical excepted for some parameters, from a Kafka topic to a http endpoint, with some processing in-between.
Besides this we want to explicitly commit the message consumption only when the http endpoint has been successfully called.
In order to achieve this we set up a route template that carries the Route parameterization and set it up to manually commit after having called the http endpoint :
public void configure() throws Exception {
// #formatter:off
routeTemplate(Constantes.KAFKA_GENERIC_ROUTE)
.templateParameter(Constantes.JOB_NAME)
.templateParameter(Constantes.TOPIC)
.templateParameter(Constantes.PUBLISHER_ID)
.templateParameter(Constantes.CORRELATION_ID_PARAMETER)
.templateParameter(Constantes.JOB_NAME_PARAMETER)
.templateParameter(Constantes.CORRELATION_ID_PARAMETER)
.from(getKafkaEndpoint())
.messageHistory()
.filter(simple("${header.publisherId} == '{{publisherId}}'"))
.process(messageLoggerProcessor)
.process(modelMapperProcessor)
.process(jsonlToArrayProcessor)
.process(payloadProcessor)
.resequence(header("dmlTimestamp")).batch().timeout(maximumRequestCount)
.setHeader(Exchange.HTTP_METHOD, simple("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json;charset=UTF-8"))
.setHeader(Constantes.ACCEPT,constant("application/json"))
.setHeader(Constantes.API_KEY, constant(apiKey))
.throttle(maximumRequestCount).timePeriodMillis(timePeriodMillis).asyncDelayed(true)
.process(apiConsumerProcessorLogger)
.to(this.url)
.process(kafkaOffsetProcessor);
// #formatter:on
}
private String getKafkaEndpoint() {
String endpoint = "kafka:{{topic}}?allowManualCommit=true&autoCommitEnable=false&brokers=" + kafkaBrokers;
if (securityEnabled()) {
endpoint += "&securityProtocol=SASL_SSL" + "&saslMechanism=PLAIN"
+ "&saslJaasConfig=org.apache.kafka.common.security.plain.PlainLoginModule required username=\""
+ username + "\" password=\"" + password + "\";" + "&sslTruststoreLocation=" + sslTrustStoreLocation
+ "&sslTruststorePassword=" + sslTruststorePassword;
}
return endpoint;
}
public class KafkaOffsetProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
KafkaManualCommit manual = exchange.getIn().getHeader(KafkaConstants.MANUAL_COMMIT, KafkaManualCommit.class);
manual.commit();
log.info("Committed Kafka offset");
}
}
The problem is that we systematically get this error when a message is consumed by a route :
Trace: java.util.ConcurrentModificationException: KafkaConsumer is not safe for multi-threaded access
at org.apache.kafka.clients.consumer.KafkaConsumer.acquire(KafkaConsumer.java:1824)
at org.apache.kafka.clients.consumer.KafkaConsumer.acquireAndEnsureOpen(KafkaConsumer.java:1808)
at org.apache.kafka.clients.consumer.KafkaConsumer.commitSync(KafkaConsumer.java:1255)
at org.apache.camel.component.kafka.DefaultKafkaManualCommit.commitOffset(DefaultKafkaManualCommit.java:60)
at org.apache.camel.component.kafka.DefaultKafkaManualCommit.commitSync(DefaultKafkaManualCommit.java:51)
My understanding is that the instance of KafkaConsumer is reused in multiple routes and therefore it generates the error, but it could be related to using SEDA endpoint as stated here (https://issues.apache.org/jira/browse/CAMEL-12722), which we dont explicitly do.
We tried injecting a KafkaComponent local bean in the route :
.templateBean("myKafkaConfiguration").typeClass("org.apache.camel.component.kafka.KafkaConfiguration").property("topic", "{{" + Constantes.TOPIC +"}}").properties(kafkaConfiguration)
.end()
.templateBean("myKafka").typeClass("org.apache.camel.component.kafka.KafkaComponent").property("configuration", "#{{myKafkaConfiguration}}")
.end()
.from("#{{myKafka}}")
But it ends up with another error because you cannot consume a Bean endpoint (https://camel.apache.org/components/3.18.x/bean-component.html)
How use a different KafkaConsumer for every created route ? Or, if the issue is SEDA related, how to make this route a direct route?
Thank you for your help
Related
I am not able to print the response from a Soap Webservice.
Seen few solutions by editing the generated stub code. But I cant edit the generated code as it gets restored to original form on every build. Looking for a solution where I can get the solution printed without change in generated code.
I am consuming the SOAP service from a Spring Boot microservice.
ServiceContext serviceConxt = omsSchedulingService._getServiceClient().getServiceContext();
OperationContext operationContext = serviceConxt.getLastOperationContext();
MessageContext inMessageContext = operationContext.getMessageContext("Out");
log.info(inMessageContext.getEnvelope().toString());
You can add a message handler for the soap message.
Then once you intercept the message with the handler, you can print out the response.
You will need to add the handler to the handler chain, depending on your project you can do that programatically or with config.
final class MyMessageHandler implements SOAPHandler<SOAPMessageContext>{
#Override
public void close(MessageContext context) {
handle(context);
}
private boolean handle(MessageContext context) {
if (context != null) {
try {
Object httpResponseCodeObj = context.get(SOAPMessageContext.HTTP_RESPONSE_CODE);
if (httpResponseCodeObj instanceof Integer)
httpResponseCode = ((Integer) httpResponseCodeObj).intValue();
if (context instanceof SOAPMessageContext) {
SOAPMessage message = ((SOAPMessageContext) context).getMessage();
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(512);
message.writeTo(byteOut);
String messageStr = byteOut.toString(getCharacterEncoding(message));
boolean outbound = Boolean.TRUE.equals(context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY));
Logger.info(loggingPrefix, outbound ? "SOAP request: " : "SOAP response: ", replaceNewLines(messageStr));
}
} catch (SOAPException e) {
Logger.error(e, loggingPrefix, "SOAPException: ", e.getMessage(), NEWLINE);
} catch (IOException e) {
Logger.error(e, loggingPrefix, "IOException: ", e.getMessage(), NEWLINE);
}
}
return true;
}
}
If you donĀ“t want to implement an interceptor the easiest way is to use the logging via vm arguments:
JAVA_OPTS=-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.showdatetime=true -Dorg.apache.commons.logging.simplelog.log.httpclient.wire=debug -Dorg.apache.commons.logging.simplelog.log.org.apache.commons.httpclient=debug
This way you should see the logging of your request / response with headers in console.
First you can get AxisConfiguration from client stub.
AxisConfiguration axisConf = stub._getServiceClient().getAxisConfiguration();
Processing incoming and outgoing messages is divided into phases. There is a list of phases (a flow) which is processed when everything works correctly (without errors) and also another for situations when some fault occurs e.g. when an exception is thrown during message processing. Every flow maybe incoming or outgoing so there are 4 flows altogether.
List<Phase> phasesIn = axisConf.getInFlowPhases(); // normal incoming communication i.e. response from webservice
List<Phase> phasesOut = axisConf.getOutFlowPhases(); // normal outgoing communication
List<Phase> phasesFaultIn = axisConf.getInFaultFlowPhases(); // faulty incoming communication e.g. when an exception occurs during message processing
List<Phase> phasesFaultOut = axisConf.getOutFaultFlowPhases(); // faulty outgoing communication
Some but not all phase names are defined in org.apache.axis2.phaseresolver.PhaseMetadata.
For example "Security" phase processed in Rampart module (module for Web Service Security) won't be found in PhaseMetadata.
You can add a handler to every phase, e.g.
for (Phase p : phasesOut) {
if (PhaseMetadata.PHASE_TRANSPORT_OUT.equals(p.getName())) {
p.addHandler(new MessageContentLoggerHandler());
}
}
Handler is a class which extends org.apache.axis2.handlers.AbstractHandler.
You just have to implement
public InvocationResponse invoke(MessageContext msgContext).
There you have access to MessageContext. Of course, you can get whole SOAP envelope like this:
msgContext.getEnvelope().toString()
and for example print it to your logs or save as a separate file.
Remember to put
return InvocationResponse.CONTINUE;
at the end of invoke method for a situation when handler processes the message successfully. Otherwise processing stops in this handler and a whole process won't get to any another phase.
If you need to see whole message with WSS headers, you can add your own phase. For example this adds your custom phase as the last in processing of outgoing message (so also after Rampart's security phase)
Phase phase = new Phase("SomePhase");
phase.addHandler(new SomeCustomHandler());
axisConf.getOutFlowPhases().add(phase);
Of course logging (and exposing in any other way) security headers in production environment is a very bad idea. Do it only for debugging purposes in some test environment.
For an application I am doing some tests with Spring Boot and RabbitMQ.
I set up a very simple Sender - Receiver application:
Sender:
public class Tut1Sender
{
private final Gson gson = new Gson();
#Autowired
private RabbitTemplate template;
#Autowired
private Queue queue;
public static int count = 1;
#Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() throws InterruptedException
{
String message = "Hello World! "+" Nr. "+count;
MessageObject mo = new MessageObject(message);
String toJson = gson.toJson(mo);
this.template.convertAndSend(queue.getName(), toJson);
System.out.println(" [x] Sent '" + toJson + "'");
Thread.sleep(5);
count++;
}
}
This part works just fine and fill my queue with messages.
Here is my receiver:
#RabbitListener(queues = "hello")
public class Tut1Receiver
{
private final Gson gson = new Gson();
#RabbitHandler
public void receive(String in) throws InterruptedException
{
System.out.println("Received Raw: " + in);
MessageObject fromJson = gson.fromJson(in, MessageObject.class);
System.out.println("Received Message '" + fromJson + "'");
int nextInt = ThreadLocalRandom.current().nextInt(1000, 5000);
System.out.println("Sleep for " + nextInt + " ms");
Thread.sleep(nextInt);
}
}
Messages created by the Sender are handled correctly by the receiver. I get a nice output, the message is acknowledged and deleted from the queue.
Then I put a message directly into the queue by the Web-GUI of RabbitMQ.
The sender grabs this message. I can say this because the message created by me switched from status "Ready" to "Unacked" (as displayed in Web-GUI)
The sender gave me no output.
Then I configured the ContainerFactory:
#Profile("receiver")
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory)
{
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setErrorHandler(e -> {
System.out.println("Error: "+e);
System.out.println("Raw: "+((ListenerExecutionFailedException) e).getFailedMessage().toString());
});
return factory;
}
Now I am getting the following error (in an endless loop)
Error: org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener method 'no match' threw exception
Raw: (Body:'[B#53452feb(byte[11])' MessageProperties [headers={content_type=text/plain, content_encoding=UTF-8}, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=true, receivedExchange=, receivedRoutingKey=hello, deliveryTag=1, consumerTag=NOT_SET, consumerQueue=hello])
How can I handle this error? The sender should just display the error, acknowledging the message and proceed with the next message.
What is the right way to handle faulty messages in general?
For broken message, consumers can reject or deliver the message. If you are sure the broken message can't be processed by any other consumers, you should tell the broker to discard the message or deliver it to a dead-letter-exchange.
From official document of spring amqp, I find:
Another alternative is to set the container's rejectRequeued property to false. This causes all failed messages to be discarded. When using RabbitMQ 2.8.x or higher, this also facilitates delivering the message to a Dead Letter Exchange.
Or, you can throw a AmqpRejectAndDontRequeueException; this prevents message requeuing, regardless of the setting of the rejectRequeued property.
I am working on a backend service which polls S3 bucket periodically using spring aws integration and processes the polled object from S3. Below is the implementation for it
#Configuration
#EnableIntegration
#IntegrationComponentScan
#EnableAsync
public class S3PollerConfiguration {
//private static final Logger log = (Logger) LoggerFactory.getLogger(S3PollerConfiguration.class);
#Value("${amazonProperties.bucketName}")
private String bucketName;
#Bean
#InboundChannelAdapter(value = "s3FilesChannel", poller = #Poller(fixedDelay = "5"))
public MessageSource<InputStream> s3InboundStreamingMessageSource() {
S3StreamingMessageSource messageSource = new S3StreamingMessageSource(template());
messageSource.setRemoteDirectory(bucketName);
return messageSource;
}
#Bean
public S3RemoteFileTemplate template() {
return new S3RemoteFileTemplate(new S3SessionFactory(thumbnailGeneratorService.getImagesS3Client()));
}
#Bean
public PollableChannel s3FilesChannel() {
return new QueueChannel();
}
#Bean
IntegrationFlow fileReadingFlow() throws IOException {
return IntegrationFlows
.from(s3InboundStreamingMessageSource(),
e -> e.poller(p -> p.fixedDelay(10, TimeUnit.SECONDS)))
.handle(Message.class, (payload, header) -> processS3Object(payload.getHeaders(), payload.getPayload()))
.get();
}
}
I am getting the messages from S3 on object upload and I am able to process it using the input stream received as part of message payload. But the problem I face here is that I get 'Time out waiting for connection from pool' exception after receiving few messages
2019-01-06 02:19:06.156 ERROR 11322 --- [ask-scheduler-5] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessagingException: Failed to execute on session; nested exception is com.amazonaws.SdkClientException: Unable to execute HTTP request: Timeout waiting for connection from pool
at org.springframework.integration.file.remote.RemoteFileTemplate.execute(RemoteFileTemplate.java:445)
at org.springframework.integration.file.remote.RemoteFileTemplate.list(RemoteFileTemplate.java:405)
at org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource.listFiles(AbstractRemoteFileStreamingMessageSource.java:194)
at org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource.poll(AbstractRemoteFileStreamingMessageSource.java:180)
at org.springframework.integration.aws.inbound.S3StreamingMessageSource.poll(S3StreamingMessageSource.java:70)
at org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource.doReceive(AbstractRemoteFileStreamingMessageSource.java:153)
at org.springframework.integration.endpoint.AbstractMessageSource.receive(AbstractMessageSource.java:155)
at org.springframework.integration.endpoint.SourcePollingChannelAdapter.receiveMessage(SourcePollingChannelAdapter.java:236)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:250)
I know that the issue is related to not closing the opened S3Object like stated here https://github.com/aws/aws-sdk-java/issues/1405 so I have implemented closing the input stream of the S3Object received as part of message payload. But that does not solve the issue and I keep getting the exceptions. Can someone help me to fix this issue ?
Your problem that you still mix Messaging Annotations declarations with Java DSL in your configuration.
Looks like in the fileReadingFlow you close those InputStreams in your code processS3Object() method, but you do nothing with InputStreams produced by the #InboundChannelAdapter(value = "s3FilesChannel", poller = #Poller(fixedDelay = "5")).
Why do you have it in fist place at all? What makes you to keep that code if you don't use it?
This S3StreamingMessageSource is polled all the time twice: by the #InboundChannelAdapter and IntegrationFlows.from().
You just have to remove that #InboundChannelAdapter from the S3StreamingMessageSource bean definition and that's all.
Please, read more Reference Manual to determine the reason of such an annotation and how you don't need it when you use Java DSL:
https://docs.spring.io/spring-integration/reference/html/configuration.html#_using_the_literal_inboundchanneladapter_literal_annotation
https://docs.spring.io/spring-integration/reference/html/java-dsl.html#java-dsl-inbound-adapters
I want to write a simple code sample which sends a message to an exchange, receive it with a listener and send a message back.
This is how I send the message:
private static void pingpong(Object messageListener) throws Exception {
ConnectionFactory cf = new CachingConnectionFactory("localhost");
// set up the queue, exchange, binding on the broker
RabbitAdmin admin = new RabbitAdmin(cf);
Queue queue = new Queue(QUEUE);
admin.declareQueue(queue);
TopicExchange exchange = new TopicExchange(EXCHANGE);
admin.declareExchange(exchange);
admin.declareBinding(BindingBuilder.bind(queue).to(exchange).with("foo.*"));
// set up the listener and container
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cf);
container.setMessageListener(messageListener);
container.setQueueNames(QUEUE);
container.start();
// send something and receive result.
RabbitTemplate template = new RabbitTemplate(cf);
Object result = template.convertSendAndReceive(EXCHANGE, "foo.bar", "ping");
System.out.println("Received: " + result);
}
The code for the listener:
public static class PingPongReceiver {
public String handleMessage(String in) {
System.out.println("Received: " + in);
return "pong";
}
}
This is how I call it:
pingpong(new MessageListenerAdapter(new PingPongReceiver()));
But sending the reply fails with the following exceptions:
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:865)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:760)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:680)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:93)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:183)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1345)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:661)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1096)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:1080)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$800(SimpleMessageListenerContainer.java:93)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1190)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.amqp.rabbit.listener.adapter.ReplyFailureException: Failed to send reply with payload 'pong'
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.handleResult(AbstractAdaptableMessageListener.java:213)
at org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter.onMessage(MessageListenerAdapter.java:296)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:757)
... 10 more
Caused by: org.springframework.amqp.AmqpException: Cannot determine ReplyTo message property value: Request message does not contain reply-to property, and no default response Exchange was set.
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.getReplyToAddress(AbstractAdaptableMessageListener.java:289)
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.handleResult(AbstractAdaptableMessageListener.java:209)
... 12 more
I have problems figuring out how to send the reply using an anonymous, non-durable queue. What am I missing?
What you are describing makes no sense; the RabbitTemplate.convertAndReceive() sets up the replyTo (Direct reply-to if the broker supports it, otherwise a temporary queue).
I just copied and pasted your code and it worked fine for me...
Received: ping
Received: pong
Perhaps you have an old, stale, message in the queue that doesn't have a replyTo?
You can purge the queue using the admin UI.
I also tested your code with
Queue queue = new AnonymousQueue();
...
container.setQueueNames(queue.getName());
and it worked fine too.
By the way, when creating Spring Beans manually, it's recommended that you call afterPropertiesSet (if the bean is an initializing bean).
container.afterPropertiesSet();
Finally, you should stop the container and destroy the connection:
container.stop();
cf.destroy();
I am testing simple producer to send messages on Kafka (0.8.2.1) using Apache Camel. I have created endpoint using java DSL in camel.
CamelContext ctx =new DefaultCamelContext();
PropertiesComponent properties=new PropertiesComponent();
properties.setLocation("com/camel/test/props.properties");
ctx.addComponent("properties",properties);
final String uri= "kafka://{{kafka.host}}?topic={{topic}}&zookeeperHost={{zookeeperHost}}&zookeeperPort={{zookeeperPort}}";
String uriParams = "&metadata.broker.list={{metadata.broker.list}";
ctx.addRoutes(new RouteBuilder() {
public void configure() { //
from(uri+"&groupId={{groupId}}")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange.getIn().getBody());
}
})
;
}
});
ctx.start();
ProducerTemplate tmp = ctx.createProducerTemplate();
tmp.sendBody(ctx.getEndpoint(uri), "my test is working");// Error occurs here
now I want to send message on kafka using ProducerTempalte provided by Apache Camel. but I get below error when runs the program
Note: Zookeeper & Kafka are up and can produce/consume messages using kafka console.
Exception in thread "main" org.apache.camel.FailedToCreateProducerException: Failed to create Producer for endpoint: Endpoint[kafka://localhost:9092?topic=test&zookeeperHost=localhost&zookeeperPort=2181]. Reason: java.lang.NullPointerException
at org.apache.camel.impl.ProducerCache.doGetProducer(ProducerCache.java:407)
at org.apache.camel.impl.ProducerCache.doInProducer(ProducerCache.java:220)
at org.apache.camel.impl.ProducerCache.sendExchange(ProducerCache.java:343)
at org.apache.camel.impl.ProducerCache.send(ProducerCache.java:184)
at org.apache.camel.impl.DefaultProducerTemplate.send(DefaultProducerTemplate.java:124)
at org.apache.camel.impl.DefaultProducerTemplate.sendBody(DefaultProducerTemplate.java:137)
at com.camel.test.CamelTest.main(CamelTest.java:45)
Caused by: java.lang.NullPointerException
at java.util.Hashtable.put(Hashtable.java:514)
at org.apache.camel.component.kafka.KafkaProducer.getProps(KafkaProducer.java:54)
at org.apache.camel.component.kafka.KafkaProducer.doStart(KafkaProducer.java:61)
at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
at org.apache.camel.impl.DefaultCamelContext.startService(DefaultCamelContext.java:2869)
at org.apache.camel.impl.DefaultCamelContext.doAddService(DefaultCamelContext.java:1097)
at org.apache.camel.impl.DefaultCamelContext.addService(DefaultCamelContext.java:1058)
at org.apache.camel.impl.ProducerCache.doGetProducer(ProducerCache.java:405)
... 6 more
I guess the properties are not set for the producer but have no idea how to set in producer template.
The uri should have the broker list as the server name (dont blame me for the syntax I didnt create this component).
final String uri= "kafka://{{metadata.broker.list}}?topic={{topic}}&zookeeperHost={{zookeeperHost}}&zookeeperPort={{zookeeperPort}}";
I was able to find the solution by debugging. By default ProducerTemplate need default parameters which are not set when new object is created (this might be a bug in API).
So I found a way to send params through URI. where below params are mandatory
metadata.broker.list (as URI param)
request.required.acks (Already set By default)
producer.type (Not required in this case but needed for other APIs)
serializer.class (Already set by default)
partitioner (class) (as URI param)
PARTITION_KEY (as Header)
We do not have an option to send param for Partition_key so need to add it in Header. So use sendBodyAndHeader method to send producer message.
ProducerTemplate tmp = ctx.createProducerTemplate();
tmp.setDefaultEndpoint(ctx.getEndpoint(uri+"&partitioner={{partitioner.class}}"));
ctx.start();
tmp.sendBodyAndHeader("my test is working "+(new Random()).nextInt(100), KafkaConstants.PARTITION_KEY, 1);
tmp.stop();
ctx.stop();