Commit 71627198 authored by Leon Roy's avatar Leon Roy Committed by leonroy

RESOLVED - issue OF-681: Add ability to search plugin to be able to restrict...

RESOLVED - issue OF-681: Add ability to search plugin to be able to restrict searching for users to only the group a user is in
http://issues.igniterealtime.org/browse/OF-681

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13705 b35dd754-fafc-0310-a699-88a17e54d16e
parent 6a4af76e
...@@ -44,6 +44,11 @@ ...@@ -44,6 +44,11 @@
Search Plugin Changelog Search Plugin Changelog
</h1> </h1>
<p><b>1.5.2</b> -- July 4, 2013</p>
<ul>
<li>OF-681 - Add ability to search plugin to be able to restrict searching for users to only the group a user is in.</li>
</ul>
<p><b>1.5.1</b> -- January 2, 2010</p> <p><b>1.5.1</b> -- January 2, 2010</p>
<ul> <ul>
<li>[<a href='http://www.igniterealtime.org/issues/browse/OF-45'>OF-45</a>] - Added Lithuanian translation by Rytis Umbrasas.</li> <li>[<a href='http://www.igniterealtime.org/issues/browse/OF-45'>OF-45</a>] - Added Lithuanian translation by Rytis Umbrasas.</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.5.1</version> <version>1.5.2</version>
<date>1/2/2010</date> <date>7/4/2013</date>
<minServerVersion>3.7.0</minServerVersion> <minServerVersion>3.7.0</minServerVersion>
<adminconsole> <adminconsole>
......
...@@ -68,4 +68,11 @@ search.props.edit.form.searchable_fields_details=Use the form below to enable wh ...@@ -68,4 +68,11 @@ search.props.edit.form.searchable_fields_details=Use the form below to enable wh
search.props.edit.form.fields=Fields search.props.edit.form.fields=Fields
search.props.edit.form.save_properties=Save Properties search.props.edit.form.save_properties=Save Properties
search.props.edit.form.search_scope=Search Scope
search.props.edit.form.search_scope_directions=Use the form below to restrict the search scope so users can search for anyone or only users in the same group.
search.props.edit.form.search_scope_anyone=Anyone
search.props.edit.form.search_scope_anyone_details=Clients will be able to search for all users.
search.props.edit.form.search_scope_groups=Group
search.props.edit.form.search_scope_groups_details=Clients will be able to only search for users in their group.
search.service_unavailable=This service is unavailable. search.service_unavailable=This service is unavailable.
...@@ -69,5 +69,12 @@ search.props.edit.form.searchable_fields_details=\u017demiau esan\u010dia forma ...@@ -69,5 +69,12 @@ search.props.edit.form.searchable_fields_details=\u017demiau esan\u010dia forma
search.props.edit.form.fields=Laukai search.props.edit.form.fields=Laukai
search.props.edit.form.save_properties=I\u0161saugoti nuostatas search.props.edit.form.save_properties=I\u0161saugoti nuostatas
search.props.edit.form.search_scope=Search Scope
search.props.edit.form.search_scope_directions=Use the form below to restrict the search scope so users can search for anyone or only users in the same group.
search.props.edit.form.search_scope_anyone=Anyone
search.props.edit.form.search_scope_anyone_details=Clients will be able to search for all users.
search.props.edit.form.search_scope_groups=Group
search.props.edit.form.search_scope_groups_details=Clients will be able to only search for users in their group.
search.service_unavailable=Paslauga negalima. search.service_unavailable=Paslauga negalima.
...@@ -48,4 +48,11 @@ search.props.edit.form.searchable_fields_details=Utilize o formul\u00e1rio abaix ...@@ -48,4 +48,11 @@ search.props.edit.form.searchable_fields_details=Utilize o formul\u00e1rio abaix
search.props.edit.form.fields=Campos search.props.edit.form.fields=Campos
search.props.edit.form.save_properties=Gravar Op\u00e7\u00f5es search.props.edit.form.save_properties=Gravar Op\u00e7\u00f5es
search.props.edit.form.search_scope=Search Scope
search.props.edit.form.search_scope_directions=Use the form below to restrict the search scope so users can search for anyone or only users in the same group.
search.props.edit.form.search_scope_anyone=Anyone
search.props.edit.form.search_scope_anyone_details=Clients will be able to search for all users.
search.props.edit.form.search_scope_groups=Group
search.props.edit.form.search_scope_groups_details=Clients will be able to only search for users in their group.
search.service_unavailable=Este servi\u00e7o est\u00e1 indispon\u00edvel. search.service_unavailable=Este servi\u00e7o est\u00e1 indispon\u00edvel.
...@@ -27,9 +27,9 @@ import java.util.Hashtable; ...@@ -27,9 +27,9 @@ import java.util.Hashtable;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; 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;
...@@ -39,6 +39,8 @@ import org.jivesoftware.openfire.container.Plugin; ...@@ -39,6 +39,8 @@ 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.IQDiscoInfoHandler;
import org.jivesoftware.openfire.disco.IQDiscoItemsHandler; import org.jivesoftware.openfire.disco.IQDiscoItemsHandler;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.openfire.group.GroupManager;
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.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
...@@ -55,48 +57,49 @@ import org.xmpp.component.ComponentManagerFactory; ...@@ -55,48 +57,49 @@ import org.xmpp.component.ComponentManagerFactory;
import org.xmpp.forms.DataForm; import org.xmpp.forms.DataForm;
import org.xmpp.forms.FormField; import org.xmpp.forms.FormField;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
import org.xmpp.packet.IQ.Type;
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 org.xmpp.packet.PacketError.Condition;
import org.xmpp.resultsetmanagement.ResultSet; import org.xmpp.resultsetmanagement.ResultSet;
import org.xmpp.resultsetmanagement.ResultSetImpl; import org.xmpp.resultsetmanagement.ResultSetImpl;
/** /**
* Provides support for Jabber Search * Provides support for Jabber Search (<a href="http://www.xmpp.org/extensions/xep-0055.html">XEP-0055</a>).
* (<a href="http://www.xmpp.org/extensions/xep-0055.html">XEP-0055</a>).<p> * <p>
* *
* The basic functionality is to query an information repository * The basic functionality is to query an information repository regarding the possible search fields, to send a search query, and to
* regarding the possible search fields, to send a search query, * receive search results. This implementation was primarily designed to use <a href="http://www.xmpp.org/extensions/xep-0004.html">Data
* and to receive search results. This implementation was primarily designed to use * Forms</a>, but also supports non-dataform searches.
* <a href="http://www.xmpp.org/extensions/xep-0004.html">Data Forms</a>, but
* also supports non-dataform searches.
* <p/> * <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 {
private static final Logger Log = LoggerFactory.getLogger(SearchPlugin.class); private static final Logger Log = LoggerFactory.getLogger(SearchPlugin.class);
public static final String NAMESPACE_JABBER_IQ_SEARCH = "jabber:iq:search"; public static final String NAMESPACE_JABBER_IQ_SEARCH = "jabber:iq:search";
public static final String SERVICENAME = "plugin.search.serviceName"; public static final String SERVICENAME = "plugin.search.serviceName";
public static final String SERVICEENABLED = "plugin.search.serviceEnabled"; public static final String SERVICEENABLED = "plugin.search.serviceEnabled";
public static final String EXCLUDEDFIELDS = "plugin.search.excludedFields"; public static final String EXCLUDEDFIELDS = "plugin.search.excludedFields";
public static final String GROUPONLY = "plugin.search.groupOnly";
private UserManager userManager;
private ComponentManager componentManager; private UserManager userManager;
private PluginManager pluginManager; private ComponentManager componentManager;
private PluginManager pluginManager;
private String serviceName;
private boolean serviceEnabled; private String serviceName;
private Collection<String> exculudedFields; private boolean serviceEnabled;
private Collection<String> excludedFields;
private static String serverName;
private boolean groupOnly;
private TreeMap<String, String> fieldLookup = new TreeMap<String, String>(new CaseInsensitiveComparator());
private Map<String, String> reverseFieldLookup = new HashMap<String, String>(); private static String serverName;
private TreeMap<String, String> fieldLookup = new TreeMap<String, String>(new CaseInsensitiveComparator());
private Map<String, String> reverseFieldLookup = new HashMap<String, String>();
/** /**
* A list of field names that are valid in jabber:iq:search * A list of field names that are valid in jabber:iq:search
...@@ -113,891 +116,892 @@ public class SearchPlugin implements Component, Plugin, PropertyEventListener { ...@@ -113,891 +116,892 @@ public class SearchPlugin implements Component, Plugin, PropertyEventListener {
validSearchRequestFields.add("set"); validSearchRequestFields.add("set");
} }
public SearchPlugin() { public SearchPlugin() {
serviceName = JiveGlobals.getProperty(SERVICENAME, "search"); serviceName = JiveGlobals.getProperty(SERVICENAME, "search");
serviceEnabled = JiveGlobals.getBooleanProperty(SERVICEENABLED, true); serviceEnabled = JiveGlobals.getBooleanProperty(SERVICEENABLED, true);
exculudedFields = StringUtils.stringToCollection(JiveGlobals.getProperty(EXCLUDEDFIELDS, "")); excludedFields = StringUtils.stringToCollection(JiveGlobals.getProperty(EXCLUDEDFIELDS, ""));
groupOnly = JiveGlobals.getBooleanProperty(GROUPONLY);
serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
userManager = UserManager.getInstance(); serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
userManager = UserManager.getInstance();
// Some clients, such as Miranda, are hard-coded to search specific fields,
// so we map those fields to the fields that Openfire actually supports. // Some clients, such as Miranda, are hard-coded to search specific fields,
fieldLookup.put("jid", "Username"); // so we map those fields to the fields that Openfire actually supports.
fieldLookup.put("username", "Username"); fieldLookup.put("jid", "Username");
fieldLookup.put("first", "Name"); fieldLookup.put("username", "Username");
fieldLookup.put("last", "Name"); fieldLookup.put("first", "Name");
fieldLookup.put("nick", "Name"); fieldLookup.put("last", "Name");
fieldLookup.put("name", "Name"); fieldLookup.put("nick", "Name");
fieldLookup.put("email", "Email"); fieldLookup.put("name", "Name");
} fieldLookup.put("email", "Email");
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.xmpp.component.Component#getName() * @see org.xmpp.component.Component#getName()
*/ */
public String getName() { public String getName() {
return pluginManager.getName(this); return pluginManager.getName(this);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.xmpp.component.Component#getDescription() * @see org.xmpp.component.Component#getDescription()
*/ */
public String getDescription() { public String getDescription() {
return pluginManager.getDescription(this); return pluginManager.getDescription(this);
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager, * @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager, java.io.File)
* java.io.File) */
*/ public void initializePlugin(PluginManager manager, File pluginDirectory) {
public void initializePlugin(PluginManager manager, File pluginDirectory) { pluginManager = manager;
pluginManager = manager;
componentManager = ComponentManagerFactory.getComponentManager();
componentManager = ComponentManagerFactory.getComponentManager(); try {
try { componentManager.addComponent(serviceName, this);
componentManager.addComponent(serviceName, this); } catch (ComponentException e) {
} Log.error(e.getMessage(), e);
catch (ComponentException e) { }
Log.error(e.getMessage(), e); PropertyEventDispatcher.addListener(this);
} }
PropertyEventDispatcher.addListener(this);
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.xmpp.component.Component#initialize(org.xmpp.packet.JID, * @see org.xmpp.component.Component#initialize(org.xmpp.packet.JID, org.xmpp.component.ComponentManager)
* org.xmpp.component.ComponentManager)
*/ */
public void initialize(JID jid, ComponentManager componentManager) { public void initialize(JID jid, ComponentManager componentManager) {
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.xmpp.component.Component#start() * @see org.xmpp.component.Component#start()
*/ */
public void start() { public void start() {
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.jivesoftware.openfire.container.Plugin#destroyPlugin() * @see org.jivesoftware.openfire.container.Plugin#destroyPlugin()
*/ */
public void destroyPlugin() { public void destroyPlugin() {
PropertyEventDispatcher.removeListener(this); PropertyEventDispatcher.removeListener(this);
pluginManager = null; pluginManager = null;
try { try {
componentManager.removeComponent(serviceName); componentManager.removeComponent(serviceName);
componentManager = null; componentManager = null;
} } catch (Exception e) {
catch (Exception e) { if (componentManager != null) {
if (componentManager != null) { Log.error(e.getMessage(), e);
Log.error(e.getMessage(), e); }
} }
} serviceName = null;
serviceName = null; userManager = null;
userManager = null; excludedFields = null;
exculudedFields = null; serverName = null;
serverName = null; fieldLookup = null;
fieldLookup = null; reverseFieldLookup = null;
reverseFieldLookup = null; }
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.xmpp.component.Component#shutdown() * @see org.xmpp.component.Component#shutdown()
*/ */
public void shutdown() { public void shutdown() {
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.xmpp.component.Component#processPacket(org.xmpp.packet.Packet) * @see org.xmpp.component.Component#processPacket(org.xmpp.packet.Packet)
*/ */
public void processPacket(Packet p) { public void processPacket(Packet p) {
if (!(p instanceof IQ)) { if (!(p instanceof IQ)) {
return; return;
} }
final IQ packet = (IQ) p; final IQ packet = (IQ) p;
if (packet.getType().equals(IQ.Type.error) if (packet.getType().equals(IQ.Type.error) || packet.getType().equals(IQ.Type.result)) {
|| packet.getType().equals(IQ.Type.result)) { return;
return; }
}
// Packet p is an IQ stanza of type GET or SET. Therefor, it _must_ be
// Packet p is an IQ stanza of type GET or SET. Therefor, it _must_ be // replied to.
// replied to. final IQ replyPacket = handleIQRequest(packet);
final IQ replyPacket = handleIQRequest(packet);
try {
try { componentManager.sendPacket(this, replyPacket);
componentManager.sendPacket(this, replyPacket); } catch (ComponentException e) {
} catch (ComponentException e) { Log.error(e.getMessage(), e);
Log.error(e.getMessage(), e); }
}
}
}
/**
/** * Handles IQ requests. This method throws an IllegalArgumentException if an IQ stanza is supplied that is not a request (if the stanza
* Handles IQ requests. This method throws an IllegalArgumentException if an * is not of type 'get' or 'set'). This method will either throw an Exception, or return a non-null IQ stanza of type 'error' or
* IQ stanza is supplied that is not a request (if the stanza is not of type * 'result', as XMPP Core specifies that <strong>all</strong> IQ request stanza's (type 'get' or 'set') MUST be replied to.
* 'get' or 'set'). This method will either throw an Exception, or return a *
* non-null IQ stanza of type 'error' or 'result', as XMPP Core specifies * @param iq
* that <strong>all</strong> IQ request stanza's (type 'get' or 'set') MUST * The IQ stanza that forms the request.
* be replied to. * @return The response to the request.
* */
* @param iq private IQ handleIQRequest(IQ iq) {
* The IQ stanza that forms the request. final IQ replyPacket; // 'final' to ensure that it is set.
* @return The response to the request.
*/ if (iq == null) {
private IQ handleIQRequest(IQ iq) { throw new IllegalArgumentException("Argument 'iq' cannot be null.");
final IQ replyPacket; // 'final' to ensure that it is set. }
if (iq == null) { final IQ.Type type = iq.getType();
throw new IllegalArgumentException("Argument 'iq' cannot be null."); if (type != IQ.Type.get && type != IQ.Type.set) {
} throw new IllegalArgumentException("Argument 'iq' must be of type 'get' or 'set'");
}
final IQ.Type type = iq.getType();
if (type != IQ.Type.get && type != IQ.Type.set) { final Element childElement = iq.getChildElement();
throw new IllegalArgumentException( if (childElement == null) {
"Argument 'iq' must be of type 'get' or 'set'"); replyPacket = IQ.createResultIQ(iq);
} replyPacket.setError(new PacketError(Condition.bad_request, org.xmpp.packet.PacketError.Type.modify,
"IQ stanzas of type 'get' and 'set' MUST contain one and only one child element (RFC 3920 section 9.2.3)."));
final Element childElement = iq.getChildElement(); return replyPacket;
if (childElement == null) { }
replyPacket = IQ.createResultIQ(iq);
replyPacket final String namespace = childElement.getNamespaceURI();
.setError(new PacketError( if (namespace == null) {
Condition.bad_request, replyPacket = IQ.createResultIQ(iq);
org.xmpp.packet.PacketError.Type.modify, replyPacket.setError(Condition.feature_not_implemented);
"IQ stanzas of type 'get' and 'set' MUST contain one and only one child element (RFC 3920 section 9.2.3).")); return replyPacket;
return replyPacket; }
}
if (namespace.equals(NAMESPACE_JABBER_IQ_SEARCH)) {
final String namespace = childElement.getNamespaceURI(); replyPacket = handleSearchRequest(iq);
if (namespace == null) { } else if (namespace.equals(IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)) {
replyPacket = IQ.createResultIQ(iq); replyPacket = handleDiscoInfo(iq);
replyPacket.setError(Condition.feature_not_implemented); } else if (namespace.equals(IQDiscoItemsHandler.NAMESPACE_DISCO_ITEMS)) {
return replyPacket; replyPacket = IQ.createResultIQ(iq);
} replyPacket.setChildElement("query", IQDiscoItemsHandler.NAMESPACE_DISCO_ITEMS);
} else {
if (namespace.equals(NAMESPACE_JABBER_IQ_SEARCH)) { // don't known what to do with this.
replyPacket = handleSearchRequest(iq); replyPacket = IQ.createResultIQ(iq);
} else if (namespace.equals(IQDiscoInfoHandler.NAMESPACE_DISCO_INFO)) { replyPacket.setError(Condition.feature_not_implemented);
replyPacket = handleDiscoInfo(iq); }
} else if (namespace.equals(IQDiscoItemsHandler.NAMESPACE_DISCO_ITEMS)) {
replyPacket = IQ.createResultIQ(iq); return replyPacket;
replyPacket.setChildElement("query", }
IQDiscoItemsHandler.NAMESPACE_DISCO_ITEMS);
} else { /**
// don't known what to do with this. * Creates a response specific to the search plugin to Disco#Info requests.
replyPacket = IQ.createResultIQ(iq); *
replyPacket.setError(Condition.feature_not_implemented); * @param iq
} * The IQ stanza that contains the request.
* @return An IQ stanza, formulated as an answer to the received request.
return replyPacket; */
} private static IQ handleDiscoInfo(IQ iq) {
if (iq == null) {
/** throw new IllegalArgumentException("Argument 'iq' cannot be null.");
* Creates a response specific to the search plugin to Disco#Info requests. }
*
* @param iq if (!iq.getChildElement().getNamespaceURI().equals(IQDiscoInfoHandler.NAMESPACE_DISCO_INFO) || iq.getType() != Type.get) {
* The IQ stanza that contains the request. throw new IllegalArgumentException("This is not a valid disco#info request.");
* @return An IQ stanza, formulated as an answer to the received request. }
*/
private static IQ handleDiscoInfo(IQ iq) { final IQ replyPacket = IQ.createResultIQ(iq);
if (iq == null) {
throw new IllegalArgumentException("Argument 'iq' cannot be null."); final Element responseElement = replyPacket.setChildElement("query", IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
} responseElement.addElement("identity").addAttribute("category", "directory").addAttribute("type", "user")
.addAttribute("name", "User Search");
if (!iq.getChildElement().getNamespaceURI().equals( responseElement.addElement("feature").addAttribute("var", NAMESPACE_JABBER_IQ_SEARCH);
IQDiscoInfoHandler.NAMESPACE_DISCO_INFO) responseElement.addElement("feature").addAttribute("var", IQDiscoInfoHandler.NAMESPACE_DISCO_INFO);
|| iq.getType() != Type.get) { responseElement.addElement("feature").addAttribute("var", ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT);
throw new IllegalArgumentException(
"This is not a valid disco#info request."); return replyPacket;
} }
final IQ replyPacket = IQ.createResultIQ(iq); private IQ handleSearchRequest(IQ packet) {
if (!serviceEnabled) {
final Element responseElement = replyPacket.setChildElement("query", return replyDisabled(packet);
IQDiscoInfoHandler.NAMESPACE_DISCO_INFO); }
responseElement.addElement("identity").addAttribute("category",
"directory").addAttribute("type", "user").addAttribute("name", switch (packet.getType()) {
"User Search"); case get:
responseElement.addElement("feature").addAttribute("var", return processGetPacket(packet);
NAMESPACE_JABBER_IQ_SEARCH);
responseElement.addElement("feature").addAttribute("var", case set:
IQDiscoInfoHandler.NAMESPACE_DISCO_INFO); return processSetPacket(packet);
responseElement.addElement("feature").addAttribute("var",
ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT); default:
// we can safely ignore 'error' and 'result' typed iq stanzas.
return replyPacket; return null;
} }
}
private IQ handleSearchRequest(IQ packet) {
if (!serviceEnabled) { /**
return replyDisabled(packet); * Constructs a IQ result stanza, based on the request stanza that is provided as an argument. The stanza tells the recipient that this
} * service is currently unavailable.
*
switch (packet.getType()) { * @param packet
case get: * The request IQ stanza to which a result will be returned.
return processGetPacket(packet); * @return A result stanza, telling the user that this service is unavailable.
*/
case set: private static IQ replyDisabled(IQ packet) {
return processSetPacket(packet); IQ replyPacket = IQ.createResultIQ(packet);
Element reply = replyPacket.setChildElement("query", NAMESPACE_JABBER_IQ_SEARCH);
default: final DataForm unavailableForm = new DataForm(DataForm.Type.cancel);
// we can safely ignore 'error' and 'result' typed iq stanzas. unavailableForm.setTitle(LocaleUtils.getLocalizedString("advance.user.search.title", "search"));
return null; unavailableForm.addInstruction(LocaleUtils.getLocalizedString("search.service_unavailable", "search"));
} reply.add(unavailableForm.getElement());
}
return replyPacket;
/** }
* Constructs a IQ result stanza, based on the request stanza that is
* provided as an argument. The stanza tells the recipient that this service /**
* is currently unavailable. * Processes an IQ stanza of type 'get', which in the context of 'Jabber Search' is a request for available search fields.
* *
* @param packet * @param packet
* The request IQ stanza to which a result will be returned. * An IQ stanza of type 'get'
* @return A result stanza, telling the user that this service is * @return A result IQ stanza that contains the possbile search fields.
* unavailable. */
*/ private IQ processGetPacket(IQ packet) {
private static IQ replyDisabled(IQ packet) { if (!packet.getType().equals(IQ.Type.get)) {
IQ replyPacket = IQ.createResultIQ(packet); throw new IllegalArgumentException("This method only accepts 'get' typed IQ stanzas as an argument.");
Element reply = replyPacket.setChildElement("query", }
NAMESPACE_JABBER_IQ_SEARCH); IQ replyPacket = IQ.createResultIQ(packet);
final DataForm unavailableForm = new DataForm(DataForm.Type.cancel);
unavailableForm.setTitle(LocaleUtils.getLocalizedString( Element queryResult = DocumentHelper.createElement(QName.get("query", NAMESPACE_JABBER_IQ_SEARCH));
"advance.user.search.title", "search"));
unavailableForm.addInstruction(LocaleUtils.getLocalizedString( String instructions = LocaleUtils.getLocalizedString("advance.user.search.details", "search");
"search.service_unavailable", "search"));
reply.add(unavailableForm.getElement()); // non-data form
queryResult.addElement("instructions").addText(instructions);
return replyPacket; queryResult.addElement("first");
} queryResult.addElement("last");
queryResult.addElement("nick");
/** queryResult.addElement("email");
* Processes an IQ stanza of type 'get', which in the context of 'Jabber
* Search' is a request for available search fields. DataForm searchForm = new DataForm(DataForm.Type.form);
* searchForm.setTitle(LocaleUtils.getLocalizedString("advance.user.search.title", "search"));
* @param packet searchForm.addInstruction(instructions);
* An IQ stanza of type 'get'
* @return A result IQ stanza that contains the possbile search fields. searchForm.addField("FORM_TYPE", null, FormField.Type.hidden).addValue(NAMESPACE_JABBER_IQ_SEARCH);
*/
private IQ processGetPacket(IQ packet) { searchForm.addField("search", LocaleUtils.getLocalizedString("advance.user.search.search", "search"), FormField.Type.text_single)
if (!packet.getType().equals(IQ.Type.get)) { .setRequired(true);
throw new IllegalArgumentException(
"This method only accepts 'get' typed IQ stanzas as an argument."); for (String searchField : getFilteredSearchFields()) {
} final FormField field = searchForm.addField();
IQ replyPacket = IQ.createResultIQ(packet); field.setVariable(searchField);
field.setType(FormField.Type.boolean_type);
Element queryResult = DocumentHelper.createElement(QName.get("query", field.addValue("1");
NAMESPACE_JABBER_IQ_SEARCH)); field.setLabel(LocaleUtils.getLocalizedString("advance.user.search." + searchField.toLowerCase(), "search"));
field.setRequired(false);
String instructions = LocaleUtils.getLocalizedString( }
"advance.user.search.details", "search");
queryResult.add(searchForm.getElement());
// non-data form replyPacket.setChildElement(queryResult);
queryResult.addElement("instructions").addText(instructions);
queryResult.addElement("first"); return replyPacket;
queryResult.addElement("last"); }
queryResult.addElement("nick");
queryResult.addElement("email"); /**
* Processes an IQ stanza of type 'set', which in the context of 'Jabber Search' is a search request.
DataForm searchForm = new DataForm(DataForm.Type.form); *
searchForm.setTitle(LocaleUtils.getLocalizedString( * @param packet
"advance.user.search.title", "search")); * An IQ stanza of type 'get'
searchForm.addInstruction(instructions); * @return A result IQ stanza that contains the possbile search fields.
*/
searchForm.addField("FORM_TYPE", null, FormField.Type.hidden) private IQ processSetPacket(IQ packet) {
.addValue(NAMESPACE_JABBER_IQ_SEARCH); if (!packet.getType().equals(IQ.Type.set)) {
throw new IllegalArgumentException("This method only accepts 'set' typed IQ stanzas as an argument.");
searchForm.addField("search", }
LocaleUtils.getLocalizedString("advance.user.search.search", "search"),
FormField.Type.text_single) JID fromJID = packet.getFrom();
.setRequired(true);
final IQ resultIQ;
for (String searchField : getFilteredSearchFields()) { // check if the request complies to the XEP-0055 standards
final FormField field = searchForm.addField(); if (!isValidSearchRequest(packet)) {
field.setVariable(searchField); resultIQ = IQ.createResultIQ(packet);
field.setType(FormField.Type.boolean_type); resultIQ.setError(Condition.bad_request);
field.addValue("1"); return resultIQ;
field.setLabel(LocaleUtils.getLocalizedString( }
"advance.user.search." + searchField.toLowerCase(), "search"));
field.setRequired(false); final Element incomingForm = packet.getChildElement();
} final boolean isDataFormQuery = (incomingForm.element(QName.get("x", "jabber:x:data")) != null);
queryResult.add(searchForm.getElement()); final Element rsmElement = incomingForm.element(QName.get("set", ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT));
replyPacket.setChildElement(queryResult);
if (rsmElement != null) {
return replyPacket; final Element maxElement = rsmElement.element("max");
} final Element startIndexElement = rsmElement.element("index");
/** int startIndex = 0;
* Processes an IQ stanza of type 'set', which in the context of 'Jabber if (startIndexElement != null) {
* Search' is a search request. startIndex = Integer.parseInt(startIndexElement.getTextTrim());
* }
* @param packet
* An IQ stanza of type 'get' int max = -1;
* @return A result IQ stanza that contains the possbile search fields. if (maxElement != null) {
*/ max = Integer.parseInt(maxElement.getTextTrim());
private IQ processSetPacket(IQ packet) { }
if (!packet.getType().equals(IQ.Type.set)) {
throw new IllegalArgumentException( final Set<User> searchResults = performSearch(incomingForm, startIndex, max);
"This method only accepts 'set' typed IQ stanzas as an argument.");
} if (groupOnly) {
Collection<Group> groups = GroupManager.getInstance().getGroups(fromJID);
final IQ resultIQ; Set<User> allSearchResults = new HashSet<User>(searchResults);
searchResults.clear();
// check if the request complies to the XEP-0055 standards for (User user : allSearchResults) {
if (!isValidSearchRequest(packet)) { for (Group group : groups) {
resultIQ = IQ.createResultIQ(packet); if (group.isUser(user.getUID())) {
resultIQ.setError(Condition.bad_request); searchResults.add(user);
return resultIQ; }
} }
}
final Element incomingForm = packet.getChildElement(); }
final boolean isDataFormQuery = (incomingForm.element(QName.get("x", "jabber:x:data")) != null);
// apply RSM
final Element rsmElement = incomingForm.element(QName.get("set", final List<User> rsmResults;
ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT)); final ResultSet<User> rs = new ResultSetImpl<User>(searchResults);
try {
if (rsmElement != null) { rsmResults = rs.applyRSMDirectives(rsmElement);
final Element maxElement = rsmElement.element("max"); } catch (NullPointerException e) {
final Element startIndexElement = rsmElement.element("index"); final IQ itemNotFound = IQ.createResultIQ(packet);
itemNotFound.setError(Condition.item_not_found);
int startIndex = 0; return itemNotFound;
if(startIndexElement != null) { }
startIndex = Integer.parseInt(startIndexElement.getTextTrim()); if (isDataFormQuery) {
} resultIQ = replyDataFormResult(rsmResults, packet);
} else {
int max = -1; resultIQ = replyNonDataFormResult(rsmResults, packet);
if(maxElement != null) { }
max = Integer.parseInt(maxElement.getTextTrim());
} // add the additional 'set' element.
final Element set = rs.generateSetElementFromResults(rsmResults);
final Set<User> searchResults = performSearch(incomingForm, startIndex, max); resultIQ.getChildElement().add(set);
// apply RSM } else {
final List<User> rsmResults; final Set<User> searchResults = performSearch(incomingForm);
final ResultSet<User> rs = new ResultSetImpl<User>(searchResults);
try { if (groupOnly) {
rsmResults = rs.applyRSMDirectives(rsmElement); Collection<Group> groups = GroupManager.getInstance().getGroups(fromJID);
} catch (NullPointerException e) { Set<User> allSearchResults = new HashSet<User>(searchResults);
final IQ itemNotFound = IQ.createResultIQ(packet); searchResults.clear();
itemNotFound.setError(Condition.item_not_found); for (User user : allSearchResults) {
return itemNotFound; for (Group group : groups) {
} if (group.isUser(user.getUID())) {
if (isDataFormQuery) { searchResults.add(user);
resultIQ = replyDataFormResult(rsmResults, packet); }
} else { }
resultIQ = replyNonDataFormResult(rsmResults, packet); }
} }
// add the additional 'set' element. // don't apply RSM
final Element set = rs.generateSetElementFromResults(rsmResults); if (isDataFormQuery) {
resultIQ.getChildElement().add(set); resultIQ = replyDataFormResult(searchResults, packet);
} else {
} else { resultIQ = replyNonDataFormResult(searchResults, packet);
final Set<User> searchResults = performSearch(incomingForm); }
// don't apply RSM }
if (isDataFormQuery) {
resultIQ = replyDataFormResult(searchResults, packet); return resultIQ;
} else { }
resultIQ = replyNonDataFormResult(searchResults, packet);
} public Set<User> filterGroupSearchResults(JID jid, Set<User> searchResults) {
} if (groupOnly) {
Collection<Group> groups = GroupManager.getInstance().getGroups(jid);
return resultIQ; Set<User> allSearchResults = new HashSet<User>(searchResults);
} searchResults.clear();
for (User user : allSearchResults) {
/** for (Group group : groups) {
* This method checks if the search request that was received is a valid if (group.isUser(user.getUID())) {
* JABBER:IQ:SEARCH request. In other words, it checks if the search request searchResults.add(user);
* 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> return searchResults;
* <li>if the stanza child element is has valid children itself.</li> }
* </ul>
* /**
* @param iq * This method checks if the search request that was received is a valid JABBER:IQ:SEARCH request. In other words, it checks if the
* The IQ object that should include a jabber:iq:search request. * search request is spec compliant (XEP-0055). It does this by checking:
* @return ''true'' if the supplied IQ stanza is a spec compliant search * <ul>
* request, ''false'' otherwise. * <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>
@SuppressWarnings("unchecked") * <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.
*/
@SuppressWarnings("unchecked")
public static boolean isValidSearchRequest(IQ iq) { public static boolean isValidSearchRequest(IQ iq) {
if (iq == null) { if (iq == null) {
throw new IllegalArgumentException("Argument 'iq' cannot be null."); throw new IllegalArgumentException("Argument 'iq' cannot be null.");
} }
if (iq.getType() != IQ.Type.set) { if (iq.getType() != IQ.Type.set) {
return false; return false;
} }
final Element childElement = iq.getChildElement(); final Element childElement = iq.getChildElement();
if (childElement == null) { if (childElement == null) {
return false; return false;
} }
if (!childElement.getNamespaceURI().equals(NAMESPACE_JABBER_IQ_SEARCH)) { if (!childElement.getNamespaceURI().equals(NAMESPACE_JABBER_IQ_SEARCH)) {
return false; return false;
} }
if (!childElement.getName().equals("query")) { if (!childElement.getName().equals("query")) {
return false; return false;
} }
final List<Element> fields = childElement.elements(); final List<Element> fields = childElement.elements();
if (fields.size() == 0) { if (fields.size() == 0) {
return false; return false;
} }
for (Element element : fields) { for (Element element : fields) {
final String name = element.getName(); final String name = element.getName();
if (!validSearchRequestFields.contains(name)) { if (!validSearchRequestFields.contains(name)) {
return false; return false;
} }
// TODO: check dataform validity. // TODO: check dataform validity.
// if (name.equals("x") && !isValidDataForm(element)) // if (name.equals("x") && !isValidDataForm(element))
// { // {
// return false; // return false;
// } // }
if (name.equals("set") && !ResultSet.isValidRSMRequest(element)) { if (name.equals("set") && !ResultSet.isValidRSMRequest(element)) {
return false; return false;
} }
} }
return true; return true;
} }
private Set<User> performSearch(Element incomingForm, int startIndex, int max) { private Set<User> performSearch(Element incomingForm, int startIndex, int max) {
Set<User> users = new HashSet<User>(); Set<User> users = new HashSet<User>();
Hashtable<String, String> searchList = extractSearchQuery(incomingForm); Hashtable<String, String> searchList = extractSearchQuery(incomingForm);
for (Entry<String, String> entry : searchList.entrySet()) { for (Entry<String, String> entry : searchList.entrySet()) {
String field = entry.getKey(); String field = entry.getKey();
String query = entry.getValue(); String query = entry.getValue();
Collection<User> foundUsers = new ArrayList<User>(); Collection<User> foundUsers = new ArrayList<User>();
if (userManager != null && query.length() > 0 && !query.equals(NAMESPACE_JABBER_IQ_SEARCH)) { if (userManager != null && query.length() > 0 && !query.equals(NAMESPACE_JABBER_IQ_SEARCH)) {
if(max >= 0) { if (max >= 0) {
foundUsers.addAll(userManager.findUsers(new HashSet<String>(Arrays.asList(field)), query, startIndex, max)); foundUsers.addAll(userManager.findUsers(new HashSet<String>(Arrays.asList(field)), query, startIndex, max));
} else { } else {
foundUsers.addAll(userManager.findUsers(new HashSet<String>(Arrays.asList(field)), query)); foundUsers.addAll(userManager.findUsers(new HashSet<String>(Arrays.asList(field)), query));
} }
} }
// occasionally a null User is returned so filter them out // occasionally a null User is returned so filter them out
for (User user : foundUsers) { for (User user : foundUsers) {
if (user != null) { if (user != null) {
users.add(user); users.add(user);
} }
} }
} }
return users; return users;
} }
/** /**
* Performs a search based on form data, and returns the search results. * Performs a search based on form data, and returns the search results.
* *
* @param incomingForm * @param incomingForm
* The form containing the search data * The form containing the search data
* @return A set of users that matches the search criteria. * @return A set of users that matches the search criteria.
*/ */
private Set<User> performSearch(Element incomingForm) { private Set<User> performSearch(Element incomingForm) {
return performSearch(incomingForm, -1, -1); return performSearch(incomingForm, -1, -1);
} }
/** /**
* This utilty method extracts the search query from the request. A query is * 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
* defined as a set of key->value pairs, where the key denotes a search * a search field, and the value contains the value that was filled out by the user for that field.
* 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
* The query can be specified in one of two ways. The first way is a query * {@link #extractSearchQuery(Element)} that make use of this last method get forwarded to {@link #extractExtendedSearchQuery(Element)}.
* 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 * @param incomingForm
* are supplied to this {@link #extractSearchQuery(Element)} that make use * The form from which to extract the query
* of this last method get forwarded to * @return The search query for a particular user search request.
* {@link #extractExtendedSearchQuery(Element)}. */
* @SuppressWarnings("unchecked")
* @param incomingForm
* The form from which to extract the query
* @return The search query for a particular user search request.
*/
@SuppressWarnings("unchecked")
private Hashtable<String, String> extractSearchQuery(Element incomingForm) { private Hashtable<String, String> extractSearchQuery(Element incomingForm) {
if (incomingForm.element(QName.get("x", "jabber:x:data")) != null) { if (incomingForm.element(QName.get("x", "jabber:x:data")) != null) {
// forward the request. // forward the request.
return extractExtendedSearchQuery(incomingForm); return extractExtendedSearchQuery(incomingForm);
} }
final Hashtable<String, String> searchList = new Hashtable<String, String>(); final Hashtable<String, String> searchList = new Hashtable<String, String>();
// since not all clients request which fields are available for // since not all clients request which fields are available for
// searching attempt to match submitted fields with available search // searching attempt to match submitted fields with available search
// fields // fields
Iterator<Element> iter = incomingForm.elementIterator(); Iterator<Element> iter = incomingForm.elementIterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Element element = iter.next(); Element element = iter.next();
String name = element.getName(); String name = element.getName();
if (fieldLookup.containsKey(name)) { if (fieldLookup.containsKey(name)) {
// make best effort to map the fields submitted by // make best effort to map the fields submitted by
// the client to those that Openfire can search // the client to those that Openfire can search
reverseFieldLookup.put(fieldLookup.get(name), name); reverseFieldLookup.put(fieldLookup.get(name), name);
searchList.put(fieldLookup.get(name), element.getText()); searchList.put(fieldLookup.get(name), element.getText());
} }
} }
return searchList; return searchList;
} }
/** /**
* Extracts a search query from a data form that makes use of data forms to * Extracts a search query from a data form that makes use of data forms to specify the search request. This 'extended' way of
* specify the search request. This 'extended' way of constructing a search * constructing a search request is documented in XEP-0055, chapter 3.
* request is documented in XEP-0055, chapter 3. *
* * @param incomingForm
* @param incomingForm * The form from which to extract the query
* The form from which to extract the query * @return The search query for a particular user search request.
* @return The search query for a particular user search request. * @see #extractSearchQuery(Element)
* @see #extractSearchQuery(Element) */
*/ @SuppressWarnings("unchecked")
@SuppressWarnings("unchecked") private Hashtable<String, String> extractExtendedSearchQuery(Element incomingForm) {
private Hashtable<String, String> extractExtendedSearchQuery( final Element dataform = incomingForm.element(QName.get("x", "jabber:x:data"));
Element incomingForm) {
final Element dataform = incomingForm.element(QName.get("x", Hashtable<String, String> searchList = new Hashtable<String, String>();
"jabber:x:data")); List<String> searchFields = new ArrayList<String>();
String search = "";
Hashtable<String, String> searchList = new Hashtable<String, String>();
List<String> searchFields = new ArrayList<String>(); Iterator<Element> fields = dataform.elementIterator("field");
String search = ""; while (fields.hasNext()) {
Element searchField = fields.next();
Iterator<Element> fields = dataform.elementIterator("field");
while (fields.hasNext()) { String field = searchField.attributeValue("var");
Element searchField = fields.next(); String value = "";
if (searchField.element("value") != null) {
String field = searchField.attributeValue("var"); value = searchField.element("value").getTextTrim();
String value = ""; }
if (searchField.element("value") != null) { if (field.equals("search")) {
value = searchField.element("value").getTextTrim(); search = value;
} } else if (value.equals("1")) {
if (field.equals("search")) { searchFields.add(field);
search = value; }
} else if (value.equals("1")) { }
searchFields.add(field);
} for (String field : searchFields) {
} searchList.put(field, search);
}
for (String field : searchFields) {
searchList.put(field, search); return searchList;
} }
return searchList; /**
} * Constructs a query that is returned as an IQ packet that contains the search results.
*
/** * @param users
* Constructs a query that is returned as an IQ packet that contains the search results. * set of users that will be used to construct the search results
* * @param packet
* @param users set of users that will be used to construct the search results * 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(Collection<User> users, IQ packet) {
private IQ replyDataFormResult(Collection<User> users, IQ packet) { final DataForm searchResults = new DataForm(DataForm.Type.result);
final DataForm searchResults = new DataForm(DataForm.Type.result);
searchResults.addField("FORM_TYPE", null, FormField.Type.hidden);
searchResults.addField("FORM_TYPE", null, FormField.Type.hidden);
searchResults.addReportedField("jid", "JID", FormField.Type.jid_single);
searchResults.addReportedField("jid", "JID", FormField.Type.jid_single);
for (final String fieldName : getFilteredSearchFields()) {
for (final String fieldName : getFilteredSearchFields()) { searchResults.addReportedField(fieldName,
searchResults.addReportedField(fieldName, LocaleUtils.getLocalizedString("advance.user.search." + fieldName.toLowerCase(), "search"), FormField.Type.text_single);
LocaleUtils.getLocalizedString("advance.user.search." + fieldName.toLowerCase(), "search"), }
FormField.Type.text_single);
} for (final User user : users) {
final String username = JID.unescapeNode(user.getUsername());
for (final User user : users) {
final String username = JID.unescapeNode(user.getUsername()); final Map<String, Object> item = new HashMap<String, Object>();
item.put("jid", username + "@" + serverName);
final Map<String, Object> item = new HashMap<String, Object>();
item.put("jid", item.put(LocaleUtils.getLocalizedString("advance.user.search.username", "search"), username);
username + "@" + serverName);
item.put(LocaleUtils.getLocalizedString("advance.user.search.name", "search"),
item.put(LocaleUtils.getLocalizedString("advance.user.search.username", "search"), (user.isNameVisible() ? removeNull(user.getName()) : ""));
username);
item.put(LocaleUtils.getLocalizedString("advance.user.search.email", "search"),
item.put(LocaleUtils.getLocalizedString("advance.user.search.name", "search"), (user.isEmailVisible() ? removeNull(user.getEmail()) : ""));
(user.isNameVisible() ? removeNull(user.getName()) : ""));
searchResults.addItemFields(item);
item.put(LocaleUtils.getLocalizedString("advance.user.search.email", "search"), }
(user.isEmailVisible() ? removeNull(user.getEmail()) : ""));
IQ replyPacket = IQ.createResultIQ(packet);
searchResults.addItemFields(item); Element reply = replyPacket.setChildElement("query", NAMESPACE_JABBER_IQ_SEARCH);
} reply.add(searchResults.getElement());
IQ replyPacket = IQ.createResultIQ(packet); return replyPacket;
Element reply = replyPacket.setChildElement("query", }
NAMESPACE_JABBER_IQ_SEARCH);
reply.add(searchResults.getElement()); /**
* Constructs a query that is returned as an IQ packet that contains the search results.
return replyPacket; *
} * @param users
* set of users that will be used to construct the search results
/** * @param packet
* Constructs a query that is returned as an IQ packet that contains the search results. * the IQ packet sent by the client
* * @return the iq packet that contains 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 private IQ replyNonDataFormResult(Collection<User> users, IQ packet) {
* @return the iq packet that contains the search results IQ replyPacket = IQ.createResultIQ(packet);
*/ Element replyQuery = replyPacket.setChildElement("query", NAMESPACE_JABBER_IQ_SEARCH);
private IQ replyNonDataFormResult(Collection<User> users, IQ packet) {
IQ replyPacket = IQ.createResultIQ(packet); for (User user : users) {
Element replyQuery = replyPacket.setChildElement("query", Element item = replyQuery.addElement("item");
NAMESPACE_JABBER_IQ_SEARCH); String username = JID.unescapeNode(user.getUsername());
item.addAttribute("jid", username + "@" + serverName);
for (User user : users) {
Element item = replyQuery.addElement("item"); // return to the client the same fields that were submitted
String username = JID.unescapeNode(user.getUsername()); for (String field : reverseFieldLookup.keySet()) {
item.addAttribute("jid", username + "@" + serverName); if ("Username".equals(field)) {
Element element = item.addElement(reverseFieldLookup.get(field));
// return to the client the same fields that were submitted element.addText(username);
for (String field : reverseFieldLookup.keySet()) { }
if ("Username".equals(field)) {
Element element = item.addElement(reverseFieldLookup if ("Name".equals(field)) {
.get(field)); Element element = item.addElement(reverseFieldLookup.get(field));
element.addText(username); element.addText(user.isNameVisible() ? removeNull(user.getName()) : "");
} }
if ("Name".equals(field)) { if ("Email".equals(field)) {
Element element = item.addElement(reverseFieldLookup Element element = item.addElement(reverseFieldLookup.get(field));
.get(field)); element.addText(user.isEmailVisible() ? removeNull(user.getEmail()) : "");
element.addText(user.isNameVisible() ? removeNull(user.getName()) : ""); }
} }
}
if ("Email".equals(field)) {
Element element = item.addElement(reverseFieldLookup return replyPacket;
.get(field)); }
element.addText(user.isEmailVisible() ? removeNull(user.getEmail()) : "");
} /**
} * Returns the service name of this component, which is "search" by default.
} *
* @return the service name of this component.
return replyPacket; */
} public String getServiceName() {
return serviceName;
/** }
* Returns the service name of this component, which is "search" by default.
* /**
* @return the service name of this component. * 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 and then add itself back using the new name.
public String getServiceName() { *
return serviceName; * @param name
} * the service name of this component.
*/
/** public void setServiceName(String name) {
* Sets the service name of this component, which is "search" by default. If the name changeServiceName(name);
* is different than the existing name the plugin will remove itself from the ComponentManager JiveGlobals.setProperty(SERVICENAME, name);
* and then add itself back using the new name. }
*
* @param name the service name of this component. /**
*/ * Checks if the search service is enabled.
public void setServiceName(String name) { *
changeServiceName(name);
JiveGlobals.setProperty(SERVICENAME, name);
}
/**
* Checks if the search service is enabled.
*
* @return true if search service is enabled. * @return true if search service is enabled.
*/ */
public boolean getServiceEnabled() { public boolean getServiceEnabled() {
return serviceEnabled; return serviceEnabled;
} }
/** /**
* Enables or disables the search service. When disabled, when a client tries * Enables or disables the search service. When disabled, when a client tries to do a search they will receive an XForm informing that
* to do a search they will receive an XForm informing that the service is * the service is unavailable.
* unavailable. *
* * @param enabled
* @param enabled true if group permission checking should be disabled. * true if group permission checking should be disabled.
*/ */
public void setServiceEnabled(boolean enabled) { public void setServiceEnabled(boolean enabled) {
serviceEnabled = enabled; serviceEnabled = enabled;
JiveGlobals.setProperty(SERVICEENABLED, enabled ? "true" : "false"); JiveGlobals.setProperty(SERVICEENABLED, enabled ? "true" : "false");
} }
/** /**
* Returns the collection of searchable field names that does not include the fields * Returns the collection of searchable field names that does not include the fields listed in the EXCLUDEDFIELDS property list.
* listed in the EXCLUDEDFIELDS property list. *
* * @return collection of searchable field names.
* @return collection of searchable field names. */
*/ public Collection<String> getFilteredSearchFields() {
public Collection<String> getFilteredSearchFields() { Collection<String> searchFields;
Collection<String> searchFields;
// See if the installed provider supports searching. If not, workaround
// See if the installed provider supports searching. If not, workaround // by providing our own searching.
// by providing our own searching. try {
try { searchFields = new ArrayList<String>(userManager.getSearchFields());
searchFields = new ArrayList<String>(userManager.getSearchFields()); } catch (UnsupportedOperationException uoe) {
} // Use a SearchPluginUserManager instead.
catch (UnsupportedOperationException uoe) { searchFields = getSearchPluginUserManagerSearchFields();
// Use a SearchPluginUserManager instead. }
searchFields = getSearchPluginUserManagerSearchFields();
} searchFields.removeAll(excludedFields);
searchFields.removeAll(exculudedFields); return searchFields;
}
return searchFields;
} /**
* 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 the search results.
* 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 * @param excludedFields
* the search results. * fields that can not be searched on or shown to the client
* */
* @param exculudedFields fields that can not be searched on or shown to the client public void setExcludedFields(Collection<String> excludedFields) {
*/ this.excludedFields = excludedFields;
public void setExcludedFields(Collection<String> exculudedFields) { JiveGlobals.setProperty(EXCLUDEDFIELDS, StringUtils.collectionToString(excludedFields));
this.exculudedFields = exculudedFields; }
JiveGlobals.setProperty(EXCLUDEDFIELDS, StringUtils.collectionToString(exculudedFields));
} /**
* Checks if the search service is restricted to groups.
*
* @return true if restricted to groups.
*/
public boolean isGroupOnly() {
return groupOnly;
}
/**
* Sets the search service scope.
*
* @param groupOnly
* true if group only.
*/
public void setGroupOnly(boolean groupOnly) {
this.groupOnly = groupOnly;
JiveGlobals.setProperty(GROUPONLY, groupOnly ? "true" : "false");
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, * @see org.jivesoftware.util.PropertyEventListener#propertySet(java.lang.String, java.util.Map)
* java.util.Map)
*/ */
public void propertySet(String property, Map<String, Object> params) { public void propertySet(String property, Map<String, Object> params) {
if (property.equals(SERVICEENABLED)) { if (property.equals(SERVICEENABLED)) {
this.serviceEnabled = Boolean.parseBoolean((String)params.get("value")); this.serviceEnabled = Boolean.parseBoolean((String) params.get("value"));
} } else if (property.equals(SERVICENAME)) {
else if (property.equals(SERVICENAME)) { changeServiceName((String) params.get("value"));
changeServiceName((String)params.get("value")); } else if (property.equals(EXCLUDEDFIELDS)) {
} excludedFields = StringUtils.stringToCollection(JiveGlobals.getProperty(EXCLUDEDFIELDS, (String) params.get("value")));
else if (property.equals(EXCLUDEDFIELDS)) { } else if (property.equals(GROUPONLY)) {
exculudedFields = StringUtils.stringToCollection(JiveGlobals.getProperty(EXCLUDEDFIELDS, (String)params.get("value"))); this.groupOnly = Boolean.parseBoolean((String) params.get("value"));
} }
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String, * @see org.jivesoftware.util.PropertyEventListener#propertyDeleted(java.lang.String, java.util.Map)
* java.util.Map)
*/ */
public void propertyDeleted(String property, Map<String, Object> params) { public void propertyDeleted(String property, Map<String, Object> params) {
if (property.equals(SERVICEENABLED)) { if (property.equals(SERVICEENABLED)) {
this.serviceEnabled = true; this.serviceEnabled = true;
} } else if (property.equals(SERVICENAME)) {
else if (property.equals(SERVICENAME)) { changeServiceName("search");
changeServiceName("search"); } else if (property.equals(EXCLUDEDFIELDS)) {
} excludedFields = new ArrayList<String>();
else if (property.equals(EXCLUDEDFIELDS)) { } else if (property.equals(GROUPONLY)) {
exculudedFields = new ArrayList<String>(); this.groupOnly = false;
} }
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.jivesoftware.util.PropertyEventListener#xmlPropertySet(java.lang.String, * @see org.jivesoftware.util.PropertyEventListener#xmlPropertySet(java.lang.String, java.util.Map)
* java.util.Map)
*/ */
public void xmlPropertySet(String property, Map<String, Object> params) { public void xmlPropertySet(String property, Map<String, Object> params) {
// not used // not used
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
* @see org.jivesoftware.util.PropertyEventListener#xmlPropertyDeleted(java.lang.String, * @see org.jivesoftware.util.PropertyEventListener#xmlPropertyDeleted(java.lang.String, java.util.Map)
* java.util.Map)
*/ */
public void xmlPropertyDeleted(String property, Map<String, Object> params) { public void xmlPropertyDeleted(String property, Map<String, Object> params) {
// not used // not used
} }
private void changeServiceName(String serviceName) { private void changeServiceName(String serviceName) {
if (serviceName == null) { if (serviceName == null) {
throw new NullPointerException("Service name cannot be null"); throw new NullPointerException("Service name cannot be null");
} }
if (this.serviceName.equals(serviceName)) { if (this.serviceName.equals(serviceName)) {
return; return;
} }
// Re-register the service. // Re-register the service.
try { try {
componentManager.removeComponent(this.serviceName); componentManager.removeComponent(this.serviceName);
} } catch (Exception e) {
catch (Exception e) { Log.error(e.getMessage(), e);
Log.error(e.getMessage(), e); }
}
try {
try { componentManager.addComponent(serviceName, this);
componentManager.addComponent(serviceName, this); } catch (Exception e) {
} Log.error(e.getMessage(), e);
catch (Exception e) { }
Log.error(e.getMessage(), e);
} this.serviceName = serviceName;
}
this.serviceName = serviceName;
}
/** /**
* Comparator that compares String objects, ignoring capitalization. * Comparator that compares String objects, ignoring capitalization.
*/ */
private class CaseInsensitiveComparator implements Comparator<String> { private class CaseInsensitiveComparator implements Comparator<String> {
public int compare(String s1, String s2) { public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2); return s1.compareToIgnoreCase(s2);
} }
} }
/** /**
* Returns the trimmed argument, or an empty String object of null was * Returns the trimmed argument, or an empty String object of null was supplied as an argument.
* supplied as an argument.
* *
* @param s * @param s
* The String to be trimmed. * The String to be trimmed.
* @return String object that does not start or end with whitespace * @return String object that does not start or end with whitespace characters.
* characters. */
*/ private String removeNull(String s) {
private String removeNull(String s) { if (s == null) {
if (s == null) { return "";
return ""; }
}
return s.trim();
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
* Returns the collection of field names that can be used to search for a * values can be used to contruct a data form.
* user. Typical fields are username, name, and email. These values can be *
* used to contruct a data form. * @return the collection of field names that can be used to search.
* */
* @return the collection of field names that can be used to search. public Collection<String> getSearchPluginUserManagerSearchFields() {
*/ return Arrays.asList("Username", "Name", "Email");
public Collection<String> getSearchPluginUserManagerSearchFields() { }
return Arrays.asList("Username", "Name", "Email");
}
} }
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
boolean success = request.getParameter("success") != null; boolean success = request.getParameter("success") != null;
String searchName = ParamUtils.getParameter(request, "searchname"); String searchName = ParamUtils.getParameter(request, "searchname");
boolean searchEnabled = ParamUtils.getBooleanParameter(request, "searchEnabled"); boolean searchEnabled = ParamUtils.getBooleanParameter(request, "searchEnabled");
boolean groupOnly = ParamUtils.getBooleanParameter(request, "groupOnly");
SearchPlugin plugin = (SearchPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("search"); SearchPlugin plugin = (SearchPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("search");
// Handle a save // Handle a save
...@@ -34,7 +35,7 @@ ...@@ -34,7 +35,7 @@
} }
} }
plugin.setExcludedFields(excludedFields); plugin.setExcludedFields(excludedFields);
plugin.setGroupOnly(groupOnly);
response.sendRedirect("search-props-edit-form.jsp?success=true"); response.sendRedirect("search-props-edit-form.jsp?success=true");
return; return;
} }
...@@ -50,6 +51,7 @@ ...@@ -50,6 +51,7 @@
searchEnabled = plugin.getServiceEnabled(); searchEnabled = plugin.getServiceEnabled();
Collection<String> searchableFields = plugin.getFilteredSearchFields(); Collection<String> searchableFields = plugin.getFilteredSearchFields();
groupOnly = plugin.isGroupOnly();
%> %>
<html> <html>
...@@ -169,6 +171,38 @@ ...@@ -169,6 +171,38 @@
<br> <br>
<div class="jive-contentBoxHeader"><fmt:message key="search.props.edit.form.search_scope" /></div>
<div class="jive-contentBox">
<p>
<fmt:message key="search.props.edit.form.search_scope_directions" />
</p>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td width="1%">
<input type="radio" name="groupOnly" value="false" id="rb-grouponly-01"
<%= ((!groupOnly) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb-grouponly-01"><b><fmt:message key="search.props.edit.form.search_scope_anyone" /></b></label> - <fmt:message key="search.props.edit.form.search_scope_anyone_details" />
</td>
</tr>
<tr>
<td width="1%">
<input type="radio" name="groupOnly" value="true" id="rb-grouponly-02"
<%= ((groupOnly) ? "checked" : "") %>>
</td>
<td width="99%">
<label for="rb-grouponly-02"><b><fmt:message key="search.props.edit.form.search_scope_groups" /></b></label> - <fmt:message key="search.props.edit.form.search_scope_groups_details" />
</td>
</tr>
</tbody>
</table>
</div>
<br>
<input type="submit" value="<fmt:message key="search.props.edit.form.save_properties" />"> <input type="submit" value="<fmt:message key="search.props.edit.form.save_properties" />">
</form> </form>
......
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