/** * $RCSfile$ * $Revision$ * $Date$ * * Copyright (C) 2004 Jive Software. All rights reserved. * * This software is published under the terms of the GNU Public License (GPL), * a copy of which is included in this distribution. */ package org.jivesoftware.messenger.disco; import org.jivesoftware.messenger.container.TrackInfo; import org.jivesoftware.messenger.*; import org.jivesoftware.messenger.auth.UnauthorizedException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.xml.stream.XMLStreamException; import org.dom4j.DocumentHelper; import org.dom4j.Element; /** * IQDiscoItemsHandler is responsible for handling disco#items requests. This class holds a map with * the main entities and the associated DiscoItemsProvider. We are considering the host of the * recipient JIDs as main entities. It's the DiscoItemsProvider responsibility to provide the items * associated with the JID's name together with any possible requested node.<p> * <p/> * For example, let's have in the entities map the following entries: "localhost" and * "conference.localhost". Associated with each entry we have different DiscoItemsProvider. Now we * receive a disco#items request for the following JID: "room@conference.localhost" which is a disco * request for a MUC room. So IQDiscoItemsHandler will look for the DiscoItemsProvider associated * with the JID's host which in this case is "conference.localhost". Once we have located the * provider we will delegate to the provider the responsibility to provide the items specific to * the JID's name which in this case is "room". Depending on the implementation, the items could be * the list of existing occupants if that information is publicly available. Finally, after we have * collected all the items provided by the provider we will add them to the reply. On the other * hand, if no provider was found or the provider has no information for the requested name/node * then a not-found error will be returned.<p> * <p/> * Publishing of client items is still not supported. * * @author Gaston Dombiak */ public class IQDiscoItemsHandler extends IQDiscoHandler implements ServerFeaturesProvider { private HashMap entities = new HashMap(); private List serverItems = new ArrayList(); private IQHandlerInfo info; public IQDiscoInfoHandler infoHandler; public IQDiscoItemsHandler() { super("XMPP Disco Items Handler"); info = new IQHandlerInfo("query", "http://jabber.org/protocol/disco#items"); } public IQHandlerInfo getInfo() { return info; } public IQ handleIQ(IQ packet) throws UnauthorizedException, XMLStreamException { // TODO Let configure an authorization policy (ACL?). Currently anyone can discover items. // Create a copy of the sent pack that will be used as the reply // we only need to add the requested items to the reply if any otherwise add // a not found error IQ reply = (IQ)packet.createDeepCopy(); reply.setType(IQ.RESULT); reply.setRecipient(packet.getSender()); reply.setSender(packet.getRecipient()); // TODO Implement publishing client items if (IQ.SET == packet.getType()) { reply.setError(XMPPError.Code.NOT_IMPLEMENTED); return reply; } // Look for a DiscoItemsProvider associated with the requested entity. // We consider the host of the recipient JID of the packet as the entity. It's the // DiscoItemsProvider responsibility to provide the items associated with the JID's name // together with any possible requested node. DiscoItemsProvider itemsProvider = getProvider(packet.getRecipient().getHost()); if (itemsProvider != null) { // Get the JID's name String name = packet.getRecipient().getName(); if (name == null || name.trim().length() == 0) { name = null; } // Get the requested node XMPPFragment iq = packet.getChildFragment(); MetaDataFragment metaData = MetaDataFragment.convertToMetaData(iq); String node = metaData.getProperty("query:node"); // Check if we have items associated with the requested name and node Iterator itemsItr = itemsProvider.getItems(name, node, packet.getSender()); if (itemsItr != null) { Element queryElement = ((XMPPDOMFragment)reply.getChildFragment()).getRootElement(); // Add to the reply all the items provided by the DiscoItemsProvider Element item; while (itemsItr.hasNext()) { item = (Element)itemsItr.next(); queryElement.add((Element)item.clone()); } ; } else { // If the DiscoItemsProvider has no items for the requested name and node // then answer a not found error reply.setError(XMPPError.Code.NOT_FOUND); } } else { // If we didn't find a DiscoItemsProvider then answer a not found error reply.setError(XMPPError.Code.NOT_FOUND); } return reply; } /** * Returns the DiscoItemsProvider responsible for providing the items related to a given entity * or null if none was found. * * @param name the name of the identity. * @return the DiscoItemsProvider responsible for providing the items related to a given entity * or null if none was found. */ private DiscoItemsProvider getProvider(String name) { return (DiscoItemsProvider)entities.get(name); } /** * Sets that a given DiscoItemsProvider will provide the items related to a given entity. This * message must be used when new modules (e.g. MUC) are implemented and need to provide * the items related to them. * * @param name the name of the entity. * @param provider the DiscoItemsProvider that will provide the entity's items. */ protected void setProvider(String name, DiscoItemsProvider provider) { entities.put(name, provider); } /** * Removes the DiscoItemsProvider related to a given entity. * * @param name the name of the entity. */ protected void removeProvider(String name) { entities.remove(name); } /** * Adds the items provided by the new service that implements the ServerItemsProvider * interface. This information will be used whenever a disco for items is made against * the server (i.e. the packet's target is the server). * Example of item is: <item jid='conference.localhost' name='Public chatrooms'/> * * @param provider the ServerItemsProvider that provides new server items. */ public void addServerItemsProvider(ServerItemsProvider provider) { DiscoServerItem discoItem; for (Iterator it = provider.getItems(); it.hasNext();) { discoItem = (DiscoServerItem)it.next(); // Create a new element based on the provided DiscoItem Element element = DocumentHelper.createElement("item"); element.addAttribute("jid", discoItem.getJID()); element.addAttribute("node", discoItem.getNode()); element.addAttribute("name", discoItem.getName()); // Add the element to the list of items related to the server serverItems.add(element); // Add the new item as a valid entity that could receive info and items disco requests String host = parseHost(discoItem.getJID()); infoHandler.setProvider(host, discoItem.getDiscoInfoProvider()); setProvider(host, discoItem.getDiscoItemsProvider()); } } /** * Removes the items provided by the service that implements the ServerItemsProvider * interface which is being removed. Example of item is: * <item jid='conference.localhost' name='Public chatrooms'/> * * @param provider the ServerItemsProvider that was providing server items. */ public void removeServerItemsProvider(ServerItemsProvider provider) { DiscoItem discoItem; for (Iterator it = provider.getItems(); it.hasNext();) { discoItem = (DiscoItem)it.next(); // Locate the element that represents the DiscoItem to remove Element element = null; boolean found = false; for (Iterator itemsItr = serverItems.iterator(); !found && it.hasNext();) { element = (Element)itemsItr.next(); if (discoItem.getJID().equals(element.attributeValue("jid"))) { found = true; break; } } // If the element was found then remove it from the items provided by the server if (found) { serverItems.remove(element); } // Remove the item as a valid entity that could receive info and items disco requests String host = parseHost(discoItem.getJID()); infoHandler.removeProvider(host); removeProvider(host); } } protected TrackInfo getTrackInfo() { TrackInfo trackInfo = super.getTrackInfo(); // Track the implementors of ServerItemsProvider so that we can collect the items // provided by the server trackInfo.getTrackerClasses().put(ServerItemsProvider.class, "ServerItemsProvider"); trackInfo.getTrackerClasses().put(IQDiscoInfoHandler.class, "infoHandler"); return trackInfo; } public void serviceAdded(Object service) { if (service instanceof XMPPServer) { setProvider(((XMPPServer)service).getServerInfo().getName(), getServerItemsProvider()); } } public Iterator getFeatures() { ArrayList features = new ArrayList(); features.add("http://jabber.org/protocol/disco#items"); // TODO Comment out this line when publishing of client items is implemented //features.add("http://jabber.org/protocol/disco#publish"); return features.iterator(); } private DiscoItemsProvider getServerItemsProvider() { DiscoItemsProvider discoItemsProvider = new DiscoItemsProvider() { public Iterator getItems(String name, String node, XMPPAddress senderJID) throws UnauthorizedException { return serverItems.iterator(); } }; return discoItemsProvider; } /** * Returns the server portion of a XMPP address. For example, for the * address "matt@jivesoftware.com/Smack", "jivesoftware.com" would be returned. * If no server is present in the address, the empty string will be returned. * * @param XMPPAddress the XMPP address. * @return the server portion of the XMPP address. */ private static String parseHost(String XMPPAddress) { if (XMPPAddress == null) { return null; } int atIndex = XMPPAddress.indexOf("@"); // If the String ends with '@', return the empty string. if (atIndex + 1 > XMPPAddress.length()) { return ""; } /*if (atIndex < 0) { atIndex = 0; }*/ int slashIndex = XMPPAddress.indexOf("/"); if (slashIndex > 0) { return XMPPAddress.substring(atIndex + 1, slashIndex); } else { return XMPPAddress.substring(atIndex + 1); } } }