package org.jbpm.job.executor; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.persistence.EntityNotFoundException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.StaleStateException; import org.hibernate.exception.LockAcquisitionException; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmContext; import org.jbpm.db.JobSession; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.graph.node.TaskNode; import org.jbpm.job.Job; import org.jbpm.job.Timer; import org.jbpm.persistence.JbpmPersistenceException; import org.jbpm.persistence.db.StaleObjectLogConfigurer; import org.jbpm.svc.Services; import org.jbpm.taskmgmt.exe.TaskInstance; import net.conselldemallorca.helium.jbpm3.integracio.Jbpm3HeliumBridge; public class JobExecutorThread extends Thread { private static Log log = LogFactory.getLog(JobExecutorThread.class); public JobExecutorThread( String name, JobExecutor jobExecutor, JbpmConfiguration jbpmConfiguration, int idleInterval, int maxIdleInterval, long maxLockTime, int maxHistory ) { super(name); this.jobExecutor = jobExecutor; this.jbpmConfiguration = jbpmConfiguration; this.idleInterval = idleInterval; this.maxIdleInterval = maxIdleInterval; this.maxLockTime = maxLockTime; } final JobExecutor jobExecutor; final JbpmConfiguration jbpmConfiguration; final int idleInterval; final int maxIdleInterval; final long maxLockTime; int currentIdleInterval; volatile boolean isActive = true; protected static Map<Long, String[]> jobErrors = new HashMap<Long, String[]>(); protected static Map<Long, String> jobUser = new HashMap<Long, String>(); @SuppressWarnings("rawtypes") public void run() { currentIdleInterval = idleInterval; while (Jbpm3HeliumBridge.getInstanceService() == null){ try { log.info("Job executor: esperant a obtenir el InstanceService"); Thread.sleep(500); } catch (Exception ex) {} } // Inicialitzam les definicions de procés per a poder tenir accés als seus handlers Jbpm3HeliumBridge.getInstanceService().initializeDefinicionsProces(); while (isActive) { try { Collection acquiredJobs = acquireJobs(); if (! acquiredJobs.isEmpty()) { Iterator iter = acquiredJobs.iterator(); while (iter.hasNext() && isActive) { Job job = (Job) iter.next(); boolean error = false; Throwable terror = null; try { executeJob(job); } // on exception, call returns normally catch (Exception e) { error = true; terror = e; } catch (Error e) { error = true; terror = e; } if (error) { saveJobException(job, terror); try{ executeJob(job); }catch(Exception ex){ log.error("error no controlat en el job executor thread.", ex); } break; } } } else { // no jobs acquired if (isActive) { long waitPeriod = getWaitPeriod(); if (waitPeriod>0) { synchronized(jobExecutor) { jobExecutor.wait(waitPeriod); } } } } // no exception so resetting the currentIdleInterval currentIdleInterval = idleInterval; } catch (InterruptedException e) { log.info((isActive? "active" : "inactive")+" job executor thread '"+getName()+"' got interrupted"); } catch (Exception e) { log.error("exception in job executor thread. waiting "+currentIdleInterval+" milliseconds", e); try { synchronized(jobExecutor) { jobExecutor.wait(currentIdleInterval); } } catch (InterruptedException e2) { log.debug("delay after exception got interrupted", e2); } // 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; } } } log.info(getName()+" leaves cyberspace"); } @SuppressWarnings("rawtypes") protected Collection acquireJobs() { Collection acquiredJobs; synchronized (jobExecutor) { log.debug("acquiring jobs for execution..."); List jobsToLock = Collections.EMPTY_LIST; List<Job> jobs = new ArrayList<Job>(); JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { JobSession jobSession = jbpmContext.getJobSession(); String lockOwner = getName(); log.debug("querying for acquirable job..."); Job job = jobSession.getFirstAcquirableJob(lockOwner); if (job!=null) { Hibernate.initialize(job.getProcessInstance()); if (job.isExclusive()) { log.debug("found exclusive " + job); ProcessInstance processInstance = job.getProcessInstance(); log.debug("finding other exclusive jobs for " + processInstance); jobsToLock = jobSession.findExclusiveJobs(lockOwner, processInstance); log.debug("trying to obtain exclusive locks on " + jobsToLock + " for " + processInstance); } else { log.debug("trying to obtain lock on " + job); jobsToLock = Collections.singletonList(job); } Date lockTime = new Date(); for (Iterator iter = jobsToLock.iterator(); iter.hasNext();) { boolean deleted = false; job = (Job) iter.next(); if (jobErrors.get(job.getId()) != null) { int retries = job.getRetries() - 1; if (retries <= 0) { jobSession.deleteJob(job); log.error("Job (" + job.getId() + ") eliminat."); deleted = true; } else { job.setRetries(job.getRetries() - 1); jobSession.saveJob(job); } jobErrors.remove(job.getId()); } if (!deleted) { job.setLockOwner(lockOwner); job.setLockTime(lockTime); Hibernate.initialize(job.getProcessInstance().getExpedient().getEntorn()); Hibernate.initialize(job.getProcessInstance().getExpedient().getTipus()); jobs.add(job); if (job instanceof Timer) { Timer timer = (Timer)job; if (job.getTaskInstance() == null) { Long tiId = 0L; String user = null; if (timer.getGraphElement() != null && timer.getGraphElement() instanceof TaskNode) { TaskNode tn = (TaskNode)timer.getGraphElement(); for (TaskInstance ti: job.getProcessInstance().getTaskMgmtInstance().getTaskInstances()) { if (ti.getToken().equals(job.getToken()) && ti.getTask() != null && ti.getTask().getTaskNode() != null && ti.getTask().getTaskNode().equals(tn)) { if (ti.getId() > tiId) { if (ti.getActorId() != null) { tiId = ti.getId(); user = ti.getActorId(); } else if (ti.getPooledActors() != null && !ti.getPooledActors().isEmpty()) { tiId = ti.getId(); user = ti.getPooledActors().iterator().next().getActorId(); } } jobUser.put(job.getId(), user); } } } } } } } } else { log.debug("no acquirable jobs in job table"); } } finally { try { jbpmContext.close(); // acquiredJobs = jobsToLock; acquiredJobs = jobs; log.debug("obtained lock on jobs: "+acquiredJobs); } catch (JbpmPersistenceException e) { // if this is a stale object exception, keep it quiet if (Services.isCausedByStaleState(e)) { log.debug("optimistic locking failed, couldn't obtain lock on jobs "+jobsToLock); acquiredJobs = Collections.EMPTY_LIST; } else { throw e; } } } } return acquiredJobs; } protected void executeJob(Job job) throws Exception { JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { JobSession jobSession = jbpmContext.getJobSession(); job = jobSession.loadJob(job.getId()); try { if (jobErrors.get(job.getId()) != null) { job.setRetries(job.getRetries() - 1); jobErrors.remove(job.getId()); } if (job.getRetries() <= 0) { jobSession.deleteJob(job); log.error("Job (" + job.getId() + ") eliminat."); return; } } catch (EntityNotFoundException enf) { return; } try { log.debug("executing " + job); if (job.execute(jbpmContext)) { jobSession.deleteJob(job); } } catch (Exception e) { if (isPersistenceException(e) || !isLockingException(e)) { throw e; } else { // prevent unsafe use of the session after an exception occurs // jbpmContext.setRollbackOnly(); log.debug("exception while executing " + job, e); StaleObjectLogConfigurer.getStaleObjectExceptionsLog().error("failed to complete " + job); } } catch (Error e) { // jbpmContext.setRollbackOnly(); throw e; } // if this job is locked too long long totalLockTimeInMillis = System.currentTimeMillis() - job.getLockTime().getTime(); if (totalLockTimeInMillis>maxLockTime) { jbpmContext.setRollbackOnly(); } } finally { try { jbpmContext.close(); } catch (JbpmPersistenceException e) { // if this is a stale state exception, keep it quiet if (Services.isCausedByStaleState(e)) { log.debug("optimistic locking failed, couldn't complete job "+job); } else { throw e; } } } } protected void decrementJobRetries(Job job) { log.error("Job (" + job.getId() + ") >> decrementant reintents després d'un error."); JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); JobSession jobSession = jbpmContext.getJobSession(); int retries = job.getRetries() - 1; if (retries <= 0) { jobSession.deleteJob(job); log.error("Job (" + job.getId() + ") eliminat."); } else { job.setRetries(retries); jobSession.saveJob(job); log.error("Job (" + job.getId() + ") decrementat."); } } // private boolean jobHasBeenExecutedWithError(Job job) { // if (SpringJobExecutorThread.jobErrors.containsKey(job.getId())) // return true; // return job.getException() != null; // } // // private String getJobException(Job job) { // if (SpringJobExecutorThread.jobErrors.containsKey(job.getId())) { // String excepcio = SpringJobExecutorThread.jobErrors.get(job.getId())[1]; // SpringJobExecutorThread.jobErrors.remove(job.getId()); // return excepcio; // } // return job.getException(); // } protected void saveJobException(Job job, Throwable exception) { StringWriter out = new StringWriter(); exception.printStackTrace(new PrintWriter(out)); job.setException(out.toString()); } private static boolean isPersistenceException(Throwable throwable) { do { if (throwable instanceof HibernateException) return true; throwable = throwable.getCause(); } while (throwable != null); return false; } private static boolean isLockingException(Throwable throwable) { for (Throwable t = throwable; t != null; t = t.getCause()) { if (t instanceof StaleStateException || t instanceof LockAcquisitionException) { return true; } } return false; } @SuppressWarnings("rawtypes") protected Date getNextDueDate() { Date nextDueDate = null; JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); try { JobSession jobSession = jbpmContext.getJobSession(); Collection jobIdsToIgnore = jobExecutor.getMonitoredJobIds(); Job job = jobSession.getFirstDueJob(getName(), jobIdsToIgnore); if (job!=null) { nextDueDate = job.getDueDate(); jobExecutor.addMonitoredJobId(getName(), job.getId()); } } finally { try { jbpmContext.close(); } catch (JbpmPersistenceException e) { // if this is a stale object exception, keep it quiet if (Services.isCausedByStaleState(e)) { log.debug("optimistic locking failed, couldn't get next due date"); nextDueDate = null; } else { throw e; } } } return nextDueDate; } protected long getWaitPeriod() { long interval = currentIdleInterval; Date nextDueDate = getNextDueDate(); if (nextDueDate!=null) { long currentTime = System.currentTimeMillis(); long nextDueTime = nextDueDate.getTime(); if (nextDueTime < currentTime+currentIdleInterval) { interval = nextDueTime-currentTime; } } if (interval<0) { interval = 0; } return interval; } /** * @deprecated As of jBPM 3.2.3, replaced by {@link #deactivate()} */ public void setActive(boolean isActive) { if (isActive == false) deactivate(); } /** * Indicates that this thread should stop running. * Execution will cease shortly afterwards. */ public void deactivate() { if (isActive) { isActive = false; interrupt(); } } }