/**
* Copyright (C) 2014 Stratio (http://stratio.com)
*
* Licensed 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 com.stratio.ingestion.sink.kafka;
import java.util.Properties;
import org.apache.flume.Channel;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.Transaction;
import org.apache.flume.conf.Configurable;
import org.apache.flume.conf.ConfigurationException;
import org.apache.flume.sink.AbstractSink;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
//@formatter:off
/**
*
* <p>Reads events from a channel and writes them to Kafka. </p>.
*
* Configuration parameters are:
*
* <p>
* <ul>
* <li><tt>topic</tt>: Name of topic where event will be sent to. Defaults to <tt>test</tt>.</li>
* <li><tt>writeBody</tt>: true to send body in raw String format and false to send headers in json String format. Default: False (Send only headers). </li>
* <li><tt>kafka.<kafka-producer-property></tt>: This sink accept any kafka producer property. Just write it after prefix <tt>kafka.</tt>.</li>
* </ul>
* </p>
*
* <code>
* a.sinks.kafkaSink.topic = Test
* a.sinks.kafkaSink.writeBody = false
* a.sinks.kafkaSink.kafka.serializer = kafka.serializer.StringEncoder
* a.sinks.kafkaSink.kafka.metadata.broker.list = localhost:9092
* </code>
*
*/
//@formatter:on
public class KafkaSink extends AbstractSink implements Configurable {
private static final Logger log = LoggerFactory.getLogger(KafkaSink.class);
private static final String CONF_TOPIC = "topic";
private static final String CONF_WRITE_BODY = "writeBody";
private static final String CONF_KAFKA = "kafka.";
private static final Boolean DEFAULT_WRITE_BODY = Boolean.FALSE;
private String topic;
private Producer<String, String> producer;
private ObjectMapper mapper;
private boolean writeBody;
public KafkaSink(){}
@Override
public void configure(Context context) {
topic = context.getString(CONF_TOPIC);
if (topic == null) {
throw new ConfigurationException("Kafka topic must be specified.");
}
writeBody = context.getBoolean(CONF_WRITE_BODY, DEFAULT_WRITE_BODY);
ImmutableMap<String, String> subProperties = context.getSubProperties(CONF_KAFKA);
Properties properties = new Properties();
properties.putAll(subProperties);
producer = new Producer<String, String>(new ProducerConfig(properties));
mapper = new ObjectMapper();
}
@Override
public Status process() throws EventDeliveryException {
Channel channel = getChannel();
Transaction tx = channel.getTransaction();
try {
tx.begin();
Event event = channel.take();
if (event == null) {
tx.commit();
return Status.READY;
}
String data = null;
if(writeBody){
data = new String(event.getBody());
} else {
data = mapper.writeValueAsString(event.getHeaders());
}
producer.send(new KeyedMessage<String, String>(topic, data));
tx.commit();
return Status.READY;
} catch (Exception e) {
try {
tx.rollback();
return Status.BACKOFF;
} catch (Exception e2) {
log.error("Rollback Exception:{}", e2);
}
log.error("KafkaSink Exception:{}", e);
return Status.BACKOFF;
} finally {
tx.close();
}
}
@Override
public synchronized void start() {
super.start();
}
@Override
public synchronized void stop() {
producer.close();
super.stop();
}
@Override
public String toString() {
return this.getClass().getName();
}
}