/*******************************************************************************
* Copyright (c) 2004, 2007 IBM Corporation and Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* File: $Source: /cvsroot/slrp/boca/com.ibm.adtech.boca.notification.web/JavaSource/com/ibm/adtech/boca/notification/web/NotificationControlListener.java,v $
* Created by: Matthew Roy ( <a href="mailto:mroy@us.ibm.com">mroy@us.ibm.com </a>)
* Created on: 3/22/2006
* Revision: $Id: NotificationControlListener.java 163 2007-07-31 14:11:08Z mroy $
*
* Contributors:
* IBM Corporation - initial API and implementation
* Cambridge Semantics Incorporated - Fork to Anzo
*******************************************************************************/
package org.openanzo.combus.endpoint;
import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.openanzo.analysis.RequestAnalysis;
import org.openanzo.analysis.RequestRecorder;
import org.openanzo.combus.MessageUtils;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.AnzoRuntimeException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.rdf.Constants;
import org.openanzo.rdf.Constants.OPTIONS;
import org.openanzo.rdf.utils.SerializationConstants;
import org.openanzo.services.AnzoPrincipal;
import org.openanzo.services.IAuthenticationService;
import org.openanzo.services.IOperationContext;
import org.openanzo.services.impl.BaseOperationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* BaseServiceListener is the base MessageListener
*
* @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>)
*/
abstract public class BaseServiceListener implements ICombusEndpointListener, MessageListener {
private static final Logger log = LoggerFactory.getLogger(BaseServiceListener.class);
private final IAuthenticationService authenticationService;
protected MessageProducer mp = null;
protected Session session = null;
private MessageConsumer consumer = null;
private MessageConsumer lowConsumer = null;
private final Lock lock = new ReentrantLock();
private int minThreadPoolSize = 1;
private int maxThreadPoolSize = 10;
private final String name;
protected boolean started = false;
protected GenericObjectPool threadPool = null;
protected RequestRecorder recorder = null;
/**
* Create a new BaseServiceListener
*
* @param name
* name of listener, used for thread name
* @param authenticationService
* authenticationService for the listener
*/
public BaseServiceListener(String name, IAuthenticationService authenticationService) {
this(name, 2, 4, authenticationService);
}
/**
* Create a new BaseServiceListener
*
* @param name
* name of listener, used for thread name
* @param minimumThread
* minimum number of listener threads
* @param maxThreads
* maximum number of listener threads
* @param authenticationService
* authenticationService for the listener
*/
public BaseServiceListener(String name, int minimumThread, int maxThreads, IAuthenticationService authenticationService) {
this.name = name;
this.minThreadPoolSize = minimumThread;
this.maxThreadPoolSize = maxThreads;
this.authenticationService = authenticationService;
}
public void setSession(Session session) {
this.session = session;
}
public void setRecorder(RequestRecorder recorder) {
this.recorder = recorder;
}
public void start() throws AnzoException {
lock.lock();
try {
if (maxThreadPoolSize > 1) {
threadPool = new GenericObjectPool(new ThreadPoolFactory(), maxThreadPoolSize, GenericObjectPool.WHEN_EXHAUSTED_BLOCK, 0);
threadPool.setMinIdle(minThreadPoolSize);
threadPool.setMaxIdle(minThreadPoolSize);
threadPool.setMinEvictableIdleTimeMillis(10000);
}
started = true;
if (consumer != null) {
try {
consumer.setMessageListener(this);
lowConsumer.setMessageListener(this);
} catch (JMSException jmsex) {
log.error(LogUtils.COMBUS_MARKER, "Error setting up consumers", jmsex);
throw new AnzoException(ExceptionConstants.COMBUS.JMS_SERVICE_EXCEPTION, jmsex);
}
}
} finally {
lock.unlock();
}
}
public void stop() throws AnzoException {
lock.lock();
try {
started = false;
try {
if (threadPool != null)
threadPool.close();
threadPool = null;
} catch (Exception e) {
log.error(LogUtils.COMBUS_MARKER, "Error closing thread pool", e);
}
} finally {
lock.unlock();
}
}
protected AnzoPrincipal getContextPrincipal(IOperationContext context, Message request) throws JMSException, AnzoException {
String uid = request.getStringProperty("JMSXUserID");
AnzoPrincipal credentials = authenticationService.getUserPrincipal(context, uid);
String runAsUser = request.getStringProperty(SerializationConstants.runAsUser);
if (credentials != null && runAsUser != null) {
if (!credentials.isSysadmin()) {
throw new AnzoException(ExceptionConstants.COMBUS.RUNAS_NOT_AUTHORIZED);
}
credentials = authenticationService.getUserPrincipal(context, runAsUser);
}
return credentials;
}
protected boolean verifyCaller(IOperationContext context) throws JMSException, AnzoException {
if (context == null || context.getOperationPrincipal() == null || context.getOperationPrincipal().getUserURI() == null) {
throw new AnzoException(ExceptionConstants.SERVER.NO_PRINCIPAL_ERROR);
}
return true;
}
/**
* @param consumer
* the consumer to set
*/
public void setConsumer(MessageConsumer consumer, MessageConsumer lowConsumer) throws JMSException {
lock.lock();
try {
this.consumer = consumer;
this.lowConsumer = lowConsumer;
if (started) {
this.consumer.setMessageListener(this);
this.lowConsumer.setMessageListener(this);
}
} finally {
lock.unlock();
}
}
protected int highActive = 0;
protected int lowActive = 0;
public void onMessage(Message message) {
if (started) {
try {
if (threadPool != null) {
if (message.propertyExists(SerializationConstants.bypassPool) && message.getBooleanProperty(SerializationConstants.bypassPool)) {
processMessage(message);
} else {
if (message.getJMSPriority() < 4) {
while (threadPool.getNumActive() >= threadPool.getMaxActive() || (highActive > (threadPool.getNumActive() / 2) && lowActive > 1)) {
synchronized (threadPool) {
threadPool.wait();
}
}
ProcessThread pt = (ProcessThread) threadPool.borrowObject();
pt.setRequest(message, false);
} else {
ProcessThread pt = (ProcessThread) threadPool.borrowObject();
pt.setRequest(message, true);
}
}
} else {
processMessage(message);
}
} catch (Throwable jmex) {
log.error(LogUtils.COMBUS_MARKER, "Error in BaseService Listener's process thread loop", jmex);
}
}
}
class ProcessThread extends Thread {
private Message request = null;
private final Lock readLock = new ReentrantLock();
private final Condition waiter = readLock.newCondition();
boolean dead = false;
private boolean highPriority = false;
ProcessThread() {
super(BaseServiceListener.this.name);
setDaemon(true);
}
protected void setRequest(Message request, boolean highPriority) {
readLock.lock();
try {
this.request = request;
this.highPriority = highPriority;
waiter.signal();
} finally {
readLock.unlock();
}
}
@Override
public void run() {
boolean alive = false;
while (started && !dead) {
readLock.lock();
try {
if (this.request == null) {
waiter.await();
}
alive = true;
if (highPriority)
highActive++;
else
lowActive++;
processMessage(request);
if (highPriority)
highActive--;
else
lowActive--;
} catch (InterruptedException jmex) {
dead = true;
} finally {
request = null;
readLock.unlock();
try {
if (threadPool != null) {
if (alive)
threadPool.returnObject(ProcessThread.this);
alive = false;
} else {
dead = true;
}
} catch (Exception e) {
log.error(LogUtils.COMBUS_MARKER, "Error in BaseService Listener's process thread loop", e);
}
}
}
}
}
private void processMessage(Message request) {
try {
IOperationContext context = null;
TextMessage response = null;
String operation = request.getStringProperty(SerializationConstants.operation);
try {
Destination replyTo = null;
try {
if (mp == null) {
mp = session.createProducer(null);
mp.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
}
replyTo = request.getJMSReplyTo();
String resultFormat = request.getStringProperty(SerializationConstants.resultFormat);
context = new BaseOperationContext(operation, request.getJMSCorrelationID(), null);
if (request.propertyExists(SerializationConstants.userDescription)) {
context.setAttribute(SerializationConstants.userDescription, request.getStringProperty(SerializationConstants.userDescription));
}
if (request.propertyExists(OPTIONS.SKIPCACHE)) {
context.setAttribute(OPTIONS.SKIPCACHE, request.getBooleanProperty(OPTIONS.SKIPCACHE));
}
if (request.propertyExists(OPTIONS.INCLUDEMETADATAGRAPHS)) {
context.setAttribute(OPTIONS.INCLUDEMETADATAGRAPHS, request.getBooleanProperty(OPTIONS.INCLUDEMETADATAGRAPHS));
}
AnzoPrincipal callerPrincipal = getContextPrincipal(context, request);
context.setOperationPrincipal(callerPrincipal);
if (log.isTraceEnabled()) {
log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(request, "Message Recieved from [" + callerPrincipal.getName() + "]" + ((replyTo != null) ? (" with replyto [" + replyTo + "]") : "")));
} else if (log.isDebugEnabled()) {
log.debug(LogUtils.COMBUS_MARKER, "Message Recieved from [" + callerPrincipal.getName() + "]" + ((replyTo != null) ? (" with replyto [" + replyTo + "]") : ""));
}
context.setMDC();
Boolean analyzeRequest = request.getBooleanProperty(RequestAnalysis.CONTEXT_PROP_REQUEST_ENABLED);
if (analyzeRequest || recorder != null) {
RequestAnalysis.setCurrentContext(context.getAttributes());
RequestAnalysis.setRequestAnalysisEnabled(true);
}
if (recorder != null) {
recorder.recordRequest((TextMessage) request, request.getStringProperty("JMSXUserID"), request.getStringProperty(SerializationConstants.runAsUser));
}
long start = 0, end = 0;
if (RequestAnalysis.isAnalysisEnabled(context.getAttributes())) {
start = System.currentTimeMillis();
}
response = handleMessage(context, replyTo, resultFormat, operation, (TextMessage) request, mp);
if (RequestAnalysis.isAnalysisEnabled(context.getAttributes())) {
end = System.currentTimeMillis();
RequestAnalysis.addAnalysisProperty(RequestAnalysis.ANS_PROP_OPERATION_TIME, String.valueOf(end - start));
}
if (response != null) {
response.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION);
if (operation != null) {
response.setStringProperty(SerializationConstants.operation, operation);
}
Integer totalSolutions = context.getAttribute(SerializationConstants.totalSolutions, Integer.class);
if (totalSolutions != null) {
response.setIntProperty(SerializationConstants.totalSolutions, totalSolutions.intValue());
}
if (analyzeRequest) {
for (String name : RequestAnalysis.getAnalysisPropertyNames()) {
response.setStringProperty(name, context.getAttribute(name).toString());
}
}
}
if (response != null && replyTo != null) {
response.setJMSCorrelationID(request.getJMSCorrelationID());
if (log.isTraceEnabled()) {
log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(response, "Sending Response to [" + replyTo + "]"));
} else if (log.isDebugEnabled()) {
log.debug(LogUtils.COMBUS_MARKER, "Sending Response to [" + replyTo + "]");
}
mp.send(replyTo, response);
}
} catch (JMSException jmex) {
response = sendJMSErrorMessage(replyTo, request, jmex, ExceptionConstants.COMBUS.JMS_SERVICE_EXCEPTION, jmex.toString());
} catch (AnzoException jmex) {
response = sendJMSErrorMessage(replyTo, request, jmex, jmex.getErrorCode(), jmex.getArgs());
} catch (AnzoRuntimeException jmex) {
response = sendJMSErrorMessage(replyTo, request, jmex, jmex.getErrorCode(), jmex.getArgs());
} catch (RuntimeException jmex) {
response = sendJMSErrorMessage(replyTo, request, jmex, ExceptionConstants.COMBUS.JMS_SERVICE_EXCEPTION, jmex.toString());
} catch (Throwable jmex) {
response = sendJMSErrorMessage(replyTo, request, jmex, ExceptionConstants.COMBUS.JMS_SERVICE_EXCEPTION, jmex.toString());
}
if (recorder != null) {
if (response != null) {
recorder.recordResponse(response);
} else {
recorder.recordResponse(request.getJMSCorrelationID(), operation);
}
}
} finally {
if (context != null) {
context.clearMDC();
}
}
} catch (JMSException jmsex) {
if (jmsex.getCause() instanceof InterruptedException) {
log.debug(LogUtils.COMBUS_MARKER, "Thread interrupted in order to stop.", jmsex);
} else {
log.error(LogUtils.COMBUS_MARKER, "Error in BaseService Listener's process thread loop", jmsex);
}
} catch (Throwable jmex) {
log.error(LogUtils.COMBUS_MARKER, "Error in BaseService Listener's process thread loop", jmex);
}
}
private TextMessage sendJMSErrorMessage(Destination replyTo, Message request, Throwable jmex, long errorCode, String... args) throws JMSException {
try {
if (replyTo != null) {
if (log.isWarnEnabled()) {
log.warn(LogUtils.COMBUS_MARKER, "Exception while ServiceListener [" + name + "] was precessing request.", jmex);
}
String message = null;
if (jmex instanceof AnzoException) {
message = ((AnzoException) jmex).getMessage(false);
} else if (jmex instanceof AnzoRuntimeException) {
message = ((AnzoRuntimeException) jmex).getMessage(false);
} else {
message = jmex.getMessage();
}
TextMessage response = session.createTextMessage(message);
response.setJMSCorrelationID(request.getJMSCorrelationID());
response.setBooleanProperty(SerializationConstants.operationFailed, true);
response.setLongProperty(SerializationConstants.errorTags, 0);
response.setLongProperty(SerializationConstants.errorCode, errorCode);
response.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION);
// send a single arg string for compat. with older readers
response.setStringProperty(SerializationConstants.errorMessageArg, Arrays.toString(args));
// send the individual error args for readers that can make use of them
for (int i = 0; i < args.length; i++) {
response.setStringProperty(SerializationConstants.errorMessageArg + i, args[i]);
}
// we log all JMS messages, even errors.
if (log.isDebugEnabled()) {
log.debug(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(response, "Sending Response to " + replyTo));
}
mp.send(replyTo, response);
return response;
}
} catch (JMSException jmsex) {
log.debug(LogUtils.COMBUS_MARKER, "Error sending error message to client", jmsex);
}
return null;
}
class ThreadPoolFactory implements PoolableObjectFactory {
public void activateObject(Object arg0) throws Exception {
}
public void destroyObject(Object arg0) throws Exception {
((ProcessThread) arg0).interrupt();
}
public Object makeObject() throws Exception {
ProcessThread pt = new ProcessThread();
pt.start();
return pt;
}
public void passivateObject(Object arg0) throws Exception {
}
public boolean validateObject(Object arg0) {
return !((ProcessThread) arg0).dead;
}
}
}