package com.cloudhopper.mq.broker;
/*
* #%L
* ch-mq
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/
import com.cloudhopper.mq.broker.protocol.MaxTransferAttemptsCountException;
import com.cloudhopper.mq.broker.protocol.MaxTransferCountException;
import com.cloudhopper.mq.broker.protocol.ProtocolConstants;
import com.cloudhopper.mq.broker.protocol.ProtocolFactory;
import com.cloudhopper.mq.broker.protocol.ProtocolParsingException;
import com.cloudhopper.mq.broker.protocol.ProtocolResponseUtil;
import com.cloudhopper.mq.broker.protocol.ProtocolResultCodeException;
import com.cloudhopper.mq.broker.protocol.TransferResponse;
import com.cloudhopper.mq.message.MQMessage;
import com.cloudhopper.mq.transcoder.TranscoderWrapped;
import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Response;
import java.io.IOException;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to encapsulate the transfer of an item to a RemoteQueue via
* an AsyncHttpClient.
*
* @author garth
*/
public class AsyncRemoteQueueTransfer {
private static final Logger logger = LoggerFactory.getLogger(AsyncRemoteQueueTransfer.class);
private final AsyncHttpClient http;
private final DistributedQueueConfiguration dqconfig;
private final RemoteQueueTransferListener listener;
private final RemoteQueueInfo remoteQueue;
private final TransferItem transferItem;
public AsyncRemoteQueueTransfer(AsyncHttpClient http, DistributedQueueConfiguration dqconfig, RemoteQueueTransferListener listener, RemoteQueueInfo remoteQueue, TransferItem transferItem) {
this.http = http;
this.dqconfig = dqconfig;
this.listener = listener;
this.remoteQueue = remoteQueue;
this.transferItem = transferItem;
}
private synchronized String getNextRemoteBroker() throws NoMoreRemoteBrokersException {
// first, let's get the URL we'll try to transfer to
String remoteBroker = remoteQueue.getNextRemoteBroker();
// are there any more remote brokers?
if (remoteBroker == null) {
throw new NoMoreRemoteBrokersException("No more RemoteBrokers available for RemoteQueue [" + remoteQueue.getName() + "] since call to getNextRemoteBroker() returned null during transfer attempt");
}
return remoteBroker;
}
private RemoteBrokerInfo getBrokerInfo(String url) {
// could throw an NPE if the broker has vanished
return remoteQueue.getRemoteBrokers().get(url).getBroker();
}
public void transfer(final TransferResponseHandler handler)
{
//
// enforce max "TransferCount" policy
// temporarily increase current transfer count value by 1 and see if it
// would exceed the maximum policy configured for this distributed queue
//
byte transferCount = (byte)transferItem.getMessage().getTransferCount();
if (transferCount+1 > dqconfig.getMaxTransferCount()) {
handler.onThrowable(new MaxTransferCountException("Max transfer count [" + transferCount + "] reached for item"));
return;
}
// get the URL we'll try to transfer to next
// NOTE: if there are no more brokers, this will throw NoMoreRemoteBrokersException
String nextBroker = null;
try {
nextBroker = getNextRemoteBroker();
} catch (NoMoreRemoteBrokersException e) {
handler.onThrowable(e);
return;
}
final String remoteBrokerUrl = new String(nextBroker);
final RemoteBrokerInfo brokerInfo = getBrokerInfo(nextBroker);
//
// enforce max "TransferAttemptsCount" policy
//
if (transferItem.getMessage().getTransferAttemptsCount()+1 > dqconfig.getMaxTransferAttemptsCount()) {
handler.onThrowable(new MaxTransferAttemptsCountException("Max transfer attempts count [" + transferItem.getMessage().getTransferAttemptsCount() + "] exceeded on this processor for item"));
return;
}
// always increment the transfer attempts count
short transferAttemptsCount = transferItem.getMessage().incrementAndGetTransferAttemptsCount();
try {
logger.trace("[{}] Attempting transfer of item to RemoteBroker [{}] [attempt: {} transferCount: {}]", new Object[] { remoteQueue.getName(), remoteBrokerUrl, transferAttemptsCount, transferCount });
AsyncCompletionHandler<TransferResponse> asyncHandler = new AsyncCompletionHandler<TransferResponse>() {
public TransferResponse onCompleted(Response response) throws Exception {
try {
// consume content and process it
String body = response.getResponseBody();
// try to parse the body
TransferResponse transferResponse = ProtocolFactory.parseTransferResponse(body);
// check if the response is valid
ProtocolResponseUtil.verifyResultCodeIsOK(transferResponse);
if (handler != null) handler.onComplete(transferResponse);
return transferResponse;
} catch (Exception e) {
onThrowable(e);
}
return null;
}
public void onThrowable(Throwable t) {
boolean retryable = true;
if (t instanceof ProtocolResultCodeException) {
// remote broker returned a specific error code (but did respond :-))
// depending on the actual error, we should likely do something special
ProtocolResultCodeException e = (ProtocolResultCodeException)t;
if (e.getResultCode() == ProtocolConstants.RC_NO_QUEUE || e.getResultCode() == ProtocolConstants.RC_NO_CONSUMER) {
logger.warn("[{}] RemoteBroker [{}] returned an error indicating the queue is either gone or no longer has a consumer", remoteQueue.getName(), remoteBrokerUrl);
if (listener != null) { listener.notifyQueueOnRemoteBrokerIsNoLongerAvailable(remoteBrokerUrl, remoteQueue.getName()); }
} else if (e.getResultCode() == ProtocolConstants.RC_GROUP_NAME_MISMATCH) {
// this should really only happen if the remote system was restarted and no longer matches ours
logger.warn("[{}] The group on RemoteBroker [{}] no longer matches ours", remoteQueue.getName(), remoteBrokerUrl);
if (listener != null) { listener.notifyRemoteBrokerIsNoLongerAvailable(remoteBrokerUrl, e.getMessage()); }
} else {
// any other errors such as ITEM_TYPE_MISMATCH, etc..
logger.warn("[{}] Result code [{}] from remote broker [{}] was not OK, safest action is to notifyQueueOnRemoteBrokerIsNoLongerAvailable", new Object[] { remoteQueue.getName(), e.getResultCode(), remoteBrokerUrl });
if (listener != null) { listener.notifyQueueOnRemoteBrokerIsNoLongerAvailable(remoteBrokerUrl, remoteQueue.getName()); }
}
} else if (t instanceof ProtocolParsingException) {
// remote broker return a bad protocol response (yet it was still an HTTP 200 error code)
// this is the most difficult exception since the item *MAY* have actually
// been transferred to the remote system -- the safe bet is to just notify the
// system that the queue is the only thing no longer available
logger.warn("["+remoteQueue.getName()+"] Unexpected parsing issue with protocol (despite an HTTP 200 status code), safest action is to notifyQueueOnRemoteBrokerIsNoLongerAvailable", t);
if (listener != null) { listener.notifyQueueOnRemoteBrokerIsNoLongerAvailable(remoteBrokerUrl, remoteQueue.getName()); }
} else if (t instanceof IOException) {
// unable to actually connect to to Remote Broker
logger.warn("["+remoteQueue.getName()+"] Unexpected IO exception, going to notifyRemoteBrokerIsNoLongerAvailable", t);
if (listener != null) { listener.notifyRemoteBrokerIsNoLongerAvailable(remoteBrokerUrl, t.getMessage()); }
} else {
logger.warn("["+remoteQueue.getName()+"] Unexpected exception on transfer. Will not retry.", t);
if (handler != null) handler.onThrowable(t);
retryable = false;
}
if (retryable) {
logger.trace("[{}] Retrying request {}", remoteQueue.getName(), transferItem);
transfer(handler);
}
}
};
// first, let's locally save the queue name (in case remoteQueue was null)
String queueName = remoteQueue.getName();
String type = null;
byte[] data = null;
// Backwards compatibility for transfers
logger.trace("RemoteBroker version {}", brokerInfo.getVersion());
logger.trace("Transcoder is {}", transferItem.getLocalQueue().getTranscoder().getClass().getName());
logger.trace("Item is {}", transferItem.getItem().getClass().getName());
if (brokerInfo.getVersion() == ProtocolConstants.VERSION_1_0 &&
transferItem.getLocalQueue().getTranscoder() instanceof TranscoderWrapped &&
transferItem.getItem() instanceof MQMessage) {
logger.trace("RemoteBroker[{}] is version 1", brokerInfo);
logger.trace("Queue[{}] uses TranscoderWrapped and item is MQMessage", transferItem.getLocalQueue().getName());
TranscoderWrapped tw = (TranscoderWrapped)transferItem.getLocalQueue().getTranscoder();
MQMessage mqItem = (MQMessage)transferItem.getItem();
type = mqItem.getBody().getClass().getCanonicalName();
data = tw.getBaseTranscoder().encode(mqItem.getBody());
} else {
type = transferItem.getItemType().getCanonicalName();
data = transferItem.getLocalQueue().getTranscoder().encode(transferItem.getItem());
}
// create the url
String cmdurl = remoteBrokerUrl + "?cmd=transfer&queue=" + queueName + "&itemType=" + type;
if (dqconfig.getGroupName() != null) {
cmdurl += "&group=" + dqconfig.getGroupName();
}
// execute the request
AsyncHttpClient.BoundRequestBuilder builder = http.preparePost(cmdurl);
builder.setBody(data);
builder.execute(asyncHandler);
} catch (Exception e) {
handler.onThrowable(e);
}
}
}