/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.soa.esb.services.jbpm.integration.msg;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.db.JobSession;
import org.jbpm.job.Job;
import org.jbpm.job.Timer;
/**
* Executor responsible for checking for retry invocations. The DB service does
* this implicitly but we need to explicitly check for this during JMS.
*
* @author <a href='mailto:kevin.conner@jboss.com'>Kevin Conner</a>
*/
public class RetryExecutor implements Runnable
{
/**
* The lock name associated with the executor.
*/
public static final String RETRY_EXECUTOR = "RetryExecutor" ;
/**
* The logger for this class.
*/
private static final Logger LOGGER = Logger.getLogger(RetryExecutor.class) ;
/**
* terminate the executor thread.
*/
private boolean terminateRetryExecutorThread ;
/**
* The wait lock.
*/
private Lock waitLock = new ReentrantLock() ;
/**
* The wait condition.
*/
private Condition waitCondition = waitLock.newCondition() ;
/**
* The normal idle interval.
*/
private final int idleInterval ;
/**
* The maximum idle interval.
*/
private int maxIdleInterval ;
/**
* The maximum number of jobs to reschedule on each poll.
*/
private int maxRetryJobs ;
/**
* Create the retry executor thread.
* @param idleInterval The idle interval.
* @param maxIdleInterval The max idle interval.
* @param maxRetryJobs The max number of retry jobs.
*/
public RetryExecutor(final int idleInterval, final int maxIdleInterval, final int maxRetryJobs)
{
this.idleInterval = idleInterval ;
this.maxIdleInterval = maxIdleInterval ;
this.maxRetryJobs = maxRetryJobs ;
}
/**
* Look for newly active suspended/retry jobs.
*/
public void run()
{
int currentIdleInterval = idleInterval ;
while(true)
{
final JbpmContext jbpmContext = JbpmConfiguration.getInstance().createJbpmContext() ;
try
{
final JobSession jobSession = jbpmContext.getJobSession() ;
if (jobSession != null)
{
final Set<Long> monitoredJobs = new HashSet<Long>() ;
final long now = System.currentTimeMillis() ;
do
{
final Job job = jobSession.getFirstDueJob(RETRY_EXECUTOR, monitoredJobs) ;
if ((job == null) || (job.getDueDate().getTime() > now))
{
break ;
}
monitoredJobs.add(Long.valueOf(job.getId())) ;
if (RETRY_EXECUTOR.equals(job.getLockOwner()))
{
job.setLockOwner(null) ;
if (job instanceof Timer)
{
final Timer timer = (Timer)job ;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Rescheduling timer " + timer.getId());
}
jbpmContext.getServices().getSchedulerService().createTimer(timer);
}
else
{
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Rescheduling job " + job.getId());
}
jbpmContext.getServices().getMessageService().send(job) ;
}
}
}
while(monitoredJobs.size() < maxRetryJobs) ;
}
if (currentIdleInterval > idleInterval)
{
currentIdleInterval >>= 1;
if (currentIdleInterval < idleInterval)
{
currentIdleInterval = idleInterval ;
}
}
}
catch (final Throwable th)
{
LOGGER.debug("Unexpected error rescheduling jobs, extending idle period", th);
// after an exception, the current idle interval is doubled to prevent
// continuous exception generation when e.g. the db is unreachable
currentIdleInterval <<= 1;
if (currentIdleInterval > maxIdleInterval || currentIdleInterval < 0)
{
currentIdleInterval = maxIdleInterval;
}
}
finally
{
jbpmContext.close() ;
}
waitLock.lock() ;
try
{
if (!terminateRetryExecutorThread)
{
try
{
waitCondition.await(currentIdleInterval, TimeUnit.MILLISECONDS) ;
}
catch (final InterruptedException ie) {} // ignore
}
if (terminateRetryExecutorThread)
{
break ;
}
}
finally
{
waitLock.unlock() ;
}
}
}
/**
* Request a termination of the loop.
*/
public void terminate()
{
waitLock.lock() ;
try
{
terminateRetryExecutorThread = true ;
waitCondition.signal() ;
}
finally
{
waitLock.unlock() ;
}
}
/**
* Process a suspended job.
* @param job The suspended job.
*/
public static void handleSuspendedJob(final Job job)
{
job.setLockOwner(RETRY_EXECUTOR) ;
JbpmConfiguration.getInstance().getCurrentJbpmContext().getServices().getMessageService().send(job) ;
}
/**
* Process a suspended timer.
* @param timer The suspended timer.
*/
public static void handleSuspendedTimer(final Timer timer)
{
timer.setLockOwner(RETRY_EXECUTOR) ;
JbpmConfiguration.getInstance().getCurrentJbpmContext().getServices().getSchedulerService().createTimer(timer) ;
}
}