Write an Operation to Search through a Directory


Fri 19 July 2013 By Laurent Doguin

Last week Thibaud and Alain asked me if there was an operation available to search some users and retrieve their username only, and not the complete principal object or DocumentModel. And unfortunately there is not. You can get the entries of the user directory but the result is a JSON file. This does not work for them as they wanted to use this operation to find WorkFlow assignee. So, I started writing an operation for that.

One thing you have to know about users in Nuxeo is that they are stored in a directory. So, if I do an operation to search for users, I might as well do a more generic operation that searches through any directory. This is what we're going to do today.

To do this search, we can use the Session.getProjection method. The Session object is a directory session. It holds a connection to the database, allowing us to access, update, add or remove entries of the directory. The getProjection method is particularly interesting as it returns only one column of the table. This is exactly what Alain and Thibaud want, the username column. Once we have the result, we need to put it in the operation context to make it usable from any other operation of a chain. So my operation will have three required parameters: the directoryName, the columnName and the variableName.

As we don't want all the entries of a directory, we need to add a filter parameter. When you're doing a search in a directory, you can add a filter represented by a Map<String, Serializable> Java object. The key of this map will be the name of the column you are filtering, and the value will be your search parameter. You can filter any column you want. To represent key/value pairs in Studio, simply declare your param variable as Properties like this:

 @Param(name = "filters", required = false)
 protected Properties filterProperties;

The good thing about those filters is that you can specify whether they will be exact or fulltext filter (exact by default). So, we need another operation parameter to specify which field will use a fulltext match instead of the default exact match. This will be a list of String. To represent a list of String in Nuxeo Studio, simply declare your param variable as a StringList like this:
 @Param(name = "fulltextFields", required = false)
 protected StringList fulltextFields;

The operation will look like this once loaded in Nuxeo Studio: Directory Projection The rest is pretty much self explanatory. Here's the resulting operation:
/*
* (C) Copyright 2006-2013 Nuxeo SAS (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* ldoguin
*
*/
package org.nuxeo.ecm.automation.core.operations.services.directory;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.automation.OperationContext;
import org.nuxeo.ecm.automation.core.Constants;
import org.nuxeo.ecm.automation.core.annotations.Context;
import org.nuxeo.ecm.automation.core.annotations.Operation;
import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
import org.nuxeo.ecm.automation.core.annotations.Param;
import org.nuxeo.ecm.automation.core.util.Properties;
import org.nuxeo.ecm.automation.core.util.StringList;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.directory.Directory;
import org.nuxeo.ecm.directory.Session;
import org.nuxeo.ecm.directory.api.DirectoryService;

/**
* @author Laurent Doguin ([email protected])
* @since 5.7.2
*/
@Operation(id = DirectoryProjection.ID, category = Constants.CAT_SERVICES, label = "Get a Directory Projection", since = "5.7.2", description = "Executes a query using given filter and return only the column *<b>columnName</b>*. The result is assigned to the context variable *<b>variableName</b>*. The filters are specified as <i>key=value</i> pairs separated by a new line. The key used for a filter is the column name of the directory. To specify multi-line values you can use a \ character followed by a new line. <p>Example:<pre>firstName=John<br>lastName=doe</pre>By default, the search filters use exact match. You can do a fulltext search on some specific columns using the fulltextFields. it's specified as comma separated columnName, for instance : <p>Example:<pre>firstName,lastName</pre>")
public class DirectoryProjection {

public static final String ID = "Directory.Projection";

private static final Log log = LogFactory.getLog(DirectoryProjection.class);

@Context
protected OperationContext ctx;

@Context
protected DirectoryService directoryService;

@Param(name = "directoryName", required = true)
protected String directoryName;

@Param(name = "columnName", required = true)
protected String columnName;

@Param(name = "variableName", required = true)
protected String variableName;

@Param(name = "filters", required = false)
protected Properties filterProperties;

@Param(name = "fulltextFields", required = false)
protected StringList fulltextFields;

@OperationMethod
public void run() throws Exception {
// Open the Directory Session
Directory directory = directoryService.getDirectory(directoryName);
Session session = null;
try {
session = directory.getSession();
Map<String, Serializable> filter = new HashMap<String, Serializable>();
Set<String> fulltext = new HashSet<String>();
// Add filters if any
if (filterProperties != null) {
filter.putAll(filterProperties);
// Specify fulltext filter if any
if (fulltextFields != null) {
fulltext.addAll(fulltextFields);
}
}
// Do the actual search
List<String> uids = session.getProjection(filter, fulltext,
columnName);
// Put the result in the operation context
ctx.put(variableName, uids);
} finally {
try {
// Never forget to close the session you have open
if (session != null) {
session.close();
}
} catch (ClientException ce) {
log.error("Could not close directory session", ce);
}
}
}

}


Category: Product & Development
Tagged: How to, Java, Nuxeo Platform