Some time ago I was asked by a customer how to log changes made to document metadata. If you are familiar with the Nuxeo Content Services Platform you probably know that it supports versioning and that it can create a new version of a document for every modification. It’s a perfectly valid solution to make sure no information is ever lost but it comes at a cost in terms of performance.
A more lightweight solution to track changes is to use the Audit service. By default it doesn’t keep track of which properties were changed but this can be improved very easily.
The first thing is to detect that a change was made to a document. Fortunately, the Nuxeo Platform does it for you by raising events every time something happens. Thus we just need to write a listener that catches the document modification event and write an entry in the Audit.
Here we’ll use the beforeDocumentModification event because when it’s raised the modification is not yet committed in the content repository and the Nuxeo Java API provides all the method we need to loop on the dirty properties. Below is a simplified example.
@Override
public void handleEvent(Event event) {
DocumentModel doc = ((DocumentEventContext)event.getContext()).getSourceDocument();
for (DataModel model : doc.getDataModels().values()) {
if (model.isDirty()) {
Collection<String> dirtyFieldNames = model.getDirtyFields();
for (String fieldName : dirtyFieldNames) {
//do something
}
}
}
}
As you can see, it’s very efficient because we don’t have to test every single property to look for changes. Next we need to add a new entry in the Audit for each modified property. Each will contain the field name, the previous value and the new value. Getting the previous value is easy, we just need to load the latest persisted version of the document from the content repository.
…
DocumentModel oldDoc = doc.getCoreSession().getDocument(doc.getRef());
…
Finally, we can create and record the audit entries.
AuditLogger logger = Framework.getLocalService(AuditLogger.class);
...
for (String fieldName : dirtyFieldNames) {
Object oldValue = oldDoc.getProperty(fieldName).getValue();
Object newValue = doc.getProperty(fieldName).getValue();
LogEntry entry = logger.newLogEntry();
entry.setEventId(EVENT_ID);
entry.setEventDate(new Date(event.getTime()));
entry.setDocUUID(doc.getRef());
Map<String, ExtendedInfo> extended = new HashMap<>();
extended.put(“fieldname”, logger.newExtendedInfo(fieldName));
extended.put(“oldValue”, logger.newExtendedInfo(oldValue));
extended.put(“newValue”, logger.newExtendedInfo(newValue));
entry.setExtendedInfos(extended);
entry.setComment(fieldName + " : " + oldValue + " -> " + newValue);
entryList.add(entry);
}
…
logger.addLogEntries(entryList);
That’s it. We now have a lot more details in the Audit for each document modification. It also enables us to run queries like “who modified property X in document A ?”.
You can find the complete source of this example in our Sandbox GitHub Repository