/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.kafka;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelException;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.TypeConverter;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.DefaultMessage;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.errors.ApiException;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class KafkaProducerTest {
private KafkaProducer producer;
private KafkaEndpoint endpoint;
private KafkaEndpoint fromEndpoint;
private TypeConverter converter = Mockito.mock(TypeConverter.class);
private CamelContext context = Mockito.mock(CamelContext.class);
private Exchange exchange = Mockito.mock(Exchange.class);
private Message in = new DefaultMessage();
private Message out = new DefaultMessage();
private AsyncCallback callback = Mockito.mock(AsyncCallback.class);
@SuppressWarnings({"unchecked"})
public KafkaProducerTest() throws Exception {
KafkaComponent kafka = new KafkaComponent(new DefaultCamelContext());
kafka.setBrokers("broker1:1234,broker2:4567");
endpoint = kafka.createEndpoint("kafka:sometopic", "sometopic", new HashMap());
producer = new KafkaProducer(endpoint);
fromEndpoint = kafka.createEndpoint("kafka:fromtopic", "fromtopic", new HashMap());
RecordMetadata rm = new RecordMetadata(null, 1, 1);
Future future = Mockito.mock(Future.class);
Mockito.when(future.get()).thenReturn(rm);
org.apache.kafka.clients.producer.KafkaProducer kp = Mockito.mock(org.apache.kafka.clients.producer.KafkaProducer.class);
Mockito.when(kp.send(Matchers.any(ProducerRecord.class))).thenReturn(future);
Mockito.when(exchange.getContext()).thenReturn(context);
Mockito.when(context.getTypeConverter()).thenReturn(converter);
Mockito.when(converter.tryConvertTo(String.class, exchange, null)).thenReturn(null);
producer.setKafkaProducer(kp);
producer.setWorkerPool(Executors.newFixedThreadPool(1));
}
@Test
public void testPropertyBuilder() throws Exception {
Properties props = producer.getProps();
assertEquals("broker1:1234,broker2:4567", props.getProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG));
}
@Test
@SuppressWarnings({"unchecked"})
public void processSendsMessage() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
in.setHeader(KafkaConstants.PARTITION_KEY, 4);
producer.process(exchange);
Mockito.verify(producer.getKafkaProducer()).send(Matchers.any(ProducerRecord.class));
assertRecordMetadataExists();
}
@Test(expected = Exception.class)
@SuppressWarnings({"unchecked"})
public void processSendsMessageWithException() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
// setup the exception here
org.apache.kafka.clients.producer.KafkaProducer kp = producer.getKafkaProducer();
Mockito.when(kp.send(Matchers.any(ProducerRecord.class))).thenThrow(new ApiException());
Mockito.when(exchange.getIn()).thenReturn(in);
in.setHeader(KafkaConstants.PARTITION_KEY, 4);
producer.process(exchange);
assertRecordMetadataExists();
}
@Test
public void processAsyncSendsMessage() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
in.setHeader(KafkaConstants.PARTITION_KEY, 4);
producer.process(exchange, callback);
ArgumentCaptor<Callback> callBackCaptor = ArgumentCaptor.forClass(Callback.class);
Mockito.verify(producer.getKafkaProducer()).send(Matchers.any(ProducerRecord.class), callBackCaptor.capture());
Callback kafkaCallback = callBackCaptor.getValue();
kafkaCallback.onCompletion(new RecordMetadata(null, 1, 1), null);
assertRecordMetadataExists();
}
@Test
public void processAsyncSendsMessageWithException() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
// setup the exception here
org.apache.kafka.clients.producer.KafkaProducer kp = producer.getKafkaProducer();
Mockito.when(kp.send(Matchers.any(ProducerRecord.class), Matchers.any(Callback.class))).thenThrow(new ApiException());
in.setHeader(KafkaConstants.PARTITION_KEY, 4);
producer.process(exchange, callback);
ArgumentCaptor<Callback> callBackCaptor = ArgumentCaptor.forClass(Callback.class);
Mockito.verify(producer.getKafkaProducer()).send(Matchers.any(ProducerRecord.class), callBackCaptor.capture());
Mockito.verify(exchange).setException(Matchers.isA(ApiException.class));
Mockito.verify(callback).done(Matchers.eq(true));
Callback kafkaCallback = callBackCaptor.getValue();
kafkaCallback.onCompletion(new RecordMetadata(null, 1, 1), null);
assertRecordMetadataExists();
}
@Test
public void processSendsMessageWithTopicHeaderAndNoTopicInEndPoint() throws Exception {
endpoint.getConfiguration().setTopic(null);
Mockito.when(exchange.getIn()).thenReturn(in);
in.setHeader(KafkaConstants.TOPIC, "anotherTopic");
Mockito.when(exchange.getOut()).thenReturn(out);
producer.process(exchange);
verifySendMessage("anotherTopic");
assertRecordMetadataExists();
}
@Test
public void processSendsMessageWithTopicHeaderAndEndPoint() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
in.setHeader(KafkaConstants.PARTITION_KEY, 4);
in.setHeader(KafkaConstants.TOPIC, "anotherTopic");
in.setHeader(KafkaConstants.KEY, "someKey");
producer.process(exchange);
verifySendMessage(4, "anotherTopic", "someKey");
assertRecordMetadataExists();
}
@Test(expected = CamelException.class)
public void processRequiresTopicInEndpointOrInHeader() throws Exception {
endpoint.getConfiguration().setTopic(null);
Mockito.when(exchange.getIn()).thenReturn(in);
in.setHeader(KafkaConstants.PARTITION_KEY, "4");
producer.process(exchange);
assertRecordMetadataExists();
}
@Test
public void processDoesNotRequirePartitionHeader() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
producer.process(exchange);
assertRecordMetadataExists();
}
@Test
public void processSendsMessageWithPartitionKeyHeader() throws Exception {
endpoint.getConfiguration().setTopic("someTopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
in.setHeader(KafkaConstants.PARTITION_KEY, 4);
in.setHeader(KafkaConstants.KEY, "someKey");
producer.process(exchange);
verifySendMessage(4, "someTopic", "someKey");
assertRecordMetadataExists();
}
@Test
public void processSendsMessageWithMessageKeyHeader() throws Exception {
endpoint.getConfiguration().setTopic("someTopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
in.setHeader(KafkaConstants.KEY, "someKey");
producer.process(exchange);
verifySendMessage("someTopic", "someKey");
assertRecordMetadataExists();
}
@Test
public void processSendMessageWithBridgeEndpoint() throws Exception {
endpoint.getConfiguration().setTopic("someTopic");
endpoint.getConfiguration().setBridgeEndpoint(true);
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
in.setHeader(KafkaConstants.TOPIC, "anotherTopic");
in.setHeader(KafkaConstants.KEY, "someKey");
in.setHeader(KafkaConstants.PARTITION_KEY, 4);
producer.process(exchange);
verifySendMessage(4, "someTopic", "someKey");
assertRecordMetadataExists();
}
@Test
public void processSendMessageWithCircularDetected() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
endpoint.getConfiguration().setCircularTopicDetection(true);
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
Mockito.when(exchange.getFromEndpoint()).thenReturn(fromEndpoint);
// this is the from topic that are from the fromEndpoint
in.setHeader(KafkaConstants.TOPIC, "fromtopic");
in.setHeader(KafkaConstants.KEY, "somekey");
producer.process(exchange);
verifySendMessage("sometopic", "somekey");
assertRecordMetadataExists();
}
@Test
public void processSendMessageWithNoCircularDetected() throws Exception {
endpoint.getConfiguration().setTopic("sometopic");
endpoint.getConfiguration().setCircularTopicDetection(false);
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
Mockito.when(exchange.getFromEndpoint()).thenReturn(fromEndpoint);
// this is the from topic that are from the fromEndpoint
in.setHeader(KafkaConstants.TOPIC, "fromtopic");
in.setHeader(KafkaConstants.KEY, "somekey");
producer.process(exchange);
// will end up sending back to itself at fromtopic
verifySendMessage("fromtopic", "somekey");
assertRecordMetadataExists();
}
@Test // Message and Topic Name alone
public void processSendsMessageWithMessageTopicName() throws Exception {
endpoint.getConfiguration().setTopic("someTopic");
Mockito.when(exchange.getIn()).thenReturn(in);
Mockito.when(exchange.getOut()).thenReturn(out);
producer.process(exchange);
verifySendMessage("someTopic");
assertRecordMetadataExists();
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected void verifySendMessage(Integer partitionKey, String topic, String messageKey) {
ArgumentCaptor<ProducerRecord> captor = ArgumentCaptor.forClass(ProducerRecord.class);
Mockito.verify(producer.getKafkaProducer()).send(captor.capture());
assertEquals(partitionKey, captor.getValue().partition());
assertEquals(messageKey, captor.getValue().key());
assertEquals(topic, captor.getValue().topic());
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected void verifySendMessage(String topic, String messageKey) {
ArgumentCaptor<ProducerRecord> captor = ArgumentCaptor.forClass(ProducerRecord.class);
Mockito.verify(producer.getKafkaProducer()).send(captor.capture());
assertEquals(messageKey, captor.getValue().key());
assertEquals(topic, captor.getValue().topic());
}
@SuppressWarnings({"unchecked", "rawtypes"})
protected void verifySendMessage(String topic) {
ArgumentCaptor<ProducerRecord> captor = ArgumentCaptor.forClass(ProducerRecord.class);
Mockito.verify(producer.getKafkaProducer()).send(captor.capture());
assertEquals(topic, captor.getValue().topic());
}
private void assertRecordMetadataExists() {
List<RecordMetadata> recordMetaData1 = (List<RecordMetadata>) in.getHeader(KafkaConstants.KAFKA_RECORDMETA);
assertTrue(recordMetaData1 != null);
assertEquals("Expected one recordMetaData", recordMetaData1.size(), 1);
assertTrue(recordMetaData1.get(0) != null);
}
}