/*
* Copyright [2012] [ShopWiki]
*
* 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.shopwiki.roger.rpc;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import org.codehaus.jackson.type.TypeReference;
import com.rabbitmq.client.*;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.shopwiki.roger.MessagingUtil;
/**
* Implementation of RabbitMQ's Consumer interface.
* The user is not expected to use this class directly.
* See {@link RpcWorker} instead.
*
* @author rstewart
*
* @param <I> request type (I is for input).
*/
class RequestConsumer<I> extends DefaultConsumer {
private static final boolean DEBUG = MessagingUtil.DEBUG;
private static final String hostname;
static {
String s = null;
try {
s = InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
// do nothing
}
hostname = s;
}
private final RequestHandler<I,?> handler;
private final TypeReference<I> requestType;
private final Channel channel;
private final String queueName;
private final PostProcessors pps;
public static <I> void start(RequestHandler<I,?> handler, List<Channel> channels, String queueName, PostProcessors postProcessors) throws IOException {
for (Channel channel : channels) {
Consumer consumer = new RequestConsumer<I>(handler, channel, queueName, postProcessors);
channel.basicConsume(queueName, false, consumer);
}
}
private RequestConsumer(RequestHandler<I,?> handler, Channel channel, String queueName, PostProcessors pps) {
super(channel);
this.handler = handler;
this.requestType = handler.getRequestType();
this.channel = channel;
this.queueName = queueName;
this.pps = pps;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties requestProps, byte[] body) throws IOException {
I request = null;
ResponseStatus status = null;
Object response = null;
Throwable thrown = null;
long timeTaken = -1;
try {
long startTime = System.currentTimeMillis();
try {
request = MessagingUtil.getDeliveryBody(body, requestType);
} catch (Exception e) {
response = new ExceptionResponse(e);
status = ResponseStatus.MALFORMED_REQUEST;
thrown = e;
}
if (DEBUG) {
System.out.println("*** RequestHandler " + handler.getClass().getCanonicalName() + " RECEIVED REQUEST ***");
System.out.println("*** consumerTag: " + consumerTag);
System.out.println("*** envelope:\n" + MessagingUtil.prettyPrint(envelope));
System.out.println("*** requestProps:\n" + MessagingUtil.prettyPrint(requestProps));
System.out.println("*** request: " + MessagingUtil.prettyPrintMessage(request));
System.out.println();
}
if (request != null) {
try {
response = handler.handleRequest(request);
status = ResponseStatus.OK;
} catch (NackException e) {
thrown = e;
channel.basicNack(envelope.getDeliveryTag(), false, true);
return;
} catch (IllegalArgumentException e) {
response = new ExceptionResponse(e);
status = ResponseStatus.INVALID_REQUEST;
thrown = e;
} catch (Throwable e) {
response = new ExceptionResponse(e);
status = ResponseStatus.HANDLER_ERROR;
thrown = e;
}
}
timeTaken = System.currentTimeMillis() - startTime;
// Ack before or after the reply is sent ???
channel.basicAck(envelope.getDeliveryTag(), false);
String replyTo = requestProps.getReplyTo();
String correlationId = requestProps.getCorrelationId();
if (correlationId != null && replyTo != null) {
Map<String,Object> headers = new LinkedHashMap<String,Object>();
headers.put("status", String.valueOf(status));
headers.put("handler_time_millis", String.valueOf(timeTaken));
headers.put("hostname", hostname);
BasicProperties.Builder replyProps = new BasicProperties.Builder();
replyProps = replyProps.headers(headers);
replyProps = replyProps.correlationId(correlationId);
// TODO: delivery mode ???
MessagingUtil.sendResponse(channel, replyTo, response, replyProps);
}
} catch (Throwable e) {
status = ResponseStatus.NUCLEAR;
thrown = e;
} finally {
if (pps != null) {
pps.process(handler, queueName, status, request, response, thrown, timeTaken);
}
}
}
}