Today I'm really happy to write about some code open sourced by the Indianapolis Museum of Art. You might already have heard of them from our previous case study. And if you want to find more about them and their use of the Nuxeo Platform, sign up for this webinar with their project lead, Charlie Moad. Maybe you have seen his name on already, or seen his email on Nuxeo's dev list. He's the one that has graciously open sourced their code on GitHub. Their code is well designed and well commented, so you might want to check it out. It's split into two repositories.

The first one, ima-nuxeo-template, contains their custom configuration template. If you want to know how to declare a MySQL datasource, or how to access users and groups from a LDAP and an SQL directory, you really should look at this.

Here's the detail of the MySQL datasource declaration:

<?xml version="1.0"?>
<component name="org.imamuseum.nuxeo.mercury.datasource">

We add a custom datasource, which is a MySQL database holding metadata about the IMA collecion

NOTE: The autoReconnect=true is important with MySQL!

<extension target="org.nuxeo.runtime.datasource" point="datasources">
<datasource driverClassName="com.mysql.jdbc.Driver"
maxActive="100" maxIdle="30" maxWait="10000" name="jdbc/mercury">
<property name="url">jdbc:mysql://${}:${mercury.db.port}/${}?autoReconnect=true</property>
<property name="username">${mercury.db.user}</property>
<property name="password">${mercury.db.password}</property>


And here's how you would declare it as a directory:

<?xml version="1.0"?>
<component name="org.imamuseum.nuxeo.custom.directories">


<extension target="" point="directories">

<!-- This custom directory points to a MySQL database holding collection metadata -->
<directory name="ima_art_metadata_mercury_directory">
<dataSource>jdbc/mercury</dataSource> <!-- declared in ima installation template -->



The second repository, ima-nuxeo-custom, contains some of the code they had to write for their customization work. I'm going to show you some of the most interesting things they did.

The first thing that I noticed was that they wrote a custom operation. By default, Nuxeo has many generic/atomic operations to cover developer needs, however we also leave the door open for people to develop their own. They needed to retrieve the working copy of a version or proxy, and there's no easy way to do this with our default set of operations. So they did what every Nuxeo developer should do in this situation - they wrote their own operation. It's really simple, as it requires only a Java class and a small XML contribution:

<?xml version="1.0"?>
<component name="org.imamuseum.nuxeo.custom.operation">

This file declares custom Nuxeo automation API operations

<extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent" point="operations">

<!-- Operation for accessing the source of a published document -->
<operation class="org.imamuseum.nuxeo.custom.operations.GetWorkingCopyDocumentOperation" />


package org.imamuseum.nuxeo.custom.operations;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.nuxeo.ecm.automation.core.Constants;
import org.nuxeo.ecm.automation.core.annotations.Operation;
import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
import org.nuxeo.ecm.automation.core.collectors.DocumentModelCollector;
import org.nuxeo.ecm.core.api.DocumentModel;

@Operation(id = GetWorkingCopyDocumentOperation.ID, category = Constants.CAT_DOCUMENT, label = "Get Working Copy", description = "Get the source if the document is a proxy")
public class GetWorkingCopyDocumentOperation {

public final static String ID = "Document.GetWorkingCopy";

public DocumentModel run(DocumentModel doc) throws Exception {
// Fetch the working copy (live document) for a proxy or version
return doc.getCoreSession().getWorkingCopy(doc.getRef());


As you can see, it's really straightforward :)

Another interesting feature they have is changing the default root of the documents exposed through WebDav. They only want the AssetLibrary, Workspaces, and UserWorkspaces as the top-level WebDav folders. So they had to write and declare a new SearchVirtualBackend:

package org.imamuseum.nuxeo.custom.backend;

import org.apache.commons.lang.StringUtils;
import org.nuxeo.ecm.platform.wi.backend.Backend;
import org.nuxeo.ecm.platform.wi.backend.SearchVirtualBackend;
import org.nuxeo.ecm.platform.wi.backend.SimpleRealBackendFactory;

public class ImaSearchRootBackend extends SearchVirtualBackend {

// For our installation we only want the AssetLibrary, Workspaces, and UserWorkspaces as the top-level WebDav folders

private static final String QUERY = "select * from Document where ecm:primaryType IN ('ImportRoot','WorkspaceRoot','UserWorkspacesRoot') "

        + &quot;AND ecm:currentLifeCycleState != 'deleted' AND ecm:isProxy = 0 order by ecm:path&quot;;

public ImaSearchRootBackend() {
    super(&quot;&quot;, &quot;&quot;, QUERY, new SimpleRealBackendFactory());

public Backend getBackend(String uri) {
    if (StringUtils.isEmpty(uri) || &quot;/&quot;.equals(uri)) {
        return this;
    } else {
        return super.getBackend(uri);


public boolean isRoot() {
    return true;


They have many other interesting examples in their code. I highly recommend that you look through the rest of it.

One last comment about the Monday and Friday blog posts -- this is going to change a bit. There's a lot going on at Nuxeo, and I may not post every week. However, please send topics you are especially interested in, and I may get to yours!

That's it for today. Hope you liked what you saw.