Upcoming document expiration notificationNotification of impending expiration of a document

Here’s a very practical question from blaszta:

How can I set an email notification about the impending expiration of a document (let’s say a 3 year contract leased document), 1 month before it expires?

Let’s break this in two parts. First, get the contract that expires in 1 month. Then send the email.

Query the contracts

If you’re looking for specific document like a contract, there’s a good chance you have an easy way to identify one using its type or some metadata. Let’s say all contract documents are of type Contract. If I want all of them I can do the following query:

 Select * From Contract

This gets me all the contract documents, even the deleted ones or different versions of the same document. To avoid this we can add the following parameters:

 Select * From Contract WHERE ecm:isCheckedInVersion = 0 AND
 ecm:currentLifeCycleState != 'deleted'

Now you want to add something that gets you only Contracts that expire in 30 days, no more, no less. I’ll let you compute the date yourself :) - you get the idea.

 Select * From Contract WHERE ecm:isCheckedInVersion = 0 AND
 ecm:currentLifeCycleState != 'deleted' AND dc:expired = DATE '2007-02-15'

If you want more information about queries in Nuxeo, take a look at the NXQL documentation.

Now we know we can get those documents, we can move on to the second part.

Send the email

We need to send the notifications only once a day. For that we can use an event listener coupled with the scheduler service. We’ll send the event notifContract one time per day every day. Then we can add a listener to the notifContract event that does the query and sends the notifications.

Here’s a contribution to send a notifContract event every day at 1am.

 <?xml version="1.0"?>
 <component name="org.nuxeo.sample.scheduler.notifContract">
 <extension
 target="org.nuxeo.ecm.core.scheduler.SchedulerService"
 point="schedule">
 <schedule id="notifContractScehdulerId">
 <username>Administrator</username>
 <eventId>notifContract</eventId>
 <eventCategory>default</eventCategory>
 <!-- every day at 1 am -->
 <cronExpression>0 0 1 * * ?</cronExpression>
 </schedule>
 </extension>
 </component>

And here’s the associated listener:

 <?xml version="1.0"?>
 <component name="org.nuxeo.sample.listener.contrib.notifContract">
 <extension target="org.nuxeo.ecm.core.event.EventServiceComponent"
 point="listener">
 <listener name="notifContractListener" async="true" postCommit="false"
 class="org.nuxeo.sample.NotifContractListener" order="140">
 <event>notifContract</event>
 </listener>
 </extension>
 </component>

Now we have to write the NotifContractListener class so that it queries contracts and sends an email for each contract. There are multiple ways to send the email. You could handle everything yourself, which means gathering users that need notifications for each document, then rendering and sending an email to everyone of them. Or you could use the notification service provided by Nuxeo, which is much simpler and what we’re going to do.

We’re going to send a new event for each document from the query, with the document attached. Then we’ll make this event available as a usual notification, like the ones you find on the Alert tab.

First comes the listener implementation, where we execute the query and send an event for each document returned by the query.

package org.nuxeo.sample;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.event.Event;
import org.nuxeo.ecm.core.event.EventListener;
import org.nuxeo.ecm.core.event.EventService;
import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
import org.nuxeo.runtime.api.Framework;

/**
* @author ldoguin
*/
public class ContractNotifListener implements EventListener {

private static final String QUERY_CONTRACTS = "Select * From Contract WHERE ecm:isCheckedInVersion = 0 AND ecm:currentLifeCycleState != 'deleted' AND dc:expired = DATE '%s'";

public void handleEvent(Event event) throws ClientException {
CoreSession coreSession = event.getContext().getCoreSession();
Calendar expirationDate = Calendar.getInstance();
expirationDate.add(Calendar.DATE, 30);
Date now = expirationDate.getTime();
String date = new SimpleDateFormat("yyyy-MM-dd").format(now);
String query = String.format(QUERY_CONTRACTS, date);
DocumentModelList contracts = coreSession.query(query);

EventService eventService;
try {
eventService = Framework.getService(EventService.class);
for (DocumentModel contract : contracts) {
DocumentEventContext ctx = new DocumentEventContext(
coreSession, coreSession.getPrincipal(), contract);
Event contractExpiredEvent = ctx.newEvent("contractExpired");
eventService.fireEvent(event);
}
} catch (Exception e) {
throw new RuntimeException("could not get the EventService", e);
}
}

}

Now that an event is sent when a contract expires in 30 days, we need to warn the user. It’s a simple contribution to the notification extension point.

<?xml version="1.0"?>
<component
name="org.nuxeo.sample.contract.notifcontrib">

<extension
target="org.nuxeo.ecm.platform.ec.notification.service.NotificationService"
point="notifications">
<notification name="Contract Expired" channel="email" enabled="true" availableIn="Workspace"
autoSubscribed="false" template="contractExpired" subject="Contract expired" label="label.nuxeo.notifications.contractExpired">
<event name="contractExpired"/>
</notification>
</extension>

<extension
target="org.nuxeo.ecm.platform.ec.notification.service.NotificationService"
point="templates">
<template name="contractExpired" src="templates/contractExpired.ftl" />
</extension>

</component>

And that’s it, you’re now set up to send an email alert 30 days before a document expires.