@SuppressWarnings("rawtypes")
  @Override
  protected Collection acquireJobs() {
    Collection jobs = Collections.EMPTY_LIST;

    if ((isActive) && (!alfrescoJobExecutor.getTransactionService().isReadOnly())) {
      try {
        jobs =
            alfrescoJobExecutor
                .getTransactionService()
                .getRetryingTransactionHelper()
                .doInTransaction(
                    new RetryingTransactionHelper.RetryingTransactionCallback<Collection>() {
                      public Collection execute() throws Throwable {
                        if (jobLockToken != null) {
                          refreshExecutorLock(jobLockToken);
                        } else {
                          jobLockToken = getExecutorLock();
                        }

                        try {
                          return AlfrescoJobExecutorThread.super.acquireJobs();
                        } catch (Throwable t) {
                          logger.error("Failed to acquire jobs");
                          releaseExecutorLock(jobLockToken);
                          jobLockToken = null;
                          throw t;
                        }
                      }
                    });

        if (jobs != null) {
          if (logger.isDebugEnabled() && (!logger.isTraceEnabled()) && (!jobs.isEmpty())) {
            logger.debug("acquired " + jobs.size() + " job" + ((jobs.size() != 1) ? "s" : ""));
          }

          if (logger.isTraceEnabled()) {
            logger.trace(
                "acquired "
                    + jobs.size()
                    + " job"
                    + ((jobs.size() != 1) ? "s" : "")
                    + ((jobs.size() > 0) ? ": " + jobs.toString() : ""));
          }

          if (jobs.size() == 0) {
            releaseExecutorLock(jobLockToken);
            jobLockToken = null;
          }
        }
      } catch (LockAcquisitionException e) {
        // ignore
        jobLockToken = null;
      }
    }

    return jobs;
  }
  private String getExecutorLock() {
    String lockToken = null;

    if (alfrescoJobExecutor.getJobExecutorLockEnabled()) {
      try {
        lockToken =
            alfrescoJobExecutor.getJobLockService().getLock(LOCK_QNAME, jobLockTTL, 3000, 10);

        if (logger.isTraceEnabled()) {
          logger.trace(Thread.currentThread().getName() + " got lock token: " + lockToken);
        }
      } catch (LockAcquisitionException e) {
        if (logger.isTraceEnabled()) {
          logger.trace(
              "Failed to get Alfresco Job Executor lock - may already running in another thread");
        }
        throw e;
      }
    }

    return lockToken;
  }
  @Override
  protected Date getNextDueDate() {
    if (!isActive) {
      return null;
    }

    return alfrescoJobExecutor
        .getTransactionService()
        .getRetryingTransactionHelper()
        .doInTransaction(
            new RetryingTransactionHelper.RetryingTransactionCallback<Date>() {
              public Date execute() throws Throwable {
                return AlfrescoJobExecutorThread.super.getNextDueDate();
              }
            },
            true);
  }
  private void releaseExecutorLock(String lockToken) {
    if (lockToken != null) {
      try {
        alfrescoJobExecutor.getJobLockService().releaseLock(lockToken, LOCK_QNAME);

        if (logger.isTraceEnabled()) {
          logger.trace(Thread.currentThread().getName() + " released lock token: " + lockToken);
        }
      } catch (LockAcquisitionException e) {
        if (logger.isTraceEnabled()) {
          logger.trace(
              "Failed to release Alfresco Job Executor lock - may no longer exist ("
                  + lockToken
                  + ")");
        }
        throw e;
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  protected void executeJob(final Job jobIn) {
    if ((!isActive) || (alfrescoJobExecutor.getTransactionService().isReadOnly())) {
      return;
    }

    // based on JBPM 3.3.1 (JobExecutorThread.executeJob)
    // - wrap executeJob / deleteJob in Alfresco retries
    // - add setRollbackOnly warnings
    // - if Alfresco retries fail, attempt to set JBPM job exception/retries

    try {
      RetryingTransactionHelper tranHelper =
          alfrescoJobExecutor.getTransactionService().getRetryingTransactionHelper();
      tranHelper.doInTransaction(
          new RetryingTransactionCallback<Object>() {
            public Object execute() throws Throwable {
              JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
              try {
                JobSession jobSession = jbpmContext.getJobSession();
                Job job = jobSession.loadJob(jobIn.getId());

                if (logger.isTraceEnabled()) {
                  logger.trace("executing " + job);
                }

                if (job.execute(jbpmContext)) {
                  jobSession.deleteJob(job);

                  if (logger.isDebugEnabled()) {
                    logger.debug("executed and deleted: " + job);
                  }
                }

                // if this job is locked too long
                long totalLockTimeInMillis =
                    System.currentTimeMillis() - job.getLockTime().getTime();
                if (totalLockTimeInMillis > jbpmMaxLockTime) {
                  logger.warn(
                      "setRollbackOnly: exceeded maxLockTime (" + jbpmMaxLockTime + ") " + job);
                  jbpmContext.setRollbackOnly();
                }
              } finally {
                jbpmContext.close();
              }

              return null;
            }
          });
    } catch (LockAcquisitionException e) {
      // ignore
      jobLockToken = null;
    } catch (Exception e) {
      if (logger.isErrorEnabled()) {
        logger.error("failed to execute " + jobIn, e);
      }

      if (!isPersistenceException(e)) {
        try {
          final StringWriter memoryWriter = new StringWriter();
          e.printStackTrace(new PrintWriter(memoryWriter));

          RetryingTransactionHelper tranHelper =
              alfrescoJobExecutor.getTransactionService().getRetryingTransactionHelper();
          tranHelper.doInTransaction(
              new RetryingTransactionCallback<Object>() {
                public Object execute() throws Throwable {
                  JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
                  try {
                    JobSession jobSession = jbpmContext.getJobSession();
                    final Job job = jobSession.loadJob(jobIn.getId());

                    if (logger.isDebugEnabled()) {
                      logger.debug("attempting to update exception/retries: " + job);
                    }

                    job.setException(memoryWriter.toString());
                    job.setRetries(job.getRetries() - 1);

                    if (logger.isInfoEnabled()) {
                      logger.info(
                          "updated job exception and set to "
                              + job.getRetries()
                              + " retries: "
                              + jobIn);
                    }
                  } finally {
                    jbpmContext.close();
                  }

                  return null;
                }
              });
        } catch (Exception e2) {
          if (logger.isErrorEnabled()) {
            logger.error("failed to update job exception/retries " + jobIn, e2);
          }
        }
      }
    }
  }