Commit 87a9a68d authored by guus's avatar guus

Result Set Management applied to the user search plugin. This effectively...

Result Set Management applied to the user search plugin. This effectively enables users to page through a potentially large number of results.

User search wasn't compliant with XEP-0055 in the basic search (invalid search fields were used). I replaced them.

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/branches/rsm@9169 b35dd754-fafc-0310-a699-88a17e54d16e
parent bbefea67
...@@ -44,6 +44,12 @@ ...@@ -44,6 +44,12 @@
Search Plugin Changelog Search Plugin Changelog
</h1> </h1>
<p><b>1.5.0</b> -- September 19, 2007 (GdK)</p>
<ul>
<li>Now implements XEP-0059 "Result Set Management".</li>
<li>Basic search (not 'extended search') is now XEP-0055 compliant.</li>
</ul>
<p><b>1.4.1</b> -- June 20, 2007</p> <p><b>1.4.1</b> -- June 20, 2007</p>
<ul> <ul>
<li>Unescape username before returning search results to the client.</li> <li>Unescape username before returning search results to the client.</li>
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
<name>Search</name> <name>Search</name>
<description>Provides support for Jabber Search (XEP-0055)</description> <description>Provides support for Jabber Search (XEP-0055)</description>
<author>Ryan Graham</author> <author>Ryan Graham</author>
<version>1.4.1</version> <version>1.5.0</version>
<date>6/20/2007</date> <date>9/19/2007</date>
<minServerVersion>3.3.0</minServerVersion> <minServerVersion>3.3.0</minServerVersion>
<adminconsole> <adminconsole>
......
...@@ -7,18 +7,29 @@ ...@@ -7,18 +7,29 @@
package org.jivesoftware.openfire.plugin; package org.jivesoftware.openfire.plugin;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import org.dom4j.DocumentHelper; import org.dom4j.DocumentHelper;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.QName; import org.dom4j.QName;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.Plugin; import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.disco.IQDiscoInfoHandler;
import org.jivesoftware.openfire.disco.IQDiscoItemsHandler;
import org.jivesoftware.openfire.forms.DataForm; import org.jivesoftware.openfire.forms.DataForm;
import org.jivesoftware.openfire.forms.FormField; import org.jivesoftware.openfire.forms.FormField;
import org.jivesoftware.openfire.forms.spi.XDataFormImpl; import org.jivesoftware.openfire.forms.spi.XDataFormImpl;
...@@ -26,6 +37,14 @@ import org.jivesoftware.openfire.forms.spi.XFormFieldImpl; ...@@ -26,6 +37,14 @@ import org.jivesoftware.openfire.forms.spi.XFormFieldImpl;
import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.resultsetmanager.ResultSet;
import org.jivesoftware.util.resultsetmanager.ResultSetImpl;
import org.xmpp.component.Component; import org.xmpp.component.Component;
import org.xmpp.component.ComponentException; import org.xmpp.component.ComponentException;
import org.xmpp.component.ComponentManager; import org.xmpp.component.ComponentManager;
...@@ -34,380 +53,705 @@ import org.xmpp.packet.IQ; ...@@ -34,380 +53,705 @@ import org.xmpp.packet.IQ;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError; import org.xmpp.packet.PacketError;
import org.xmpp.packet.IQ.Type;
import org.xmpp.packet.PacketError.Condition;
import java.io.File; /**
import java.util.*; * Provides support for Jabber Search (<a
* href="http://www.xmpp.org/extensions/xep-0055.html">XEP-0055</a>).
/** * <p>
* Provides support for Jabber Search *
* (<a href="http://www.xmpp.org/extensions/xep-0055.html">XEP-0055</a>).<p> * The basic functionality is to query an information repository regarding the
* * possible search fields, to send a search query, and to receive search
* The basic functionality is to query an information repository * results. This implementation was primarily designed to use <a
* regarding the possible search fields, to send a search query, * href="http://www.xmpp.org/extensions/xep-0004.html">Data Forms</a>, but also
* and to receive search results. This implementation was primarily designed to use * supports non-dataform searches. <p/>
* <a href="http://www.xmpp.org/extensions/xep-0004.html">Data Forms</a>, but
* also supports non-dataform searches.
* <p/>
* *
* @author <a href="mailto:ryan@version2software.com">Ryan Graham</a> * @author <a href="mailto:ryan@version2software.com">Ryan Graham</a>
*/ */
public class SearchPlugin implements Component, Plugin, PropertyEventListener { public class SearchPlugin implements Component, Plugin, PropertyEventListener {
public static final String SERVICENAME = "plugin.search.serviceName";
public static final String SERVICEENABLED = "plugin.search.serviceEnabled"; public static final String NAMESPACE_JABBER_IQ_SEARCH = "jabber:iq:search";
public static final String EXCLUDEDFIELDS = "plugin.search.excludedFields";
public static final String SERVICENAME = "plugin.search.serviceName";
private UserManager userManager;
private ComponentManager componentManager; public static final String SERVICEENABLED = "plugin.search.serviceEnabled";
private PluginManager pluginManager;
public static final String EXCLUDEDFIELDS = "plugin.search.excludedFields";
private String serviceName;
private boolean serviceEnabled; private final UserManager userManager;
private Collection<String> exculudedFields;
private ComponentManager componentManager;
private static String serverName;
private PluginManager pluginManager;
private TreeMap<String, String> fieldLookup = new TreeMap<String, String>(new CaseInsensitiveComparator());
private Map<String, String> reverseFieldLookup = new HashMap<String, String>(); private String serviceName;
public SearchPlugin() { private boolean serviceEnabled;
serviceName = JiveGlobals.getProperty(SERVICENAME, "search");
serviceEnabled = JiveGlobals.getBooleanProperty(SERVICEENABLED, true); private Collection<String> exculudedFields;
exculudedFields = StringUtils.stringToCollection(JiveGlobals.getProperty(EXCLUDEDFIELDS, ""));
private static String serverName;
serverName = XMPPServer.getInstance().getServerInfo().getName();
userManager = UserManager.getInstance(); private final TreeMap<String, String> fieldLookup = new TreeMap<String, String>(
new CaseInsensitiveComparator());
// Some clients, such as Miranda, are hard-coded to search specific fields,
// so we map those fields to the fields that Openfire actually supports. private Map<String, String> reverseFieldLookup = new HashMap<String, String>();
fieldLookup.put("jid", "Username");
fieldLookup.put("username", "Username"); /**
fieldLookup.put("first", "Name"); * A list of field names that are valid in jabber:iq:search
fieldLookup.put("last", "Name"); */
fieldLookup.put("nick", "Name"); public final static Collection<String> validSearchRequestFields = new ArrayList<String>();
fieldLookup.put("name", "Name"); static {
fieldLookup.put("email", "Email"); validSearchRequestFields.add("first");
} validSearchRequestFields.add("last");
validSearchRequestFields.add("nick");
public String getName() { validSearchRequestFields.add("email");
return pluginManager.getName(this); validSearchRequestFields.add("x"); // extended info
}
// result set management (XEP-0059)
public String getDescription() { validSearchRequestFields.add("set");
return pluginManager.getDescription(this); }
}
public SearchPlugin() {
public void initializePlugin(PluginManager manager, File pluginDirectory) { serviceName = JiveGlobals.getProperty(SERVICENAME, "search");
pluginManager = manager; serviceEnabled = JiveGlobals.getBooleanProperty(SERVICEENABLED, true);
exculudedFields = StringUtils.stringToCollection(JiveGlobals
componentManager = ComponentManagerFactory.getComponentManager(); .getProperty(EXCLUDEDFIELDS, ""));
try {
componentManager.addComponent(serviceName, this); serverName = XMPPServer.getInstance().getServerInfo().getName();
} userManager = UserManager.getInstance();
catch (ComponentException e) {
componentManager.getLog().error(e); // Some clients, such as Miranda, are hard-coded to search specific
} // fields so we map those fields to the fields that Openfire actually
PropertyEventDispatcher.addListener(this); // supports.
} fieldLookup.put("jid", "Username");
fieldLookup.put("username", "Username");
public void initialize(JID jid, ComponentManager componentManager) { fieldLookup.put("first", "Name");
} fieldLookup.put("last", "Name");
fieldLookup.put("nick", "Name");
public void start() { fieldLookup.put("name", "Name");
} fieldLookup.put("email", "Email");
}
public void destroyPlugin() {
PropertyEventDispatcher.removeListener(this); /*
pluginManager = null; * (non-Javadoc)
try { *
componentManager.removeComponent(serviceName); * @see org.xmpp.component.Component#getName()
componentManager = null; */
} public String getName() {
catch (Exception e) { return pluginManager.getName(this);
componentManager.getLog().error(e); }
}
serviceName = null; /*
userManager = null; * (non-Javadoc)
exculudedFields = null; *
serverName = null; * @see org.xmpp.component.Component#getDescription()
fieldLookup = null; */
reverseFieldLookup = null; public String getDescription() {
} return pluginManager.getDescription(this);
}
public void shutdown() {
} /*
* (non-Javadoc)
public void processPacket(Packet p) { *
if (p instanceof IQ) { * @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager,
IQ packet = (IQ) p; * java.io.File)
*/
Element childElement = (packet).getChildElement(); public void initializePlugin(PluginManager manager, File pluginDirectory) {
String namespace = null; pluginManager = manager;
if (childElement != null) {
namespace = childElement.getNamespaceURI(); componentManager = ComponentManagerFactory.getComponentManager();
} try {
componentManager.addComponent(serviceName, this);
if ("jabber:iq:search".equals(namespace)) { } catch (ComponentException e) {
try { componentManager.getLog().error(e);
IQ replyPacket = handleIQ(packet); }
if (replyPacket != null) { PropertyEventDispatcher.addListener(this);
componentManager.sendPacket(this, replyPacket); }
}
} /*
catch (ComponentException e) { * (non-Javadoc)
componentManager.getLog().error(e); *
} * @see org.xmpp.component.Component#initialize(org.xmpp.packet.JID,
} * org.xmpp.component.ComponentManager)
else if ("http://jabber.org/protocol/disco#info".equals(namespace)) { */
try { public void initialize(JID jid, ComponentManager componentManager) {
IQ replyPacket = IQ.createResultIQ(packet); // intentionally left blank. See initializePlugin()
}
Element responseElement = replyPacket
.setChildElement("query", "http://jabber.org/protocol/disco#info"); /*
responseElement.addElement("identity").addAttribute("category", "directory") * (non-Javadoc)
.addAttribute("type", "user") *
.addAttribute("name", "User Search"); * @see org.xmpp.component.Component#start()
responseElement.addElement("feature").addAttribute("var", "jabber:iq:search"); */
public void start() {
componentManager.sendPacket(this, replyPacket); // intentionally left blank. See initializePlugin()
} }
catch (ComponentException e) {
componentManager.getLog().error(e); /*
} * (non-Javadoc)
} *
else if ("http://jabber.org/protocol/disco#items".equals(namespace)) { * @see org.jivesoftware.openfire.container.Plugin#destroyPlugin()
try { */
IQ replyPacket = IQ.createResultIQ(packet); public void destroyPlugin() {
replyPacket.setChildElement("query", "http://jabber.org/protocol/disco#items"); PropertyEventDispatcher.removeListener(this);
componentManager.sendPacket(this, replyPacket); pluginManager = null;
} try {
catch (ComponentException e) { componentManager.removeComponent(serviceName);
componentManager.getLog().error(e); componentManager = null;
} } catch (Exception e) {
} componentManager.getLog().error(e);
} }
} }
private IQ handleIQ(IQ packet) { /*
if (!serviceEnabled) { * (non-Javadoc)
return replyDisabled(packet); *
} * @see org.xmpp.component.Component#shutdown()
*/
if (IQ.Type.get.equals(packet.getType())) { public void shutdown() {
return processGetPacket(packet); // intentionally left blank. See destroyPlugin()
}
else if (IQ.Type.set.equals(packet.getType())) { }
return processSetPacket(packet);
} /*
else if (IQ.Type.result.equals(packet.getType()) || IQ.Type.error.equals(packet.getType())) { * (non-Javadoc)
// Ignore *
} * @see org.xmpp.component.Component#processPacket(org.xmpp.packet.Packet)
else { */
// Unknown type was sent so return an error public void processPacket(Packet p) {
IQ reply = new IQ(IQ.Type.error, packet.getID()); if (!(p instanceof IQ)) {
reply.setFrom(packet.getTo()); return;
reply.setTo(packet.getFrom()); }
reply.setError(PacketError.Condition.bad_request); final IQ packet = (IQ) p;
return reply;
} if (packet.getType().equals(IQ.Type.error)
|| packet.getType().equals(IQ.Type.result)) {
return null; return;
} }
private IQ replyDisabled(IQ packet) { // Packet p is an IQ stanza of type GET or SET. Therefor, it _must_ be
IQ replyPacket = IQ.createResultIQ(packet); // replied to.
Element reply = replyPacket.setChildElement("query", "jabber:iq:search"); final IQ replyPacket = handleIQRequest(packet);
XDataFormImpl unavailableForm = new XDataFormImpl(DataForm.TYPE_CANCEL);
unavailableForm.setTitle(LocaleUtils.getLocalizedString("advance.user.search.title", "search")); try {
unavailableForm.addInstruction(LocaleUtils.getLocalizedString("search.service_unavailable", "search")); componentManager.sendPacket(this, replyPacket);
reply.add(unavailableForm.asXMLElement()); } catch (ComponentException e) {
componentManager.getLog().error(e);
return replyPacket; }
}
}
private IQ processGetPacket(IQ packet) {
IQ replyPacket = IQ.createResultIQ(packet); /**
* Handles IQ requests. This method throws an IllegalArgumentException if an
Element queryResult = DocumentHelper.createElement(QName.get("query", "jabber:iq:search")); * IQ stanza is supplied that is not a request (if the stanza is not of type
* 'get' or 'set'). This method will either throw an Exception, or return a
String instructions = LocaleUtils.getLocalizedString("advance.user.search.details", "search"); * non-null IQ stanza of type 'error' or 'result', as XMPP Core specifies
* that <strong>all</strong> IQ request stanza's (type 'get' or 'set') MUST
// non-data form * be replied to.
queryResult.addElement("instructions").addText(instructions); *
* @param iq
XDataFormImpl searchForm = new XDataFormImpl(DataForm.TYPE_FORM); * The IQ stanza that forms the request.
searchForm.setTitle(LocaleUtils.getLocalizedString("advance.user.search.title", "search")); * @return The response to the request.
searchForm.addInstruction(instructions); */
private IQ handleIQRequest(IQ iq) {
XFormFieldImpl field = new XFormFieldImpl("FORM_TYPE"); final IQ replyPacket; // 'final' to ensure that it is set.
field.setType(FormField.TYPE_HIDDEN);
field.addValue("jabber:iq:search"); if (iq == null) {
searchForm.addField(field); throw new IllegalArgumentException("Argument 'iq' cannot be null.");
}
field = new XFormFieldImpl("search");
field.setType(FormField.TYPE_TEXT_SINGLE); final IQ.Type type = iq.getType();
field.setLabel(LocaleUtils.getLocalizedString("advance.user.search.search", "search")); if (type != IQ.Type.get && type != IQ.Type.set) {
field.setRequired(true); throw new IllegalArgumentException(
searchForm.addField(field); "Argument 'iq' must be of type 'get' or 'set'");
}
for (String searchField : getFilteredSearchFields()) {
// non-data form final Element childElement = iq.getChildElement();
queryResult.addElement(searchField); if (childElement == null) {
replyPacket = IQ.createResultIQ(iq);
field = new XFormFieldImpl(searchField); replyPacket
field.setType(FormField.TYPE_BOOLEAN); .setError(new PacketError(
field.addValue("1"); Condition.bad_request,
field.setLabel(LocaleUtils.getLocalizedString("advance.user.search." + searchField.toLowerCase(), "search")); org.xmpp.packet.PacketError.Type.modify,
field.setRequired(false); "IQ stanzas of type 'get' and 'set' MUST contain one and only one child element (RFC 3920 section 9.2.3)."));
searchForm.addField(field); return replyPacket;
} }
queryResult.add(searchForm.asXMLElement()); final String namespace = childElement.getNamespaceURI();
replyPacket.setChildElement(queryResult); if (namespace == null) {
replyPacket = IQ.createResultIQ(iq);
return replyPacket; replyPacket.setError(Condition.feature_not_implemented);
} return replyPacket;
}
private IQ processSetPacket(IQ packet) {
Set<User> users = new HashSet<User>(); if (namespace.equals(NAMESPACE_JABBER_IQ_SEARCH)) {
replyPacket = handleSearchRequest(iq);
Element incomingForm = packet.getChildElement(); } else if (namespace.equals(IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)) {
boolean isDataFormQuery = (incomingForm.element(QName.get("x", "jabber:x:data")) != null); replyPacket = handleDiscoInfo(iq);
} else if (namespace.equals(IQDiscoItemsHandler.NAMESPACE_DISCO_ITEMS)) {
Hashtable<String, String> searchList = extractSearchQuery(incomingForm); replyPacket = IQ.createResultIQ(iq);
Enumeration<String> searchIter = searchList.keys(); replyPacket.setChildElement("query",
while (searchIter.hasMoreElements()) { IQDiscoItemsHandler.NAMESPACE_DISCO_ITEMS);
String field = searchIter.nextElement(); } else {
String query = searchList.get(field); // don't known what to do with this.
replyPacket = IQ.createResultIQ(iq);
Collection<User> foundUsers = new ArrayList<User>(); replyPacket.setError(Condition.feature_not_implemented);
if (userManager != null) { }
if (query.length() > 0 && !query.equals("jabber:iq:search")) {
foundUsers.addAll(userManager.findUsers(new HashSet<String>( return replyPacket;
Arrays.asList((field))), query)); }
}
} /**
else { * Creates a response specific to the search plugin to Disco#Info requests.
foundUsers.addAll(findUsers(field, query)); *
} * @param iq
* The IQ stanza that contains the request.
//occasionally null a User is returned so filter them out * @return An IQ stanza, formulated as an answer to the received request.
for (User user : foundUsers) { */
if (user != null) { private static IQ handleDiscoInfo(IQ iq) {
users.add(user); if (iq == null) {
} throw new IllegalArgumentException("Argument 'iq' cannot be null.");
} }
}
if (!iq.getChildElement().getNamespaceURI().equals(
if (isDataFormQuery) { IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)
return replyDataFormResult(users, packet); || iq.getType() != Type.get) {
} throw new IllegalArgumentException(
else { "This is not a valid disco#info request.");
return replyNonDataFormResult(users, packet); }
}
} final IQ replyPacket = IQ.createResultIQ(iq);
private Hashtable<String, String> extractSearchQuery(Element incomingForm) { final Element responseElement = replyPacket.setChildElement("query",
Hashtable<String, String> searchList = new Hashtable<String, String>(); IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
Element form = incomingForm.element(QName.get("x", "jabber:x:data")); responseElement.addElement("identity").addAttribute("category",
if (form == null) { "directory").addAttribute("type", "user").addAttribute("name",
//since not all clients request which fields are available for searching "User Search");
//attempt to match submitted fields with available search fields responseElement.addElement("feature").addAttribute("var",
Iterator iter = incomingForm.elementIterator(); NAMESPACE_JABBER_IQ_SEARCH);
while (iter.hasNext()) { responseElement.addElement("feature").addAttribute("var",
Element element = (Element) iter.next(); IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
String name = element.getName(); responseElement.addElement("feature").addAttribute("var",
ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT);
if (fieldLookup.containsKey(name)) {
//make best effort to map the fields submitted by return replyPacket;
//the client to those that Openfire can search }
reverseFieldLookup.put(fieldLookup.get(name), name);
searchList.put(fieldLookup.get(name), element.getText()); private IQ handleSearchRequest(IQ packet) {
} if (!serviceEnabled) {
} return replyDisabled(packet);
} }
else {
List<String> searchFields = new ArrayList<String>(); switch (packet.getType()) {
String search = ""; case get:
return processGetPacket(packet);
Iterator fields = form.elementIterator("field");
while (fields.hasNext()) { case set:
Element searchField = (Element) fields.next(); return processSetPacket(packet);
String field = searchField.attributeValue("var"); default:
String value = ""; // we can safely ignore 'error' and 'result' typed iq stanzas.
if (searchField.element("value") != null) { return null;
value = searchField.element("value").getTextTrim(); }
} }
if (field.equals("search")) {
search = value; /**
} * Constructs a IQ result stanza, based on the request stanza that is
else if (value.equals("1")) { * provided as an argument. The stanza tells the recipient that this service
searchFields.add(field); * is currently unavailable.
} *
} * @param packet
* The request IQ stanza to which a result will be returned.
for (String field : searchFields) { * @return A result stanza, telling the user that this service is
searchList.put(field, search); * unavailable.
} */
} private static IQ replyDisabled(IQ packet) {
IQ replyPacket = IQ.createResultIQ(packet);
return searchList; Element reply = replyPacket.setChildElement("query",
} NAMESPACE_JABBER_IQ_SEARCH);
XDataFormImpl unavailableForm = new XDataFormImpl(DataForm.TYPE_CANCEL);
unavailableForm.setTitle(LocaleUtils.getLocalizedString(
"advance.user.search.title", "search"));
unavailableForm.addInstruction(LocaleUtils.getLocalizedString(
"search.service_unavailable", "search"));
reply.add(unavailableForm.asXMLElement());
return replyPacket;
}
/**
* Processes an IQ stanza of type 'get', which in the context of 'Jabber
* Search' is a request for available search fields.
*
* @param packet
* An IQ stanza of type 'get'
* @return A result IQ stanza that contains the possbile search fields.
*/
private IQ processGetPacket(IQ packet) {
if (!packet.getType().equals(IQ.Type.get)) {
throw new IllegalArgumentException(
"This method only accepts 'get' typed IQ stanzas as an argument.");
}
IQ replyPacket = IQ.createResultIQ(packet);
Element queryResult = DocumentHelper.createElement(QName.get("query",
NAMESPACE_JABBER_IQ_SEARCH));
String instructions = LocaleUtils.getLocalizedString(
"advance.user.search.details", "search");
// non-data form
queryResult.addElement("instructions").addText(instructions);
queryResult.addElement("first");
queryResult.addElement("last");
queryResult.addElement("nick");
queryResult.addElement("email");
XDataFormImpl searchForm = new XDataFormImpl(DataForm.TYPE_FORM);
searchForm.setTitle(LocaleUtils.getLocalizedString(
"advance.user.search.title", "search"));
searchForm.addInstruction(instructions);
XFormFieldImpl field = new XFormFieldImpl("FORM_TYPE");
field.setType(FormField.TYPE_HIDDEN);
field.addValue(NAMESPACE_JABBER_IQ_SEARCH);
searchForm.addField(field);
field = new XFormFieldImpl("search");
field.setType(FormField.TYPE_TEXT_SINGLE);
field.setLabel(LocaleUtils.getLocalizedString(
"advance.user.search.search", "search"));
field.setRequired(true);
searchForm.addField(field);
for (String searchField : getFilteredSearchFields()) {
field = new XFormFieldImpl(searchField);
field.setType(FormField.TYPE_BOOLEAN);
field.addValue("1");
field.setLabel(LocaleUtils.getLocalizedString(
"advance.user.search." + searchField.toLowerCase(), "search"));
field.setRequired(false);
searchForm.addField(field);
}
queryResult.add(searchForm.asXMLElement());
replyPacket.setChildElement(queryResult);
return replyPacket;
}
/**
* Processes an IQ stanza of type 'set', which in the context of 'Jabber
* Search' is a search request.
*
* @param packet
* An IQ stanza of type 'get'
* @return A result IQ stanza that contains the possbile search fields.
*/
private IQ processSetPacket(IQ packet) {
if (!packet.getType().equals(IQ.Type.set)) {
throw new IllegalArgumentException(
"This method only accepts 'set' typed IQ stanzas as an argument.");
}
final IQ resultIQ;
// check if the request complies to the XEP-0055 standards
if (!isValidSearchRequest(packet)) {
resultIQ = IQ.createResultIQ(packet);
resultIQ.setError(Condition.bad_request);
return resultIQ;
}
final Element incomingForm = packet.getChildElement();
final boolean isDataFormQuery = (incomingForm.element(QName.get("x",
"jabber:x:data")) != null);
final Set<User> searchResults = performSearch(incomingForm);
final Element rsmElement = incomingForm.element(QName.get("set",
ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT));
final boolean applyRSM = rsmElement != null && !searchResults.isEmpty();
if (applyRSM) {
// apply RSM
final List<User> rsmResults;
final ResultSet<User> rs = new ResultSetImpl<User>(searchResults);
try {
rsmResults = rs.applyRSMDirectives(rsmElement);
} catch (NullPointerException e) {
final IQ itemNotFound = IQ.createResultIQ(packet);
itemNotFound.setError(Condition.item_not_found);
return itemNotFound;
}
if (isDataFormQuery) {
resultIQ = replyDataFormResult(rsmResults, packet);
} else {
resultIQ = replyNonDataFormResult(rsmResults, packet);
}
// add the additional 'set' element.
final Element set = rs.generateSetElementFromResults(rsmResults);
resultIQ.getChildElement().add(set);
} else {
// don't apply RSM
if (isDataFormQuery) {
resultIQ = replyDataFormResult(searchResults, packet);
} else {
resultIQ = replyNonDataFormResult(searchResults, packet);
}
}
return resultIQ;
}
/**
* This method checks if the search request that was received is a valid
* JABBER:IQ:SEARCH request. In other words, it checks if the search request
* is spec compliant (XEP-0055). It does this by checking:
* <ul>
* <li>if the IQ stanza is of type 'set';</li>
* <li>if a child element identified by the jabber:iq:search namespace is
* supplied;</li>
* <li>if the stanza child element is has valid children itself.</li>
* </ul>
*
* @param iq
* The IQ object that should include a jabber:iq:search request.
* @return ''true'' if the supplied IQ stanza is a spec compliant search
* request, ''false'' otherwise.
*/
public static boolean isValidSearchRequest(IQ iq) {
if (iq == null) {
throw new IllegalArgumentException("Argument 'iq' cannot be null.");
}
if (iq.getType() != IQ.Type.set) {
return false;
}
final Element childElement = iq.getChildElement();
if (childElement == null) {
return false;
}
if (!childElement.getNamespaceURI().equals(NAMESPACE_JABBER_IQ_SEARCH)) {
return false;
}
if (!childElement.getName().equals("query")) {
return false;
}
final List<Element> fields = childElement.elements();
if (fields.size() == 0) {
return false;
}
for (Element element : fields) {
final String name = element.getName();
if (!validSearchRequestFields.contains(name)) {
return false;
}
// TODO: check dataform validity.
// if (name.equals("x") && !isValidDataForm(element))
// {
// return false;
// }
if (name.equals("set") && !ResultSet.isValidRSMRequest(element)) {
return false;
}
}
return true;
}
/**
* Performs a search based on form data, and returns the search results.
*
* @param incomingForm
* The form containing the search data
* @return A set of users that matches the search criteria.
*/
private Set<User> performSearch(Element incomingForm) {
Set<User> users = new HashSet<User>();
Hashtable<String, String> searchList = extractSearchQuery(incomingForm);
for (Entry<String, String> entry : searchList.entrySet()) {
String field = entry.getKey();
String query = entry.getValue();
Collection<User> foundUsers = new ArrayList<User>();
if (userManager != null) {
if (query.length() > 0
&& !query.equals(NAMESPACE_JABBER_IQ_SEARCH)) {
foundUsers
.addAll(userManager.findUsers(new HashSet<String>(
Arrays.asList((field))), query));
}
} else {
foundUsers.addAll(findUsers(field, query));
}
// occasionally a null User is returned so filter them out
for (User user : foundUsers) {
if (user != null) {
users.add(user);
}
}
}
return users;
}
/**
* This utilty method extracts the search query from the request. A query is
* defined as a set of key->value pairs, where the key denotes a search
* field, and the value contains the value that was filled out by the user
* for that field.
*
* The query can be specified in one of two ways. The first way is a query
* is formed is by filling out any of the the standard search fields. The
* other search method makes use of extended data forms. Search queries that
* are supplied to this {@link #extractSearchQuery(Element)} that make use
* of this last method get forwarded to
* {@link #extractExtendedSearchQuery(Element)}.
*
* @param incomingForm
* The form from which to extract the query
* @return The search query for a particular user search request.
*/
private Hashtable<String, String> extractSearchQuery(Element incomingForm) {
if (incomingForm.element(QName.get("x", "jabber:x:data")) != null) {
// forward the request.
return extractExtendedSearchQuery(incomingForm);
}
final Hashtable<String, String> searchList = new Hashtable<String, String>();
// since not all clients request which fields are available for
// searching attempt to match submitted fields with available search
// fields
Iterator<Element> iter = incomingForm.elementIterator();
while (iter.hasNext()) {
Element element = iter.next();
String name = element.getName();
if (fieldLookup.containsKey(name)) {
// make best effort to map the fields submitted by
// the client to those that Openfire can search
reverseFieldLookup.put(fieldLookup.get(name), name);
searchList.put(fieldLookup.get(name), element.getText());
}
}
return searchList;
}
/**
* Extracts a search query from a data form that makes use of data forms to
* specify the search request. This 'extended' way of constructing a search
* request is documented in XEP-0055, chapter 3.
*
* @param incomingForm
* The form from which to extract the query
* @return The search query for a particular user search request.
* @see #extractSearchQuery(Element)
*/
private Hashtable<String, String> extractExtendedSearchQuery(
Element incomingForm) {
final Element dataform = incomingForm.element(QName.get("x",
"jabber:x:data"));
Hashtable<String, String> searchList = new Hashtable<String, String>();
List<String> searchFields = new ArrayList<String>();
String search = "";
Iterator<Element> fields = dataform.elementIterator("field");
while (fields.hasNext()) {
Element searchField = fields.next();
String field = searchField.attributeValue("var");
String value = "";
if (searchField.element("value") != null) {
value = searchField.element("value").getTextTrim();
}
if (field.equals("search")) {
search = value;
} else if (value.equals("1")) {
searchFields.add(field);
}
}
for (String field : searchFields) {
searchList.put(field, search);
}
return searchList;
}
/** /**
* Constructs a XForm that is returned as an IQ packet that contains the search results. * Constructs a query that is returned as an IQ packet that contains the search results.
* *
* @param users set of users that will be used to construct the search results * @param users set of users that will be used to construct the search results
* @param packet the IQ packet sent by the client * @param packet the IQ packet sent by the client
* @return the iq packet that contains the search results * @return the iq packet that contains the search results
*/ */
private IQ replyDataFormResult(Set<User> users, IQ packet) { private IQ replyDataFormResult(Collection<User> users, IQ packet) {
XDataFormImpl searchResults = new XDataFormImpl(DataForm.TYPE_RESULT); XDataFormImpl searchResults = new XDataFormImpl(DataForm.TYPE_RESULT);
XFormFieldImpl field = new XFormFieldImpl("FORM_TYPE"); XFormFieldImpl field = new XFormFieldImpl("FORM_TYPE");
field.setType(FormField.TYPE_HIDDEN); field.setType(FormField.TYPE_HIDDEN);
searchResults.addField(field); searchResults.addField(field);
field = new XFormFieldImpl("jid"); field = new XFormFieldImpl("jid");
field.setLabel("JID"); field.setLabel("JID");
searchResults.addReportedField(field); searchResults.addReportedField(field);
for (String fieldName : getFilteredSearchFields()) { for (String fieldName : getFilteredSearchFields()) {
field = new XFormFieldImpl(fieldName); field = new XFormFieldImpl(fieldName);
field.setLabel(LocaleUtils.getLocalizedString("advance.user.search." + fieldName.toLowerCase(), "search")); field.setLabel(LocaleUtils.getLocalizedString(
searchResults.addReportedField(field); "advance.user.search." + fieldName.toLowerCase(), "search"));
} searchResults.addReportedField(field);
}
for (User user : users) {
String username = JID.unescapeNode(user.getUsername()); for (User user : users) {
String username = JID.unescapeNode(user.getUsername());
ArrayList<XFormFieldImpl> items = new ArrayList<XFormFieldImpl>();
ArrayList<XFormFieldImpl> items = new ArrayList<XFormFieldImpl>();
XFormFieldImpl fieldJID = new XFormFieldImpl("jid");
fieldJID.addValue(username + "@" + serverName); XFormFieldImpl fieldJID = new XFormFieldImpl("jid");
items.add(fieldJID); fieldJID.addValue(username + "@" + serverName);
items.add(fieldJID);
XFormFieldImpl fieldUsername = new XFormFieldImpl(LocaleUtils.getLocalizedString("advance.user.search.username", "search"));
fieldUsername.addValue(username); XFormFieldImpl fieldUsername = new XFormFieldImpl(LocaleUtils.getLocalizedString("advance.user.search.username", "search"));
items.add(fieldUsername); fieldUsername.addValue(username);
items.add(fieldUsername);
XFormFieldImpl fieldName = new XFormFieldImpl(LocaleUtils.getLocalizedString("advance.user.search.name", "search"));
fieldName.addValue(removeNull(user.getName())); XFormFieldImpl fieldName = new XFormFieldImpl(LocaleUtils.getLocalizedString("advance.user.search.name", "search"));
items.add(fieldName); fieldName.addValue(removeNull(user.getName()));
items.add(fieldName);
XFormFieldImpl fieldEmail = new XFormFieldImpl(LocaleUtils.getLocalizedString("advance.user.search.email", "search"));
fieldEmail.addValue(removeNull(user.getEmail())); XFormFieldImpl fieldEmail = new XFormFieldImpl(LocaleUtils.getLocalizedString("advance.user.search.email", "search"));
items.add(fieldEmail); fieldEmail.addValue(removeNull(user.getEmail()));
items.add(fieldEmail);
searchResults.addItemFields(items);
} searchResults.addItemFields(items);
}
IQ replyPacket = IQ.createResultIQ(packet);
Element reply = replyPacket.setChildElement("query", "jabber:iq:search"); IQ replyPacket = IQ.createResultIQ(packet);
reply.add(searchResults.asXMLElement()); Element reply = replyPacket.setChildElement("query",
NAMESPACE_JABBER_IQ_SEARCH);
return replyPacket; reply.add(searchResults.asXMLElement());
}
return replyPacket;
}
/** /**
* Constructs a query that is returned as an IQ packet that contains the search results. * Constructs a query that is returned as an IQ packet that contains the search results.
...@@ -416,48 +760,50 @@ public class SearchPlugin implements Component, Plugin, PropertyEventListener { ...@@ -416,48 +760,50 @@ public class SearchPlugin implements Component, Plugin, PropertyEventListener {
* @param packet the IQ packet sent by the client * @param packet the IQ packet sent by the client
* @return the iq packet that contains the search results * @return the iq packet that contains the search results
*/ */
private IQ replyNonDataFormResult(Set<User> users, IQ packet) { private IQ replyNonDataFormResult(Collection<User> users, IQ packet) {
IQ replyPacket = IQ.createResultIQ(packet); IQ replyPacket = IQ.createResultIQ(packet);
Element replyQuery = replyPacket.setChildElement("query", "jabber:iq:search"); Element replyQuery = replyPacket.setChildElement("query",
NAMESPACE_JABBER_IQ_SEARCH);
for (User user : users) {
Element item = replyQuery.addElement("item"); for (User user : users) {
Element item = replyQuery.addElement("item");
String username = JID.unescapeNode(user.getUsername()); String username = JID.unescapeNode(user.getUsername());
item.addAttribute("jid", username + "@" + serverName);
item.addAttribute("jid", username + "@" + serverName);
// return to the client the same fields that were submitted
//return to the client the same fields that were submitted for (String field : reverseFieldLookup.keySet()) {
for (String field : reverseFieldLookup.keySet()) { if ("Username".equals(field)) {
if ("Username".equals(field)) { Element element = item.addElement(reverseFieldLookup
Element element = item.addElement(reverseFieldLookup.get(field)); .get(field));
element.addText(username); element.addText(username);
} }
if ("Name".equals(field)) { if ("Name".equals(field)) {
Element element = item.addElement(reverseFieldLookup.get(field)); Element element = item.addElement(reverseFieldLookup
element.addText(removeNull(user.getName())); .get(field));
} element.addText(removeNull(user.getName()));
}
if ("Email".equals(field)) {
Element element = item.addElement(reverseFieldLookup.get(field)); if ("Email".equals(field)) {
element.addText(removeNull(user.getEmail())); Element element = item.addElement(reverseFieldLookup
} .get(field));
} element.addText(removeNull(user.getEmail()));
} }
}
return replyPacket; }
}
return replyPacket;
}
/** /**
* Returns the service name of this component, which is "search" by default. * Returns the service name of this component, which is "search" by default.
* *
* @return the service name of this component. * @return the service name of this component.
*/ */
public String getServiceName() { public String getServiceName() {
return serviceName; return serviceName;
} }
/** /**
* Sets the service name of this component, which is "search" by default. If the name * Sets the service name of this component, which is "search" by default. If the name
* is different than the existing name the plugin will remove itself from the ComponentManager * is different than the existing name the plugin will remove itself from the ComponentManager
...@@ -465,213 +811,251 @@ public class SearchPlugin implements Component, Plugin, PropertyEventListener { ...@@ -465,213 +811,251 @@ public class SearchPlugin implements Component, Plugin, PropertyEventListener {
* *
* @param name the service name of this component. * @param name the service name of this component.
*/ */
public void setServiceName(String name) { public void setServiceName(String name) {
changeServiceName(name); changeServiceName(name);
JiveGlobals.setProperty(SERVICENAME, name); JiveGlobals.setProperty(SERVICENAME, name);
} }
/**
* @return true if search service is enabled.
*/
public boolean getServiceEnabled() {
return serviceEnabled;
}
/**
* Enables or disables the search service. When disabled, when a client tries
* to do a search they will receive an XForm informing that the service is
* unavailable.
*
* @param enabled true if group permission checking should be disabled.
*/
public void setServiceEnabled(boolean enabled) {
serviceEnabled = enabled;
JiveGlobals.setProperty(SERVICEENABLED, enabled ? "true" : "false");
}
/** /**
* Returns the collection of searchable field names that does not include the fields * Checks if the search service is enabled.
* listed in the EXCLUDEDFIELDS property list. *
*/ * @return true if search service is enabled.
public Collection<String> getFilteredSearchFields() { */
Collection<String> searchFields; public boolean getServiceEnabled() {
return serviceEnabled;
// See if the installed provider supports searching. If not, workaround }
// by providing our own searching.
try { /**
searchFields = new ArrayList<String>(userManager.getSearchFields()); * Enables or disables the search service. When disabled, when a client
} * tries to do a search they will receive an XForm informing that the
catch (UnsupportedOperationException uoe) { * service is unavailable.
// Use a SearchPluginUserManager instead. *
searchFields = getSearchPluginUserManagerSearchFields(); * @param enabled
} * true if group permission checking should be disabled.
*/
searchFields.removeAll(exculudedFields); public void setServiceEnabled(boolean enabled) {
serviceEnabled = enabled;
return searchFields; JiveGlobals.setProperty(SERVICEENABLED, enabled ? "true" : "false");
} }
/**
* Returns the collection of searchable field names that does not include
* the fields listed in the EXCLUDEDFIELDS property list.
*
* @return A collection of field names that can be used in a search request.
*/
public Collection<String> getFilteredSearchFields() {
Collection<String> searchFields;
// See if the installed provider supports searching. If not, workaround
// by providing our own searching.
try {
searchFields = new ArrayList<String>(userManager.getSearchFields());
} catch (UnsupportedOperationException uoe) {
// Use a SearchPluginUserManager instead.
searchFields = getSearchPluginUserManagerSearchFields();
}
searchFields.removeAll(exculudedFields);
return searchFields;
}
/** /**
* Restricts which fields can be searched on and shown to clients. This can be used * Restricts which fields can be searched on and shown to clients. This can be used
* in the case of preventing users email addresses from being revealed as part of * in the case of preventing users email addresses from being revealed as part of
* the search results. * the search results.
* *
* @param set of fields that can not be searched on or shown to the client * @param set of fields that can not be searched on or shown to the client
*/ */
public void setExcludedFields(Collection<String> exculudedFields) { public void setExcludedFields(Collection<String> exculudedFields) {
this.exculudedFields = exculudedFields; this.exculudedFields = exculudedFields;
JiveGlobals.setProperty(EXCLUDEDFIELDS, StringUtils.collectionToString(exculudedFields)); JiveGlobals.setProperty(EXCLUDEDFIELDS, StringUtils
} .collectionToString(exculudedFields));
}
public void propertySet(String property, Map params) {
if (property.equals(SERVICEENABLED)) {
this.serviceEnabled = Boolean.parseBoolean((String)params.get("value"));
}
else if (property.equals(SERVICENAME)) {
changeServiceName((String)params.get("value"));
}
else if (property.equals(EXCLUDEDFIELDS)) {
exculudedFields = StringUtils.stringToCollection(JiveGlobals.getProperty(EXCLUDEDFIELDS, (String)params.get("value")));
}
}
public void propertyDeleted(String property, Map params) {
if (property.equals(SERVICEENABLED)) {
this.serviceEnabled = true;
}
else if (property.equals(SERVICENAME)) {
changeServiceName("search");
}
else if (property.equals(EXCLUDEDFIELDS)) {
exculudedFields = new ArrayList<String>();
}
}
public void xmlPropertySet(String property, Map params) {
// not used
}
public void xmlPropertyDeleted(String property, Map params) {
// not used
}
private void changeServiceName(String serviceName) {
if (serviceName == null) {
throw new NullPointerException("Service name cannot be null");
}
if (this.serviceName.equals(serviceName)) {
return;
}
// Re-register the service.
try {
componentManager.removeComponent(this.serviceName);
}
catch (Exception e) {
componentManager.getLog().error(e);
}
try {
componentManager.addComponent(serviceName, this);
}
catch (Exception e) {
componentManager.getLog().error(e);
}
this.serviceName = serviceName;
}
private class CaseInsensitiveComparator implements Comparator<String> {
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
}
private String removeNull(String s) {
if (s == null) {
return "";
}
return s.trim();
}
/** /*
* Returns the collection of field names that can be used to search for a * (non-Javadoc)
* user. Typical fields are username, name, and email. These values can be *
* used to contruct a data form. * @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String,
*/ * java.util.Map)
public Collection<String> getSearchPluginUserManagerSearchFields() { */
return Arrays.asList("Username", "Name", "Email"); public void propertySet(String property, Map<String, Object> params) {
} if (property.equals(SERVICEENABLED)) {
this.serviceEnabled = Boolean.parseBoolean((String) params
.get("value"));
} else if (property.equals(SERVICENAME)) {
changeServiceName((String) params.get("value"));
} else if (property.equals(EXCLUDEDFIELDS)) {
exculudedFields = StringUtils.stringToCollection(JiveGlobals
.getProperty(EXCLUDEDFIELDS, (String) params.get("value")));
}
}
/** /*
* Finds a user using the specified field and query string. For example, a * (non-Javadoc)
* field name of "email" and query of "jsmith@example.com" would search for *
* the user with that email address. Wildcard (*) characters are allowed as * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String,
* part of queries. * java.util.Map)
* */
* A possible future improvement would be to have a third parameter that public void propertyDeleted(String property, Map<String, Object> params) {
* sets the maximum number of users returned and/or the number of users if (property.equals(SERVICEENABLED)) {
* that are searched. this.serviceEnabled = true;
*/ } else if (property.equals(SERVICENAME)) {
public Collection<User> findUsers(String field, String query) { changeServiceName("search");
List<User> foundUsers = new ArrayList<User>(); } else if (property.equals(EXCLUDEDFIELDS)) {
exculudedFields = new ArrayList<String>();
if (!getSearchPluginUserManagerSearchFields().contains(field)) { }
return foundUsers; }
}
/*
int index = query.indexOf("*"); * (non-Javadoc)
if (index == -1) { *
Collection<User> users = userManager.getUsers(); * @see org.jivesoftware.util.PropertyEventListener#xmlPropertySet(java.lang.String,
for (User user : users) { * java.util.Map)
if (field.equals("Username")) { */
try { public void xmlPropertySet(String property, Map<String, Object> params) {
foundUsers.add(userManager.getUser(query)); // not used
return foundUsers; }
}
catch (UserNotFoundException e) { /*
Log.error("Error getting user", e); * (non-Javadoc)
} *
} * @see org.jivesoftware.util.PropertyEventListener#xmlPropertyDeleted(java.lang.String,
else if (field.equals("Name")) { * java.util.Map)
if (query.equalsIgnoreCase(user.getName())) { */
foundUsers.add(user); public void xmlPropertyDeleted(String property, Map<String, Object> params) {
} // not used
} }
else if (field.equals("Email")) {
if (user.getEmail() != null) { /**
if (query.equalsIgnoreCase(user.getEmail())) { * Changes the name of this service. Note that this method will re-register
foundUsers.add(user); * this component if the new name differs from the old name.
} *
} * @param serviceName
} * The new service name.
} */
} private void changeServiceName(String serviceName) {
else { if (serviceName == null) {
String prefix = query.substring(0, index); throw new NullPointerException("Service name cannot be null");
Collection<User> users = userManager.getUsers(); }
for (User user : users) {
String userInfo = ""; if (this.serviceName.equals(serviceName)) {
if (field.equals("Username")) { return;
userInfo = user.getUsername(); }
}
else if (field.equals("Name")) { // Re-register the service.
userInfo = user.getName(); try {
} componentManager.removeComponent(this.serviceName);
else if (field.equals("Email")) { } catch (Exception e) {
userInfo = user.getEmail() == null ? "" : user.getEmail(); componentManager.getLog().error(e);
} }
if (index < userInfo.length()) { try {
if (userInfo.substring(0, index).equalsIgnoreCase(prefix)) { componentManager.addComponent(serviceName, this);
foundUsers.add(user); } catch (Exception e) {
} componentManager.getLog().error(e);
} }
}
} this.serviceName = serviceName;
}
return foundUsers;
} /**
* Comparator that compares String objects, ignoring capitalization.
*/
class CaseInsensitiveComparator implements Comparator<String> {
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
}
/**
* Returns the trimmed argument, or an empty String object of null was
* supplied as an argument.
*
* @param s
* The String to be trimmed.
* @return String object that does not start or end with whitespace
* characters.
*/
private String removeNull(String s) {
if (s == null) {
return "";
}
return s.trim();
}
/**
* Returns the collection of field names that can be used to search for a
* user. Typical fields are username, name, and email. These values can be
* used to contruct a data form.
*/
public Collection<String> getSearchPluginUserManagerSearchFields() {
return Arrays.asList("Username", "Name", "Email");
}
/**
* Finds a user using the specified field and query string. For example, a
* field name of "email" and query of "jsmith@example.com" would search for
* the user with that email address. Wildcard (*) characters are allowed as
* part of queries.
*
* A possible future improvement would be to have a third parameter that
* sets the maximum number of users returned and/or the number of users that
* are searched.
*/
public Collection<User> findUsers(String field, String query) {
List<User> foundUsers = new ArrayList<User>();
if (!getSearchPluginUserManagerSearchFields().contains(field)) {
return foundUsers;
}
int index = query.indexOf("*");
if (index == -1) {
Collection<User> users = userManager.getUsers();
for (User user : users) {
if (field.equals("Username")) {
try {
foundUsers.add(userManager.getUser(query));
return foundUsers;
} catch (UserNotFoundException e) {
Log.error("Error getting user", e);
}
} else if (field.equals("Name")) {
if (query.equalsIgnoreCase(user.getName())) {
foundUsers.add(user);
}
} else if (field.equals("Email")) {
if (user.getEmail() != null) {
if (query.equalsIgnoreCase(user.getEmail())) {
foundUsers.add(user);
}
}
}
}
} else {
String prefix = query.substring(0, index);
Collection<User> users = userManager.getUsers();
for (User user : users) {
String userInfo = "";
if (field.equals("Username")) {
userInfo = user.getUsername();
} else if (field.equals("Name")) {
userInfo = user.getName();
} else if (field.equals("Email")) {
userInfo = user.getEmail() == null ? "" : user.getEmail();
}
if (index < userInfo.length()) {
if (userInfo.substring(0, index).equalsIgnoreCase(prefix)) {
foundUsers.add(user);
}
}
}
}
return foundUsers;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment