/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* 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 org.apache.sandesha2.workers;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.sandesha2.Sandesha2Constants;
import org.apache.sandesha2.SandeshaException;
import org.apache.sandesha2.context.ContextManager;
import org.apache.sandesha2.i18n.SandeshaMessageHelper;
import org.apache.sandesha2.i18n.SandeshaMessageKeys;
import org.apache.sandesha2.storage.StorageManager;
import org.apache.sandesha2.storage.Transaction;
import org.apache.sandesha2.storage.beanmanagers.InvokerBeanMgr;
import org.apache.sandesha2.storage.beanmanagers.RMDBeanMgr;
import org.apache.sandesha2.storage.beans.InvokerBean;
import org.apache.sandesha2.storage.beans.RMDBean;
import org.apache.sandesha2.util.Range;
import org.apache.sandesha2.util.RangeString;
import org.apache.sandesha2.util.SandeshaUtil;
/**
* This is used when InOrder invocation is required. This is a seperated Thread
* that keep running all the time. At each iteration it checks the InvokerTable
* to find weather there are any messages to me invoked.
*/
public class Invoker extends SandeshaThread {
private static final Log log = LogFactory.getLog(Invoker.class);
// If this invoker is working for several sequences, we use round-robin to
// try and give them all a chance to invoke messages.
int nextIndex = 0;
boolean processedMessage = false;
public Invoker() {
super(Sandesha2Constants.INVOKER_SLEEP_TIME);
}
/**
* Forces dispatch of queued messages to the application.
* NOTE: may break ordering
* @param ctx
* @param sequenceID
* @param allowLaterDeliveryOfMissingMessages if true, messages skipped over during this
* action will be invoked if they arrive on the system at a later time.
* Otherwise messages skipped over will be ignored
* @throws SandeshaException
*/
public synchronized void forceInvokeOfAllMessagesCurrentlyOnSequence(ConfigurationContext ctx,
String sequenceID,
boolean allowLaterDeliveryOfMissingMessages)throws SandeshaException{
//first we block while we wait for the invoking thread to pause
blockForPause();
try{
//get all invoker beans for the sequence
InvokerBeanMgr storageMapMgr = storageManager
.getInvokerBeanMgr();
RMDBeanMgr rmdBeanMgr = storageManager.getRMDBeanMgr();
RMDBean rMDBean = rmdBeanMgr.retrieve(sequenceID);
if (rMDBean != null) {
//The outOfOrder window is the set of known sequence messages (including those
//that are missing) at the time the button is pressed.
long firstMessageInOutOfOrderWindow = rMDBean.getNextMsgNoToProcess();
InvokerBean selector = new InvokerBean();
selector.setSequenceID(sequenceID);
Iterator stMapIt = storageMapMgr.find(selector).iterator();
long highestMsgNumberInvoked = 0;
Transaction transaction = null;
//invoke each bean in turn.
//NOTE: here we are breaking ordering
while(stMapIt.hasNext()){
//invoke the app
try{
transaction = storageManager.getTransaction();
InvokerBean invoker = (InvokerBean)stMapIt.next();
// start a new worker thread and let it do the invocation.
String workId = sequenceID + "::" + invoker.getMsgNo(); //creating a workId to uniquely identify the
//piece of work that will be assigned to the Worker.
String messageContextKey = invoker.getMessageContextRefKey();
InvokerWorker worker = new InvokerWorker(context,
messageContextKey,
true); //want to ignore the enxt msg number
worker.setLock(getWorkerLock());
worker.setWorkId(workId);
// Wrap the invoker worker with the correct context, if needed.
Runnable work = worker;
ContextManager contextMgr = SandeshaUtil.getContextManager(context);
if(contextMgr != null) {
work = contextMgr.wrapWithContext(work, invoker.getContext());
}
threadPool.execute(work);
//adding the workId to the lock after assigning it to a thread makes sure
//that all the workIds in the Lock are handled by threads.
getWorkerLock().addWork(workId);
long msgNumber = invoker.getMsgNo();
//if necessary, update the "next message number" bean under this transaction
if(msgNumber>highestMsgNumberInvoked){
highestMsgNumberInvoked = invoker.getMsgNo();
rMDBean.setNextMsgNoToProcess(highestMsgNumberInvoked+1);
if(allowLaterDeliveryOfMissingMessages){
//we also need to update the sequence OUT_OF_ORDER_RANGES property
//so as to include our latest view of this outOfOrder range.
//We do that here (rather than once at the end) so that we reamin
//transactionally consistent
Range r = new Range(firstMessageInOutOfOrderWindow,highestMsgNumberInvoked);
RangeString rangeString = null;
if(rMDBean.getOutOfOrderRanges()==null){
//insert a new blank one one
rangeString = new RangeString();
}
else{
rangeString = rMDBean.getOutOfOrderRanges();
}
//update the range String with the new value
rangeString.addRange(r);
rMDBean.setOutOfOrderRanges(rangeString);
}
rmdBeanMgr.update(rMDBean);
}
if(transaction != null && transaction.isActive()) transaction.commit();
transaction = null;
}
catch(Exception e){
// Just log the error
if(log.isDebugEnabled()) log.debug("Exception", e);
} finally {
if(transaction != null && transaction.isActive()) {
transaction.rollback();
transaction = null;
}
}
}//end while
}
}
finally{
//restart the invoker
finishPause();
}
}
private void addOutOfOrderInvokerBeansToList(String sequenceID,
StorageManager storageManager, List list)throws SandeshaException{
if (log.isDebugEnabled())
log.debug("Enter: InOrderInvoker::addOutOfOrderInvokerBeansToList " + sequenceID + ", " + list);
RMDBean rmdBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, sequenceID);
if(rmdBean != null && rmdBean.getOutOfOrderRanges() != null){
RangeString rangeString = rmdBean.getOutOfOrderRanges();
//we now have the set of ranges that can be delivered out of order.
//Look for any invokable message that lies in one of those ranges
InvokerBean selector = new InvokerBean();
selector.setSequenceID(sequenceID);
Iterator invokerBeansIterator =
storageManager.getInvokerBeanMgr().find(selector).iterator();
while(invokerBeansIterator.hasNext()){
InvokerBean invokerBean = (InvokerBean)invokerBeansIterator.next();
if(rangeString.isMessageNumberInRanges(invokerBean.getMsgNo())){
//an invoker bean that has not been deleted and lies in an out
//or order range - we can add this to the list
list.add(invokerBean);
}
}
}
if (log.isDebugEnabled())
log.debug("Exit: InOrderInvoker::addOutOfOrderInvokerBeansToList");
}
protected boolean internalRun() {
if (log.isDebugEnabled()) log.debug("Enter: Invoker::internalRun");
boolean sleep = false;
Transaction transaction = null;
try {
RMDBeanMgr nextMsgMgr = storageManager.getRMDBeanMgr();
InvokerBeanMgr storageMapMgr = storageManager
.getInvokerBeanMgr();
transaction = storageManager.getTransaction();
// Pick a sequence using a round-robin approach
ArrayList allSequencesList = getSequences();
int size = allSequencesList.size();
log.debug("Choosing one from " + size + " sequences");
if(nextIndex >= size) {
nextIndex = 0;
// We just looped over the set of sequences. If we didn't process any
// messages on this loop then we sleep before the next one
if(size == 0 || !processedMessage) {
sleep = true;
}
processedMessage = false;
if (log.isDebugEnabled()) log.debug("Exit: Invoker::internalRun, looped over all sequences, sleep " + sleep);
if(transaction != null && transaction.isActive()) transaction.commit();
transaction = null;
return sleep;
}
SequenceEntry entry = (SequenceEntry) allSequencesList.get(nextIndex++);
String sequenceId = entry.getSequenceId();
log.debug("Chose sequence " + sequenceId);
RMDBean nextMsgBean = nextMsgMgr.retrieve(sequenceId);
if (nextMsgBean == null) {
log.debug("Next message not set correctly. Removing invalid entry.");
stopThreadForSequence(sequenceId, entry.isRmSource());
allSequencesList = getSequences();
if (allSequencesList.size() == 0)
sleep = true;
if (log.isDebugEnabled()) log.debug("Exit: Invoker::internalRun, sleep " + sleep);
if(transaction != null && transaction.isActive()) transaction.commit();
transaction = null;
return sleep;
}
long nextMsgno = nextMsgBean.getNextMsgNoToProcess();
if (nextMsgno <= 0) {
// Make sure we sleep on the next loop, so that we don't spin in a tight loop
sleep = true;
if (log.isDebugEnabled())
log.debug("Invalid Next Message Number " + nextMsgno);
String message = SandeshaMessageHelper.getMessage(
SandeshaMessageKeys.invalidMsgNumber, Long
.toString(nextMsgno));
throw new SandeshaException(message);
}
InvokerBean selector = new InvokerBean();
selector.setSequenceID(sequenceId);
selector.setMsgNo(nextMsgno);
List invokerBeans = storageMapMgr.find(selector);
//add any msgs that belong to out of order windows
addOutOfOrderInvokerBeansToList(sequenceId,
storageManager, invokerBeans);
// If there aren't any beans to process then move on to the next sequence
if (invokerBeans.size() == 0) {
if (log.isDebugEnabled()) log.debug("Exit: Invoker::internalRun, no beans to invoke on sequence " + sequenceId + ", sleep " + sleep);
if(transaction != null && transaction.isActive()) transaction.commit();
transaction = null;
return sleep;
}
Iterator stMapIt = invokerBeans.iterator();
//TODO correct the locking mechanism to have one lock per sequence.
//TODO should this be a while, not an if?
if (stMapIt.hasNext()) { //some invokation work is present
InvokerBean bean = (InvokerBean) stMapIt.next();
//see if this is an out of order msg
boolean beanIsOutOfOrderMsg = bean.getMsgNo()!=nextMsgno;
String workId = sequenceId + "::" + bean.getMsgNo();
//creating a workId to uniquely identify the
//piece of work that will be assigned to the Worker.
//check whether the bean is already assigned to a worker.
if (getWorkerLock().isWorkPresent(workId)) {
// As there is already a worker assigned we are probably dispatching
// messages too quickly, so we sleep before trying the next sequence.
sleep = true;
String message = SandeshaMessageHelper.getMessage(SandeshaMessageKeys.workAlreadyAssigned, workId);
if (log.isDebugEnabled()) log.debug("Exit: Invoker::internalRun, " + message + ", sleep " + sleep);
if(transaction != null) {
transaction.commit();
transaction = null;
}
return sleep;
}
String messageContextKey = bean.getMessageContextRefKey();
if(transaction != null) {
transaction.commit();
transaction = null;
}
// start a new worker thread and let it do the invocation.
InvokerWorker worker = new InvokerWorker(context,
messageContextKey,
beanIsOutOfOrderMsg); //only ignore nextMsgNumber if the bean is an
//out of order message
worker.setLock(getWorkerLock());
worker.setWorkId(workId);
// Wrap the invoker worker with the correct context, if needed.
Runnable work = worker;
ContextManager contextMgr = SandeshaUtil.getContextManager(context);
if(contextMgr != null) {
work = contextMgr.wrapWithContext(work, bean.getContext());
}
threadPool.execute(work);
//adding the workId to the lock after assigning it to a thread makes sure
//that all the workIds in the Lock are handled by threads.
getWorkerLock().addWork(workId);
processedMessage = true;
}
if(transaction != null && transaction.isActive()) transaction.commit();
transaction = null;
} catch (Exception e) {
String message = SandeshaMessageHelper
.getMessage(SandeshaMessageKeys.invokeMsgError);
if(log.isDebugEnabled()) log.debug(message, e);
} finally {
if (transaction != null && transaction.isActive()) {
try {
transaction.rollback();
} catch (Exception e) {
String message = SandeshaMessageHelper.getMessage(
SandeshaMessageKeys.rollbackError, e.toString());
if(log.isDebugEnabled()) log.debug(message, e);
}
}
}
if (log.isDebugEnabled())
log.debug("Exit: InOrderInvoker::internalRun");
return sleep;
}
}