Today I’ll be talking about a very common task in Nuxeo: How to regularly run business logic, server-side.

There are many reasons why one would want to do this, such as for generating a weekly or monthly report, performing a quarterly “cleaning” of obsolete documents, connecting daily to an external service, etc.

The way this works is quite simple:

  • A scheduler periodically sends an event.
  • A handler is assigned to this event, and performs its duty every time the event is initiated.

Scheduler, Event, Handler: Each of these items can be configured very easily with Nuxeo Studio, the best tool on the planet (which I may have mentioned a few times in previous posts).

For this post, I’ll use “Handling expired assets” as my use case example.

Let’s say I have Picture, Video, and Audio Documents, and some of them have an expiration date (field dc:expired) setup. They’re available to everyone, but as soon as their expiration date is reached, the logic must apply. For this example, we’ll keep the logic simple: Once expired, we move it to another lifecycle state (“obsolete”) and change the permissions on the documents so that only administrators and members of the Validators group can now access them. There are many different ways to handle expired assets, which all depend on your specific business rules and processes (above was just one example).

Let’s dive a bit deeper into the different elements in the following order:

  • First, the scheduler (since it’s at the core of everything)
  • Then, the handler (nothing else than an automation chain in my example)
  • And finally, the event

The Scheduler

Nuxeo is built on a Component Model architecture where everything is a plugin, and everything is configurable via extension points. Since Nuxeo is the coolest Content Services Platform, it also comes with - among hundreds of other services - a configurable Scheduler Service, which allows for the ability to add any number of schedulers that may be needed.

The way a scheduler works is quite simple - at least four properties need to be defined:

  • A unique ID for the scheduler. Interestingly, this is a way to override one of the default Nuxeo schedulers. For example, nuxeo-imap-connector checks for new mail every five minutes, but if you want to change this frequency, just paste the definition and change the cronExpression part (see below).
  • The ID of the event to be sent.
  • An event category (“default” is fine).
  • The cron expression defining the frequency at which the event will be fired.

Cultural Side Note: Because being a developer doesn’t mean being a barbarian, we can still be interested in etymology, I looked into the origin of the word “cron” on Wikipedia, which says: “The origin of the name cron is from the Greek word for time, χρόνος (chronos)”. This totally makes sense.

The expression to use is detailed in this documentation page (with examples). In our case, we want to check for expired assets every day at 3am, so the cronExpression should be…
* * 3 * * ?
… which means (from left to right): at second 0; minute 0; when hour equals 3; any day of the month; any month; any day of the week.

Now I don’t know about you, but, I’m not a big fan of waking up at 3am to see if my scheduler is working, and if not, to fix it and wait until the day after in the middle of the night to check it again. It’s also not compatible when you have a demo due in two days.

For our testing scenario use case, we’ll run the scheduler every 30 seconds. The following expression runs it at exactly hh:mm:00s and hh:mm30s. Copy this XML extension and paste it to your Studio project, in CONFIGURATION > Advanced Settings > XML Extensions:

<extension
           target="org.nuxeo.ecm.core.scheduler.SchedulerService"
           point="schedule">
  <schedule id="`HandleExpiredAssets`">
    <event>`HandleExpiredAssets`</event>
    <eventCategory>default</eventCategory>
    <!-- Every day at 3am: * * 3 * * ? →
    <!-- For our test, let's do it every 30s at hh:mm:00 and hh:mm:30-->
    <cronExpression>0/30 * * * * ?</cronExpression>
  </schedule>
</extension>

This defines a new scheduler with the ID of HandleExpiredAssets, and every 30 seconds, it will fire the HandleExpiredAssets event`. I used the same values here for both the scheduler ID and the event ID, but this is not a requirement.

And just like that, we’re done with the scheduler part. I told you that was easy! Seriously - once you know how, it takes 15 seconds to create a scheduler. That’s how rocking’ Nuxeo is!

The Handler

Next, we’ll write the automation chain that handles our expired assets. We simply need to:

  1. Search for assets that have a dc:expired field noted as earlier than today, and that have not yet been handled
  2. Handle them

In our example, we’ll handle these assets by:

  1. Changing their lifecycle state
  2. Changing their permission settings so that only administrators and Validators can now access them

We’ll need to have a way to handle only the new expired assets, and our example will be based on the lifecycle state. We have this lifecycle policy for Picture, Video, and Audio documents:

Handler lifecyle

Our query searches only for assets that are in the “approved” lifecycle state, and then moves them into the “obsolete” state. This is the NXQL query to use when setting up this automated process (as usual, we don’t want to try to modify versions and proxies):

SELECT * FROM Document WHERE
ecm:primaryType IN ('Picture','Video', 'Audio')
AND ecm:currentLifeCycleState = 'approved'
AND dc:expired < DATE '@{CurrentDate.format("yyyy-MM-dd")}'
AND ecm:isVersion = 0 AND ecm:isProxy = 0

Changing the lifecycle state is easy, just use the Document.FollowLifecycleTransition operation.

Let’s now change the permissions, which entails:

  • Removing the “local” permission (if it existed)
  • Blocking inheritance (this automatically adds administrators as the only group able to see the documents)
  • Creating a new permission for Validators

This level of automation is incredibly powerful! Automating the handling of entire lists of documents by just allowing this operation to do all the magic saves a lot of time and resources.

There are two very important points to keep in mind here, which are related to the fact that the chain runs from the scheduler, so when it starts, there’s no Context or CoreSession. This means that you must first remove the Fetch > ContextDocument operation that Studio ads by default. Next, you must call LoginAs before performing any query, because LoginAs will create a CoreSession. Below is the full chain for your reference. I added a log operation so we can monitor what is happening and see how our scheduler works, how it fires the event, and how our chain is executed:

Handler Chain

Looks good, doesn’t it? We’ve covered the scheduler and the handler, so let’s now create the event that performs the glue between the two of them.

The Event

Like the other two operations, setting up an event is very easy. We just need to create a new Event Handler that:

  1. Listens to our HandleExpiredAssets event
  2. Runs the HandleExpiredAssets chain when the event is catched

And yes, I tend to use the same naming conventions everywhere. If you find this confusing, please feel free to make up your own.

Creating an Event Handler in Studio is quite simple, but in our use case example, we have a small issue that must be solved.

HandleExpiredAssets is a custom event, so Nuxeo knows nothing about it since it’s not part of the default configuration. Here is when Studio Registries enters the game. This is where you declare the elements that Studio does not know, and then Studio will make them available everywhere it’s relevant. So, we go to SETTING > Registries > Core Events and add our custom event. For example:

Registry

We can now create a new Event Handler, and using this event, it’s now listed in Studio. So, we create the event, select our event and our automation chain, and voilà!

Event Listener

Deploy, Enjoy

What I just covered is an example of a configuration that can be done very quickly - probably in less time that it takes to read how to do it!

My advice? Save everything, deploy on your test server, and monitor your log. Just remember we’re using two very specific elements: A custom lifecycle policy and a custom group (Validators). Maybe begin by opening a Picture Document, modify its dc:expired to yesterday and see if the log shows it was found, then 30 second later if it’s not.

This was a simple example, and your business rule will probably be more complex. But with Nuxeo Studio, you have all of the tools to build anything you need. For example, maybe you want to end mail notifications one week before expiration, so a user could handle the asset, check and change the expiration date, etc.

I hope my boss doesn’t find out that my expiration date was reached a looooooooooooong time ago.