/** * $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.forms.XDataForm; 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; /** * IQDiscoInfoHandler is responsible for handling disco#info requests. This class holds a map with * the main entities and the associated DiscoInfoProvider. We are considering the host of the * recipient JIDs as main entities. It's the DiscoInfoProvider responsibility to provide information * about 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 DiscoInfoProviders. Now we * receive a disco#info request for the following JID: "room@conference.localhost" which is a disco * request for a MUC room. So IQDiscoInfoHandler will look for the DiscoInfoProvider 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 info specific to * the JID's name which in this case is "room". Among the information that a room could provide we * could find its identity and the features it supports (e.g. 'muc_passwordprotected', * 'muc_unmoderated', etc.). Finally, after we have collected all the information provided by the * provider we will add it 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. * * @author Gaston Dombiak */ public class IQDiscoInfoHandler extends IQDiscoHandler { private HashMap entities = new HashMap(); private List serverFeatures = new ArrayList(); private IQHandlerInfo info; public IQDiscoInfoHandler() { super("XMPP Disco Info Handler"); info = new IQHandlerInfo("query", "http://jabber.org/protocol/disco#info"); serverFeatures.add("http://jabber.org/protocol/disco#info"); } public IQHandlerInfo getInfo() { return info; } public IQ handleIQ(IQ packet) throws UnauthorizedException, XMLStreamException { // TODO Let configure an authorization policy (ACL?). Currently anyone can discover info. // Create a copy of the sent pack that will be used as the reply // we only need to add the requested info 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()); // Look for a DiscoInfoProvider associated with the requested entity. // We consider the host of the recipient JID of the packet as the entity. It's the // DiscoInfoProvider responsibility to provide information about the JID's name together // with any possible requested node. DiscoInfoProvider infoProvider = getProvider(packet.getRecipient().getHost()); if (infoProvider != 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 information about the requested name and node if (infoProvider.hasInfo(name, node, packet.getSender())) { Element queryElement = ((XMPPDOMFragment)reply.getChildFragment()).getRootElement(); // Add to the reply all the identities provided by the DiscoInfoProvider Element identity; Iterator identities = infoProvider.getIdentities(name, node, packet.getSender()); while (identities.hasNext()) { identity = (Element)identities.next(); queryElement.add((Element)identity.clone()); } // Add to the reply all the features provided by the DiscoInfoProvider Element featureElement; Iterator features = infoProvider.getFeatures(name, node, packet.getSender()); while (features.hasNext()) { featureElement = DocumentHelper.createElement("feature"); featureElement.addAttribute("var", (String)features.next()); queryElement.add(featureElement); } // Add to the reply the extended info (XDataForm) provided by the DiscoInfoProvider XDataForm dataForm = infoProvider.getExtendedInfo(name, node, packet.getSender()); if (dataForm != null) { queryElement.add(dataForm.asXMLElement()); } } else { // If the DiscoInfoProvider has no information 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 DiscoInfoProvider then answer a not found error reply.setError(XMPPError.Code.NOT_FOUND); } return reply; } /** * Returns the DiscoInfoProvider responsible for providing information about a given entity or * null if none was found. * * @param name the name of the identity. * @return the DiscoInfoProvider responsible for providing information about a given entity or * null if none was found. */ private DiscoInfoProvider getProvider(String name) { return (DiscoInfoProvider)entities.get(name); } /** * Sets that a given DiscoInfoProvider will provide information about a given entity. This * message must be used when new modules (e.g. MUC) are implemented and need to provide * information about them. * * @param name the name of the entity. * @param provider the DiscoInfoProvider that will provide the entity's information. */ protected void setProvider(String name, DiscoInfoProvider provider) { entities.put(name, provider); } /** * Removes the DiscoInfoProvider related to a given entity. * * @param name the name of the entity. */ protected void removeProvider(String name) { entities.remove(name); } /** * Adds the features provided by the new service that implements the ServerFeaturesProvider * interface. This information will be used whenever a disco for information is made against * the server (i.e. the packet's target is the server). * Example of features are: jabber:iq:agents, jabber:iq:time, etc. * * @param provider the ServerFeaturesProvider that provides new server features. */ public void addServerFeaturesProvider(ServerFeaturesProvider provider) { for (Iterator it = provider.getFeatures(); it.hasNext();) { serverFeatures.add(it.next()); } } /** * Removes the features provided by the service that implements the ServerFeaturesProvider * interface which is being removed. Example of features are: jabber:iq:agents, * jabber:iq:time, etc. * * @param provider the ServerFeaturesProvider that was providing server features. */ public void removeServerFeaturesProvider(ServerFeaturesProvider provider) { for (Iterator it = provider.getFeatures(); it.hasNext();) { serverFeatures.remove(it.next()); } } protected TrackInfo getTrackInfo() { TrackInfo trackInfo = super.getTrackInfo(); // Track the implementors of ServerFeaturesProvider so that we can collect the features // provided by the server trackInfo.getTrackerClasses().put(ServerFeaturesProvider.class, "ServerFeaturesProvider"); return trackInfo; } public void serviceAdded(Object service) { if (service instanceof XMPPServer) { setProvider(((XMPPServer)service).getServerInfo().getName(), getServerInfoProvider()); } } /** * Returns the DiscoInfoProvider responsible for providing information at the server level. This * means that this DiscoInfoProvider will provide information whenever a disco request whose * recipient JID is the server (e.g. localhost) is made. * * @return the DiscoInfoProvider responsible for providing information at the server level. */ private DiscoInfoProvider getServerInfoProvider() { DiscoInfoProvider discoInfoProvider = new DiscoInfoProvider() { ArrayList identities = new ArrayList(); ArrayList features = new ArrayList(); public Iterator getIdentities(String name, String node, XMPPAddress senderJID) { synchronized (identities) { if (identities.isEmpty()) { Element identity = DocumentHelper.createElement("identity"); identity.addAttribute("category", "services"); identity.addAttribute("name", "Messenger Server"); identity.addAttribute("type", "jabber"); identities.add(identity); } } return identities.iterator(); } public Iterator getFeatures(String name, String node, XMPPAddress senderJID) { return serverFeatures.iterator(); } public boolean hasInfo(String name, String node, XMPPAddress senderJID) throws UnauthorizedException { return name == null && node == null; } public XDataForm getExtendedInfo(String name, String node, XMPPAddress senderJID) { return null; } }; return discoInfoProvider; } }