/* * Copyright 2013 ZANOX AG * * 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.zanox.vertx.mods; import com.timgroup.statsd.NoOpStatsDClient; import com.timgroup.statsd.NonBlockingStatsDClient; import com.timgroup.statsd.StatsDClient; import com.zanox.vertx.mods.handlers.MessageHandler; import com.zanox.vertx.mods.internal.MessageSerializerType; import kafka.common.FailedToSendMessageException; import kafka.javaapi.producer.Producer; import org.vertx.java.busmods.BusModBase; import org.vertx.java.core.Handler; import org.vertx.java.core.eventbus.Message; import org.vertx.java.core.json.JsonObject; import java.util.Properties; import static com.zanox.vertx.mods.internal.EventProperties.*; import static com.zanox.vertx.mods.internal.KafkaProperties.*; import static com.zanox.vertx.mods.internal.StatsDProperties.*; /** * This verticle is responsible for processing messages. * It subscribes to Vert.x's specific EventBus address to handle messages published by other verticals * and sends messages to Kafka Broker. */ public class KafkaMessageProcessor extends BusModBase implements Handler<Message<JsonObject>> { private Producer producer; private String topic; private MessageSerializerType serializerType; private MessageHandlerFactory messageHandlerFactory; private KafkaProducerFactory kafkaProducerFactory; private StatsDClient statsDClient; public KafkaMessageProcessor() { messageHandlerFactory = new MessageHandlerFactory(); kafkaProducerFactory = new KafkaProducerFactory(); } @Override public void start() { super.start(); topic = getOptionalStringConfig(KAFKA_TOPIC, DEFAULT_TOPIC); serializerType = MessageSerializerType.getEnum(getOptionalStringConfig(SERIALIZER_CLASS, MessageSerializerType.BYTE_SERIALIZER.toString())); producer = createProducer(); statsDClient = createStatsDClient(); // Get the address of EventBus where the message was published final String address = getMandatoryStringConfig("address"); vertx.eventBus().registerHandler(address, this); } @Override public void stop() { if (producer != null) { producer.close(); } if (statsDClient != null) { statsDClient.stop(); } } @Override public void handle(Message<JsonObject> event) { sendMessageToKafka(producer, event); } /** * Returns an initialized instance of kafka producer. * * @return initialized kafka producer */ private Producer createProducer() { final Properties props = new Properties(); final String brokerList = getOptionalStringConfig(BROKER_LIST, DEFAULT_BROKER_LIST); final int requestAcks = getOptionalIntConfig(REQUEST_ACKS, DEFAULT_REQUEST_ACKS); props.put(BROKER_LIST, brokerList); props.put(SERIALIZER_CLASS, serializerType.getValue()); props.put(REQUEST_ACKS, String.valueOf(requestAcks)); props.put(KEY_SERIALIZER_CLASS, DEFAULT_KEY_SERIALIZER_CLASS); // always use String serializer for the key return kafkaProducerFactory.createProducer(serializerType, props); } /** * Returns an initialized instance of the StatsDClient If StatsD is enabled * this is a NonBlockingStatsDClient which guarantees not to block the thread or * throw exceptions. If StatsD is not enabled it creates a NoOpStatsDClient which * contains all empty methods * * @return initialized StatsDClient */ protected StatsDClient createStatsDClient() { final boolean enabled = getOptionalBooleanConfig(STATSD_ENABLED, DEFAULT_STATSD_ENABLED); if (enabled) { final String prefix = getOptionalStringConfig(STATSD_PREFIX, DEFAULT_STATSD_PREFIX); final String host = getOptionalStringConfig(STATSD_HOST, DEFAULT_STATSD_HOST); final int port = getOptionalIntConfig(STATSD_PORT, DEFAULT_STATSD_PORT); return new NonBlockingStatsDClient(prefix, host, port); } else { return new NoOpStatsDClient(); } } /** * Sends messages to Kafka topic using specified properties in kafka.properties file. * * @param producer kafka producer provided by the caller * @param event event that should be sent to Kafka Broker */ protected void sendMessageToKafka(Producer producer, Message<JsonObject> event) { if (!isValid(event.body().getString(PAYLOAD))) { logger.error("Invalid kafka message provided. Message not sent to kafka..."); sendError(event, String.format("Invalid kafka message provided. Property [%s] is not set.", PAYLOAD)) ; return; } try { final MessageHandler messageHandler = messageHandlerFactory.createMessageHandler(serializerType); String topic = isValid(event.body().getString(TOPIC)) ? event.body().getString(TOPIC) : getTopic(); String partKey = event.body().getString(PART_KEY); long startTime = System.currentTimeMillis(); messageHandler.send(producer, topic, partKey, event.body()); statsDClient.recordExecutionTime("submitted", (System.currentTimeMillis()-startTime)); sendOK(event); logger.info(String.format("Message sent to kafka topic: %s. Payload: %s", topic, event.body().getString(PAYLOAD))); } catch (FailedToSendMessageException ex) { sendError(event, "Failed to send message to Kafka broker...", ex); } } private boolean isValid(String str) { return str != null && !str.isEmpty(); } protected String getTopic() { return topic; } protected MessageSerializerType getSerializerType() { return serializerType; } }