You are here

Scheduled tasks and authenticated transactions

Simon Hutchinson's picture
Simon Hutchinson

A requirement that seems to come up in various guises on different projects can be defined in high level terms as follows:

At some given time (Midnight being a favorite) find items based on some criteria that includes a timeframe (Often the previous day) and perform some action on the results.

So we fire up our IDE and create a class with a public method doDailyThing(). We wire the object up in our spring context file and inject the Alfresco search service which will be used for the “find” aspect of our requirement and the node service which will be used for updating the results of our search.

<bean id="blog.nightlyTask" class="com.ixxus.blog.tasks.NightlyTask" >
  <property name="searchService" ref="searchService" />
  <property name="nodeService" ref="nodeService" />
</bean>

So now we have the bean for performing our task we are almost finished. Our final requirement is to ensure that this task is executed at midnight each day, what we need is some kind of chronological trigger.... Thankfully Alfresco have provided just what we need in the shape of the CronTriggerBean.

So we wire this up in our spring context:

<bean id="blog.nightlyTaskTrigger"class="org.alfresco.util.CronTriggerBean">
  <property name="jobDetail">
    <bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
      <property name="targetObject"ref="blog.nightlyTask" />
      <property name="targetMethod" value="doDailyThing" />
      <property name="concurrent" value="false" />
    </bean>
  </property>
  <property name="scheduler">
    <ref bean="schedulerFactory" />
  </property>
  <!-- Run at midnight each night -->
  <property name="cronExpression" value="0 0 0 * * ?" />
</bean>

The main properties to be aware of here are the targetObject which is a reference to the object that represents our job, the targetMethod to call on that object and finally the cronExpression which is text in standard crontab format which specifies the schedule period for triggering the task.

So it looks like we are finished. Unfortunately when we arrive at work the next morning and check our log file we see security exceptions being thrown when the job runs.

The problem is that our cron job is running in it own thread and has not authenticated to Alfresco which is causing the nodeService to fail.

So how do we ensure that our job is executed in an authenticated transaction. Even better if we apply the Don’t Repeat Yourself (DRY) principle how can we ensure that all of our system cron jobs execute in an authenticated manner.

Fortunately the Alfresco API has provided us with with a static method for executing a unit of work as a given user. The class is org.alfresco.repo.security.authentication.AuthenticationUtil and the method runAs has the following signature:

public static java.lang.Object runAs(AuthenticationUtil.RunAsWork runAsWork, java.lang.String uid)

For more information, see the javadocs for AuthenticationUtil.

Now we’re cooking with gas.

So once again we fire up the IDE and create a new abstract class that looks something like this:

public abstract class AbstractTransactionJob {
  private String runAsUser;
  private TransactionService transactionService;
  public void execute() {

  AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>() {
    public Object doWork() {
      executeWithinTransaction();
      return null;
    }

    private void executeWithinTransaction() {
      getTransactionService().getRetryingTransactionHelper().doInTransaction(
        new RetryingTransactionCallback<Object>() {
          public Object execute() throws Exception {
            try {
            doExecute();
            return null;
          } catch (Throwable t) {
            throw new Exception();
          }
        }
      });
    }
  }, getRunAsUser());
}

public String getRunAsUser() {
  return runAsUser;
}

public void setRunAsUser(String runAsUser) {
  this.runAsUser = runAsUser;
}

protected abstract void doExecute () throws ExecutionException;

public TransactionService getTransactionService() {
  return transactionService;
}

public void setTransactionService(TransactionService transactionService) {
  this.transactionService = transactionService;
}

Concrete subclasses must implement the abstract doExecute() method which will now run authenticated as the the user set by the setRunAsUser() method (note this is simply for flexibility, If we always wanted the System user to run our jobs then we could use another static method provided by the Alfresco provided on the AuthenticaionUtil class, getSystemUserName().

The final step is for us to re-factor our NightlyTask class to extend our new AbstractTransactionJob class and to ensure that its implementation of doExecute() calls its doDailyThing() method. Our spring wiring now looks something like this.

<bean id="blog.abstractJob" abstract="true">
  <property name="transactionService">
    <ref bean="TransactionService"/>
  </property>
  <property name="runAsUser">
    <value>System</value>
  </property>
</bean>

We need to add the parent attribute to our task bean to let spring know about what it has extended.

<bean id="blog.nightlyTask" class="com.ixxus.blog.tasks.NightlyTask"
parent=”blog.abstractJob” >
  <property name="searchService" ref="searchService" />
  <property name="nodeService" ref="nodeService" />
</bean>

Now our task runs authenticated (in this case as SYSTEM) and any future tasks can extend the same abstract parent.

Add new comment