/*******************************************************************************
* Copyright (C) 2015, MOHAMED-ALI SAID
* All Rights Reserved
*******************************************************************************/
/* Generated by Streams Studio: March 26, 2014 11:37:11 AM EDT */
package com.ibm.streamsx.messaging.rabbitmq;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import com.ibm.streams.operator.OperatorContext;
import com.ibm.streams.operator.StreamingInput;
import com.ibm.streams.operator.Tuple;
import com.ibm.streams.operator.logging.TraceLevel;
import com.ibm.streams.operator.model.InputPortSet;
import com.ibm.streams.operator.model.InputPorts;
import com.ibm.streams.operator.model.Parameter;
import com.ibm.streams.operator.model.PrimitiveOperator;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* This operator was originally contributed by Mohamed-Ali Said @saidmohamedali
*/
@InputPorts(@InputPortSet(cardinality = 1, optional = false, description = ""))
@PrimitiveOperator(name = "RabbitMQSink", description = RabbitMQSink.DESC)
public class RabbitMQSink extends RabbitMQBaseOper {
private final Logger trace = Logger.getLogger(RabbitMQSink.class
.getCanonicalName());
Integer deliveryMode = 1;
int maxMessageSendRetries = 0;
int messageSendRetryDelay = 10000;
private boolean firstConnection = true;
@Override
public synchronized void initialize(OperatorContext context)
throws Exception {
// Must call super.initialize(context) to correctly setup an operator.
super.initialize(context);
super.initSchema(getInput(0).getStreamSchema());
trace.log(TraceLevel.INFO, "Operator " + context.getName() + " initializing in PE: " //$NON-NLS-1$ //$NON-NLS-2$
+ context.getPE().getPEId() + " in Job: " //$NON-NLS-1$
+ context.getPE().getJobId());
}
@Override
public synchronized void allPortsReady() throws Exception {
OperatorContext context = getOperatorContext();
trace.log(TraceLevel.INFO, "Operator " + context.getName() //$NON-NLS-1$
+ " all ports are ready in PE: " + context.getPE().getPEId() //$NON-NLS-1$
+ " in Job: " + context.getPE().getJobId()); //$NON-NLS-1$
}
@SuppressWarnings("unchecked")
@Override
public void process(StreamingInput<Tuple> stream, Tuple tuple) throws Exception {
// Our first time, we need to setup our connection
if (firstConnection) {
firstConnection = false;
try {
readyForShutdown = false;
initializeRabbitChannelAndConnection();
} finally {
readyForShutdown = true;
}
}
// Handle case of lost connection/failed authentication
// but we have new credentials from appConfig
if (isConnected.getValue() == 0
&& newCredentialsExist()){
try {
readyForShutdown = false;
resetRabbitClient();
} finally {
readyForShutdown = true;
}
}
byte[] message = messageAH.getBytes(tuple);
String routingKey = ""; //$NON-NLS-1$
Map<String, Object> headers = new HashMap<String, Object>();
if (routingKeyAH.isAvailable()) {
routingKey = tuple.getString(routingKeyAH.getName());
}
BasicProperties.Builder propsBuilder = new BasicProperties.Builder();
if (messageHeaderAH.isAvailable()) {
headers = (Map<String, Object>) tuple.getMap(messageHeaderAH.getName());
propsBuilder.headers(headers);
}
propsBuilder.deliveryMode(deliveryMode);
try {
if (trace.isLoggable(TraceLevel.DEBUG))
trace.log(TraceLevel.DEBUG,
"Producing message: " + message.toString() + " in thread: " + Thread.currentThread().getName()); //$NON-NLS-1$ //$NON-NLS-2$
channel.basicPublish(exchangeName, routingKey, propsBuilder.build(), message);
if (isConnected.getValue() == 0){
// We succeeded at publish, so we must be connected
// Adding this to deal with an issue where we catch
// a stale AuthorizationException that makes us look
// disconnected
isConnected.setValue(1);
}
} catch (Exception e) {
trace.log(TraceLevel.ERROR, "Exception message:" + e.getMessage() + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$
handleFailedPublish(message, routingKey, propsBuilder);
}
}
private void handleFailedPublish(byte[] message, String routingKey, BasicProperties.Builder propsBuilder) {
Boolean failedToSend = true;
int attemptCount = 0;
while (failedToSend && attemptCount < maxMessageSendRetries) {
attemptCount++;
try {
Thread.sleep(messageSendRetryDelay);
trace.log(TraceLevel.ERROR, "Attempting to resend. Try number: " + attemptCount); //$NON-NLS-1$
channel.basicPublish(exchangeName, routingKey, propsBuilder.build(), message);
failedToSend = false;
} catch (Exception e1) {
e1.printStackTrace();
}
}
// if we still can't send after the number of maxMessageSendRetries,
// we want to log error and move on
if (failedToSend) {
trace.log(TraceLevel.ERROR, "Failed to send message after " + attemptCount + " attempts."); //$NON-NLS-1$ //$NON-NLS-2$
}
}
@Parameter(optional = true, description = "Name of the RabbitMQ exchange to send messages to. To use default RabbitMQ exchange, use empty quotes or do not specify: \\\"\\\".")
public void setExchangeName(String value) {
exchangeName = value;
}
@Parameter(optional = true, description = "Marks message as persistent(2) or non-persistent(1). Default as 1. ")
public void setDeliveryMode(Integer value) {
deliveryMode = value;
}
@Parameter(optional = true, description = "This optional parameter specifies the number of successive retries that are attempted for a message if a failure occurs when the message is sent. The default value is zero; no retries are attempted.")
public void setMaxMessageSendRetries(int value) {
maxMessageSendRetries = value;
}
@Parameter(optional = true, description = "This optional parameter specifies the time in milliseconds to wait before the next delivery attempt. If the maxMessageSendRetries is specified, you must also specify a value for this parameter.")
public void setMessageSendRetryDelay(int value) {
messageSendRetryDelay = value;
}
@Override
public synchronized void shutdown() throws Exception {
super.shutdown();
}
public static final String DESC =
"This operator acts as a RabbitMQ producer, sending messages to a RabbitMQ broker. " + //$NON-NLS-1$
"The broker is assumed to be already configured and running. " + //$NON-NLS-1$
"The incoming stream can have three attributes: message, routing_key, and messageHeader. " + //$NON-NLS-1$
"The message is a required attribute. " + //$NON-NLS-1$
"The exchange name, queue name, and routing key can be specified using parameters. " + //$NON-NLS-1$
"If a specified exchange does not exist, it will be created as a non-durable exchange. " + //$NON-NLS-1$
"All exchanges created by this operator are non-durable and auto-delete." + //$NON-NLS-1$
"This operator supports direct, fanout, and topic exchanges. It does not support header exchanges. " + //$NON-NLS-1$
"Messages are non-persistent and sending will only be attempted once by default. " + //$NON-NLS-1$
"This behavior can be modified using the deliveryMode and maxMessageSendRetries parameters. " + //$NON-NLS-1$
"\\n\\n**Behavior in a Consistent Region**" + //$NON-NLS-1$
"\\nThis operator can participate in a consistent region. It cannot be the start of a consistent region. " + //$NON-NLS-1$
BASE_DESC
;
}