/**
* Copyright 2012 Comcast Corporation
*
* 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.comcast.cmb.common.controller;
import com.comcast.cmb.common.model.IAuthModule;
import com.comcast.cmb.common.model.ReceiptModule;
import com.comcast.cmb.common.model.User;
import com.comcast.cmb.common.model.UserAuthModule;
import com.comcast.cmb.common.persistence.IUserPersistence;
import com.comcast.cmb.common.persistence.PersistenceFactory;
import com.comcast.cmb.common.util.CMBErrorCodes;
import com.comcast.cmb.common.util.CMBException;
import com.comcast.cmb.common.util.CMBProperties;
import com.comcast.cmb.common.util.Util;
import com.comcast.cmb.common.util.ValueAccumulator;
import com.comcast.cmb.common.util.ValueAccumulator.AccumulatorName;
import com.comcast.cns.controller.CNSControllerServlet;
import com.comcast.cqs.controller.CQSCache;
import com.comcast.cqs.controller.CQSControllerServlet;
import com.comcast.cqs.controller.CQSHttpServletRequest;
import com.comcast.cqs.controller.CQSLongPollReceiver;
import com.comcast.cqs.io.CQSMessagePopulator;
import com.comcast.cqs.model.CQSMessage;
import com.comcast.cqs.model.CQSQueue;
import com.comcast.cqs.util.CQSConstants;
import org.apache.log4j.Logger;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Abstract class for all servlets
* @author aseem, bwolf, baosen, vvenkatraman, jorge, michael
*
*/
abstract public class CMBControllerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(CMBControllerServlet.class);
public static volatile ScheduledThreadPoolExecutor workerPool;
private static volatile boolean initialized = false;
protected IUserPersistence userHandler = null;
protected IAuthModule authModule = null;
/**
* This instance of the acccumulator is used throughout the code to accumulate response times
* for a particular thread
*/
public final static ValueAccumulator valueAccumulator = new ValueAccumulator();
public final static String VERSION = "2.2.42";
public final static int HARD_TIMEOUT_SEC = CMBProperties.getInstance().getCMBRequestTimeoutSec();
public volatile static ConcurrentHashMap<String, AtomicLong> callStats;
public volatile static ConcurrentHashMap<String, AtomicLong> callFailureStats;
public final static int NUM_MINUTES = 60;
public final static int NUM_BUCKETS = 10;
public volatile static AtomicLong[][] callResponseTimes1MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
public volatile static AtomicLong[][] callResponseTimes10MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
public volatile static AtomicLong[][] callResponseTimes100MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
public volatile static AtomicLong[][] callResponseTimes1000MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
public volatile static AtomicLong[][] callResponseTimesRedisMS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
public volatile static AtomicLong[][] callResponseTimesCassandraMS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
public volatile static ConcurrentHashMap<String, AtomicLong[][]> callResponseTimesByApi;
public volatile static AtomicInteger currentMinute;
public volatile static String[] recentErrors = new String[10];
public volatile static int recentErrorIdx = -1;
public static final long startTime = System.currentTimeMillis();
@Override
public void init() throws ServletException {
try {
if (!initialized) {
Util.initLog4j();
CMBProperties.getInstance();
workerPool = new ScheduledThreadPoolExecutor(CMBProperties.getInstance().getCMBWorkerPoolSize());
initStats();
initialized = true;
}
} catch (Exception ex) {
throw new ServletException(ex);
}
}
protected void initRequest() {
userHandler = PersistenceFactory.getUserPersistence();
authModule = new UserAuthModule();
authModule.setUserPersistence(userHandler);
}
public static void initStats() {
callStats = new ConcurrentHashMap<String, AtomicLong>();
callFailureStats = new ConcurrentHashMap<String, AtomicLong>();
callResponseTimes1MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
callResponseTimes10MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
callResponseTimes100MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
callResponseTimes1000MS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
callResponseTimesRedisMS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
callResponseTimesCassandraMS = new AtomicLong[NUM_MINUTES][NUM_BUCKETS];
callResponseTimesByApi = new ConcurrentHashMap<String, AtomicLong[][]>();
for (int i=0; i<NUM_MINUTES; i++) {
for (int k=0; k<NUM_BUCKETS; k++) {
callResponseTimes1MS[i][k] = new AtomicLong();
callResponseTimes10MS[i][k] = new AtomicLong();
callResponseTimes100MS[i][k] = new AtomicLong();
callResponseTimes1000MS[i][k] = new AtomicLong();
callResponseTimesRedisMS[i][k] = new AtomicLong();
callResponseTimesCassandraMS[i][k] = new AtomicLong();
}
}
currentMinute = new AtomicInteger(new Date(System.currentTimeMillis()).getMinutes());
recentErrors = new String[10];
recentErrorIdx = -1;
}
/**
* handles the action
* @param action
* @param user the user making the call
* @param request
* @param response
* @return true if this action was performed, false otherwise
* @throws Exception
*/
abstract protected boolean handleAction(String action, User user, AsyncContext asyncContext) throws Exception;
/**
*
* @param action
* @return true if the action is supported by this servlet, false otherwise
* @throws ServletException
*/
abstract protected boolean isValidAction(String action) throws ServletException;
/**
* @param action
* @return true if validation is required. False otherwise
* Should be overriden by subclasses
*/
protected boolean isAuthenticationRequired(String action) {
return true;
}
private String getParameterString(AsyncContext asyncContext) {
HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest();
Enumeration<String> keys = request.getParameterNames();
StringBuffer params = new StringBuffer("");
boolean logMessageBodyFlag = true;
while (keys.hasMoreElements()) {
String key = keys.nextElement();
String value = request.getParameter(key);
int length = 0;
if(value != null){
length = value.length();
}
if (key.equals("MessageBody")) {
if (CMBProperties.getInstance().getMaxMessagePayloadLogLength() > 0) {
if (value != null && value.length() > CMBProperties.getInstance().getMaxMessagePayloadLogLength()) {
value = value.substring(0, CMBProperties.getInstance().getMaxMessagePayloadLogLength())
+ "...";
logMessageBodyFlag = true;
}
} else {
logMessageBodyFlag = false;
value = null;
}
}
if (value != null) {
if (value.indexOf('\n') >= 0) {
value = value.replace("\n", "\\n");
}
if (value.indexOf('\r') >= 0) {
value = value.replace("\r", "\\r");
}
}
if (!key.equals("MessageBody") || logMessageBodyFlag == true){
params.append(key).append("=").append(value).append(" ");
}
if (key.equals("MessageBody")) {
params.append("msg_size=").append(length).append(" ");
}
}
return params.toString();
}
private String getUrlQueryString(AsyncContext asyncContext) {
HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest();
Enumeration<String> keys = request.getParameterNames();
StringBuffer params = new StringBuffer("?");
while (keys.hasMoreElements()) {
String key = keys.nextElement();
String value = request.getParameter(key);
if (value != null && value.length() > CMBProperties.getInstance().getMaxMessagePayloadLogLength()) {
value = value.substring(0, CMBProperties.getInstance().getMaxMessagePayloadLogLength()) + "...";
}
if (value != null) {
if (value.indexOf('\n') >= 0) {
value = value.replace("\n", "\\n");
}
if (value.indexOf('\r') >= 0) {
value = value.replace("\r", "\\r");
}
}
params.append(key).append("=").append(value).append("&");
}
return params.toString();
}
private void logStats(String action, long responseTimeMS, long redisTimeMS, long cassandraTimeMS) {
try {
if (!initialized) {
return;
}
if (action != null && !action.equals("")) {
callStats.putIfAbsent(action, new AtomicLong());
if (callStats.get(action).incrementAndGet() == Long.MAX_VALUE - 100) {
callStats = new ConcurrentHashMap<String, AtomicLong>();
}
int newMinute = new Date(System.currentTimeMillis()).getMinutes();
int oldMinute = currentMinute.getAndSet(newMinute);
if (newMinute != oldMinute) {
int eraseIdx = (newMinute+1)%60;
for (int i=0; i<NUM_BUCKETS; i++) {
callResponseTimes1MS[eraseIdx][i].set(0);
callResponseTimes10MS[eraseIdx][i].set(0);
callResponseTimes100MS[eraseIdx][i].set(0);
callResponseTimes1000MS[eraseIdx][i].set(0);
callResponseTimesRedisMS[eraseIdx][i].set(0);
callResponseTimesCassandraMS[eraseIdx][i].set(0);
for (String ac : callResponseTimesByApi.keySet()) {
callResponseTimesByApi.get(ac)[eraseIdx][i].set(0);
}
}
}
int responseTimeIdx;
// response time percentiles
responseTimeIdx = (int)(responseTimeMS)/1;
if (responseTimeIdx > NUM_BUCKETS-1) {
responseTimeIdx = NUM_BUCKETS-1;
} else if (responseTimeIdx < 0) {
responseTimeIdx = 0;
}
callResponseTimes1MS[newMinute][responseTimeIdx].incrementAndGet();
responseTimeIdx = (int)(responseTimeMS)/10;
if (responseTimeIdx > NUM_BUCKETS-1) {
responseTimeIdx = NUM_BUCKETS-1;
} else if (responseTimeIdx < 0) {
responseTimeIdx = 0;
}
callResponseTimes10MS[newMinute][responseTimeIdx].incrementAndGet();
responseTimeIdx = (int)(responseTimeMS)/100;
if (responseTimeIdx > NUM_BUCKETS-1) {
responseTimeIdx = NUM_BUCKETS-1;
} else if (responseTimeIdx < 0) {
responseTimeIdx = 0;
}
callResponseTimes100MS[newMinute][responseTimeIdx].incrementAndGet();
responseTimeIdx = (int)(responseTimeMS)/1000;
if (responseTimeIdx > NUM_BUCKETS-1) {
responseTimeIdx = NUM_BUCKETS-1;
} else if (responseTimeIdx < 0) {
responseTimeIdx = 0;
}
callResponseTimes1000MS[newMinute][responseTimeIdx].incrementAndGet();
AtomicLong[][] callResponseTimes = callResponseTimesByApi.get(action);
if (callResponseTimes != null) {
// resolution for the api specific response time array is always 10 ms
responseTimeIdx = (int)(responseTimeMS)/10;
if (responseTimeIdx > NUM_BUCKETS-1) {
responseTimeIdx = NUM_BUCKETS-1;
} else if (responseTimeIdx < 0) {
responseTimeIdx = 0;
}
callResponseTimes[newMinute][responseTimeIdx].incrementAndGet();
}
// redis time
responseTimeIdx = (int)(redisTimeMS)/1;
if (responseTimeIdx > NUM_BUCKETS-1) {
responseTimeIdx = NUM_BUCKETS-1;
} else if (responseTimeIdx < 0) {
responseTimeIdx = 0;
}
callResponseTimesRedisMS[newMinute][responseTimeIdx].incrementAndGet();
// cassandra time
responseTimeIdx = (int)(cassandraTimeMS)/1;
if (responseTimeIdx > NUM_BUCKETS-1) {
responseTimeIdx = NUM_BUCKETS-1;
} else if (responseTimeIdx < 0) {
responseTimeIdx = 0;
}
callResponseTimesCassandraMS[newMinute][responseTimeIdx].incrementAndGet();
}
} catch (Exception ex) {
logger.warn("event=failed_to_log_stats", ex);
}
}
private String getLogLine(AsyncContext asyncContext, CQSHttpServletRequest request, User user, long responseTimeMS, String status) {
StringBuffer logLine = new StringBuffer("");
logLine.append("event=req status="+status+" client=").append(request.getRemoteAddr());
logLine.append(((this instanceof CQSControllerServlet) ? (" queue_url=" + request.getPathInfo()) : ""));
if (request.getReceiptHandles() != null && request.getReceiptHandles().size() > 0) {
for (int i=0; i<request.getReceiptHandles().size(); i++) {
logLine.append(" ReceiptHandle=").append(request.getReceiptHandles().get(i));
}
}
logLine.append(" ").append(getParameterString(asyncContext));
logLine.append((user != null ? "user=" + user.getUserName() : ""));
if (request.getAttribute("lp") == null) {
//if status is timeout for normal action, it does not have below info
if((status != null) && (!status.equals("timeout"))){
logLine.append(" resp_ms=").append(responseTimeMS);
logLine.append(" cass_ms=" + valueAccumulator.getCounter(AccumulatorName.CassandraTime));
logLine.append(" cass_num_rd=" + valueAccumulator.getCounter(AccumulatorName.CassandraRead));
logLine.append(" cass_num_wr=" + valueAccumulator.getCounter(AccumulatorName.CassandraWrite));
logLine.append(((this instanceof CNSControllerServlet) ? (" cnscqs_ms=" + CMBControllerServlet.valueAccumulator.getCounter(AccumulatorName.CNSCQSTime)) : ""));
logLine.append(((this instanceof CQSControllerServlet) ? (" redis_ms=" + valueAccumulator.getCounter(AccumulatorName.RedisTime)) : ""));
logLine.append(" io_ms=" + valueAccumulator.getCounter(AccumulatorName.IOTime));
logLine.append(" asyncq_ms=" + valueAccumulator.getCounter(AccumulatorName.AsyncQueueTime));
logLine.append(" auth_ms=" + valueAccumulator.getCounter(AccumulatorName.CMBControllerPreHandleAction));
}
} else if (request.getAttribute("lp").equals("yy")) { // long poll receive with messages
logLine.append(" resp_ms=").append(responseTimeMS);
logLine.append(" cass_ms=" + request.getAttribute("cass_ms"));
logLine.append(" cass_num_rd=" + request.getAttribute("cass_num_rd"));
logLine.append(" cass_num_wr=" + request.getAttribute("cass_num_wr"));
logLine.append(" redis_ms=" + request.getAttribute("redis_ms"));
logLine.append(" io_ms=" + request.getAttribute("io_ms"));
logLine.append(" lp_ms=").append(System.currentTimeMillis()-request.getRequestReceivedTimestamp());
} else if (request.getAttribute("lp").equals("yn")) { // long poll receive without messages
logLine.append(" lp_ms=").append(System.currentTimeMillis()-request.getRequestReceivedTimestamp());
}
logLine.append(" async_pool_queue=").append(CMBControllerServlet.workerPool.getQueue().size()).
append(" async_pool_size=").append(CMBControllerServlet.workerPool.getActiveCount());
if(CMBProperties.getInstance().getCQSServiceEnabled()){
logLine.append(" cqs_pool_size=").append(CMB.cqsServer.getThreadPool().getThreads());
}
if(CMBProperties.getInstance().getCNSServiceEnabled()){
logLine.append(" cns_pool_size=").append(CMB.cnsServer.getThreadPool().getThreads());
}
// log external headers from proxy if present
String rid = request.getHeader("CMB-RID");
if (rid != null) {
logLine.append(" rid=").append(rid);
}
String extStartTime = request.getHeader("START-TIME");
if (extStartTime != null) {
logLine.append(" ext_ts=").append(extStartTime);
}
return logLine.toString();
}
private String getFullRequestUrl(AsyncContext asyncContext) {
HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest();
return "event=raw_request url=" + request.getRequestURL() + "/" + this.getUrlQueryString(asyncContext);
}
private void handleRequest(AsyncContext asyncContext) throws ServletException, IOException {
CQSHttpServletRequest request = (CQSHttpServletRequest)asyncContext.getRequest();
HttpServletResponse response = (HttpServletResponse)asyncContext.getResponse();
User user = null;
String action = request.getParameter("Action");
long ts1 = System.currentTimeMillis();
try {
valueAccumulator.initializeAllCounters();
valueAccumulator.addToCounter(AccumulatorName.AsyncQueueTime, System.currentTimeMillis()-request.getRequestReceivedTimestamp());
initRequest();
response.setContentType("text/xml");
if (!isValidAction(action)) {
logger.warn("event=invalid_action action=" + action + " url=" + request.getRequestURL());
String errXml = createErrorResponse(CMBErrorCodes.InvalidAction.getCMBCode(), action + " is not a valid action");
response.setStatus(CMBErrorCodes.InvalidAction.getHttpCode());
response.getWriter().println(errXml);
return;
}
if (isAuthenticationRequired(action)) {
user = authModule.authenticateByRequest(request);
} else {
user = null;
}
long ts3 = System.currentTimeMillis();
valueAccumulator.addToCounter(AccumulatorName.CMBControllerPreHandleAction, (ts3 - ts1));
handleAction(action, user, asyncContext);
response.setStatus(200);
long ts2 = System.currentTimeMillis();
//if it is waiting for long poll receive, do not log now. Wait till asyn request finished.
if(request.getAttribute("lp")==null){
String logLine = getLogLine(asyncContext, request, user, ts2-ts1, "ok");
logger.info(logLine);
}
//String rawRequest = this.getFullRequestUrl(asyncContext);
//logger.info(rawRequest);
if (CMBProperties.getInstance().isCMBStatsEnabled()) {
logStats(action, ts2-ts1, valueAccumulator.getCounter(AccumulatorName.RedisTime), valueAccumulator.getCounter(AccumulatorName.CassandraTime));
}
} catch (Exception ex) {
long ts2 = System.currentTimeMillis();
String logLine = getLogLine(asyncContext, request, user, ts2-ts1, "failed");
logger.error(logLine, ex);
int httpCode = CMBErrorCodes.InternalError.getHttpCode();
String code = CMBErrorCodes.InternalError.getCMBCode();
String message = "There is an internal problem with CMB";
if (ex instanceof CMBException) {
httpCode = ((CMBException)ex).getHttpCode();
code = ((CMBException) ex).getCMBCode();
message = ex.getMessage();
}
String errXml = createErrorResponse(code, message);
response.setStatus(httpCode);
Action.writeResponse(errXml, response);
if (CMBProperties.getInstance().isCMBStatsEnabled() && action != null && !action.equals("")) {
callFailureStats.putIfAbsent(action, new AtomicLong());
if (callFailureStats.get(action).incrementAndGet() == Long.MAX_VALUE - 100) {
callFailureStats = new ConcurrentHashMap<String, AtomicLong>();
}
StringBuffer errorDetail = new StringBuffer(new Date() + "|" + logLine + "|" + ex.getMessage() + "\n");
for (StackTraceElement element : ex.getStackTrace()) {
errorDetail.append(element).append("\n");
}
// not thread safe but that's ok here
recentErrorIdx = (recentErrorIdx+1)%recentErrors.length;
recentErrors[recentErrorIdx] = errorDetail.toString();
}
} finally {
valueAccumulator.deleteAllCounters();
}
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (!request.isAsyncSupported()) {
throw new ServletException("Servlet container does not support asynchronous calls");
}
HttpServletRequest wrappedRequest = new CQSHttpServletRequest(request);
final AsyncContext asyncContext = request.startAsync(wrappedRequest, response);
// jetty appears to require calling setTimeout on http handler thread so we are setting
// wait time seconds for long polling receive message here
String actionParam = wrappedRequest.getParameter("Action");
String waitTimeSecondsParam = wrappedRequest.getParameter(CQSConstants.WAIT_TIME_SECONDS);
if (actionParam != null && actionParam.equals("ReceiveMessage") && waitTimeSecondsParam != null) {
try {
int waitTimeSeconds = Integer.parseInt(waitTimeSecondsParam);
if (waitTimeSeconds >= 1 && waitTimeSeconds <= CMBProperties.getInstance().getCMBRequestTimeoutSec()) {
asyncContext.setTimeout(waitTimeSeconds*1000);
((CQSHttpServletRequest)asyncContext.getRequest()).setWaitTime(waitTimeSeconds*1000);
logger.debug("event=set_message_timeout secs=" + waitTimeSeconds);
}
} catch (Exception ex) {
logger.warn("event=ignoring_suspicious_wait_time_parameter value=" + waitTimeSecondsParam);
}
} else if (actionParam != null && actionParam.equals("ReceiveMessage")) {
String queueUrl = com.comcast.cqs.util.Util.getRelativeForAbsoluteQueueUrl(request.getRequestURL().toString());
CQSQueue queue;
int waitTimeSeconds = 0;
try {
queue = CQSCache.getCachedQueue(queueUrl);
if (queue != null && queue.getReceiveMessageWaitTimeSeconds() > 0) {
waitTimeSeconds = queue.getReceiveMessageWaitTimeSeconds();
}
} catch (Exception ex) {
logger.warn("event=lookup_queue queue_url=" + queueUrl, ex);
}
if (waitTimeSeconds > 0) {
asyncContext.setTimeout(waitTimeSeconds*1000);
((CQSHttpServletRequest)asyncContext.getRequest()).setWaitTime(waitTimeSeconds*1000);
logger.debug("event=set_queue_timeout secs=" + waitTimeSeconds + " queue_url=" + queueUrl);
} else {
asyncContext.setTimeout(HARD_TIMEOUT_SEC*1000);
logger.debug("event=set_default_timeout secs="+CMBProperties.getInstance().getCMBRequestTimeoutSec());
}
} else {
asyncContext.setTimeout(HARD_TIMEOUT_SEC*1000);
logger.debug("event=set_default_timeout secs=" + CMBProperties.getInstance().getCMBRequestTimeoutSec());
}
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
AsyncContext asyncContext = asyncEvent.getAsyncContext();
CQSHttpServletRequest request = (CQSHttpServletRequest)asyncContext.getRequest();
String action = request.getParameter("Action");
if(action.equals("ReceiveMessage")){
String lpValue = (String)request.getAttribute("lp");
if(lpValue!=null){
User user = authModule.getUserByRequest(request);
Object lp_ms = request.getAttribute("lp_ms");
String logLine = null;
if(lp_ms!=null){
logLine = getLogLine(asyncContext, request, user, (Long)request.getAttribute("lp_ms"), "ok");
}else{
logLine = getLogLine(asyncContext, request, user, 0, "ok");
}
logger.info(logLine);
}
}
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
int httpCode = CMBErrorCodes.InternalError.getHttpCode();
String code = CMBErrorCodes.InternalError.getCMBCode();
String message = "There is an internal problem with CMB";
if (asyncEvent.getThrowable() instanceof CMBException) {
httpCode = ((CMBException)asyncEvent.getThrowable()).getHttpCode();
code = ((CMBException)asyncEvent.getThrowable()).getCMBCode();
message = asyncEvent.getThrowable().getMessage();
}
String errXml = CMBControllerServlet.createErrorResponse(code, message);
HttpServletResponse response = ((HttpServletResponse)asyncEvent.getSuppliedResponse());
response.setStatus(httpCode);
Action.writeResponse(errXml, response);
if (!(asyncEvent.getSuppliedRequest() instanceof CQSHttpServletRequest)) {
logger.error("event=invalid_request stage=on_error");
return;
}
CQSQueue queue = ((CQSHttpServletRequest)asyncEvent.getSuppliedRequest()).getQueue();
AsyncContext asyncContext = asyncEvent.getAsyncContext();
if (queue != null) {
logger.info("event=on_error queue_url=" + queue.getAbsoluteUrl());
ConcurrentLinkedQueue<AsyncContext> queueContextsList = CQSLongPollReceiver.contextQueues.get(queue.getArn());
if (queueContextsList != null && asyncContext != null) {
queueContextsList.remove(asyncContext);
}
} else {
logger.info("event=on_error");
}
asyncContext.complete();
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
if (!(asyncEvent.getSuppliedRequest() instanceof CQSHttpServletRequest)) {
logger.error("event=invalid_request stage=on_timeout");
return;
}
//first check if it is long poll receive, if so, complete and return.
AsyncContext asyncContext = asyncEvent.getAsyncContext();
CQSHttpServletRequest request = (CQSHttpServletRequest)asyncContext.getRequest();
String action = request.getParameter("Action");
if(action.equals("ReceiveMessage")){
String lpValue = (String)request.getAttribute("lp");
if(lpValue!=null){
String out = CQSMessagePopulator.getReceiveMessageResponseAfterSerializing(new ArrayList<CQSMessage>(), new ArrayList<String>());
asyncEvent.getSuppliedResponse().getWriter().println(out);
CQSQueue queue = ((CQSHttpServletRequest)asyncEvent.getSuppliedRequest()).getQueue();
asyncContext = asyncEvent.getAsyncContext();
if (queue != null) {
logger.debug("event=on_timeout queue_url=" + queue.getAbsoluteUrl());
ConcurrentLinkedQueue<AsyncContext> queueContextsList = CQSLongPollReceiver.contextQueues.get(queue.getArn());
if (queueContextsList != null && asyncContext != null) {
queueContextsList.remove(asyncContext);
}
} else {
logger.debug("event=on_timeout");
}
asyncContext.complete();
return;
}
}
//for other Time out show log error and return error response.
int httpCode = CMBErrorCodes.InternalError.getHttpCode();
String code = CMBErrorCodes.InternalError.getCMBCode();
String message = "CMB timeout after "+CMBProperties.getInstance().getCMBRequestTimeoutSec()+" seconds";
if (asyncEvent.getThrowable() instanceof CMBException) {
httpCode = ((CMBException)asyncEvent.getThrowable()).getHttpCode();
code = ((CMBException)asyncEvent.getThrowable()).getCMBCode();
message = asyncEvent.getThrowable().getMessage();
}
String errXml = CMBControllerServlet.createErrorResponse(code, message);
HttpServletResponse response = ((HttpServletResponse)asyncEvent.getSuppliedResponse());
response.setStatus(httpCode);
Action.writeResponse(errXml, response);
if (!(asyncEvent.getSuppliedRequest() instanceof CQSHttpServletRequest)) {
logger.error("event=invalid_request stage=on_timeout");
return;
}
CQSQueue queue = ((CQSHttpServletRequest)asyncEvent.getSuppliedRequest()).getQueue();
asyncContext = asyncEvent.getAsyncContext();
if (queue != null) {
logger.error("event=on_timeout queue_url=" + queue.getAbsoluteUrl());
ConcurrentLinkedQueue<AsyncContext> queueContextsList = CQSLongPollReceiver.contextQueues.get(queue.getArn());
if (queueContextsList != null && asyncContext != null) {
queueContextsList.remove(asyncContext);
}
} else {
logger.error("event=on_timeout "+getLogLine(asyncContext, request, authModule.getUserByRequest(request), 0, "timeout"));
}
asyncContext.complete();
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
}
});
workerPool.submit(new Runnable() {
public void run() {
try {
ReceiptModule.init();
handleRequest(asyncContext);
} catch (Exception ex) {
logger.error("event=async_api_call_failure receipt_id=" + ReceiptModule.getReceiptId(), ex);
}
if (!((CQSHttpServletRequest)asyncContext.getRequest()).isQueuedForProcessing()) {
asyncContext.complete();
}
}
});
}
public static String createErrorResponse(String code, String errorMsg) {
StringBuffer message = new StringBuffer("<ErrorResponse>\n")
.append("\t<Error>\n")
.append("\t\t<Type>Sender</Type>\n")
.append("\t\t<Code>").append(code).append("</Code>\n")
.append("\t\t<Message>").append(errorMsg).append("</Message>\n")
.append("\t\t<Detail/>\n")
.append("\t</Error>\n")
.append("\t<RequestId>").append(ReceiptModule.getReceiptId()).append("</RequestId>\n")
.append("</ErrorResponse>\n");
return message.toString();
}
}