/** * $RCSfile$ * $Revision$ * $Date$ * * Copyright 2003-2007 Jive Software. * * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx; import org.jivesoftware.smack.*; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smackx.entitycaps.EntityCapsManager; import org.jivesoftware.smackx.packet.DiscoverInfo; import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; import org.jivesoftware.smackx.packet.DiscoverItems; import org.jivesoftware.smackx.packet.DataForm; import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Manages discovery of services in XMPP entities. This class provides: *
* * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the * NodeInformationProvider will provide information about the rooms where the user has joined. * * @param node the node that contains items associated with an entity not addressable as a JID. * @return the NodeInformationProvider responsible for providing information related * to a given node. */ private NodeInformationProvider getNodeInformationProvider(String node) { if (node == null) { return null; } return nodeInformationProviders.get(node); } /** * Sets the NodeInformationProvider responsible for providing information * (ie items) related to a given node. Every time this client receives a disco request * regarding the items of a given node, the provider associated to that node will be the * responsible for providing the requested information.
*
* In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
* NodeInformationProvider will provide information about the rooms where the user has joined.
*
* @param node the node whose items will be provided by the NodeInformationProvider.
* @param listener the NodeInformationProvider responsible for providing items related
* to the node.
*/
public void setNodeInformationProvider(String node, NodeInformationProvider listener) {
nodeInformationProviders.put(node, listener);
}
/**
* Removes the NodeInformationProvider responsible for providing information
* (ie items) related to a given node. This means that no more information will be
* available for the specified node.
*
* In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
* NodeInformationProvider will provide information about the rooms where the user has joined.
*
* @param node the node to remove the associated NodeInformationProvider.
*/
public void removeNodeInformationProvider(String node) {
nodeInformationProviders.remove(node);
}
/**
* Returns the supported features by this XMPP entity.
*
* @return an Iterator on the supported features by this XMPP entity.
*/
public Iterator
*
* Since no packet is actually sent to the server it is safe to perform this operation
* before logging to the server. In fact, you may want to configure the supported features
* before logging to the server so that the information is already available if it is required
* upon login.
*
* @param feature the feature to register as supported.
*/
public void addFeature(String feature) {
synchronized (features) {
features.add(feature);
renewEntityCapsVersion();
}
}
/**
* Removes the specified feature from the supported features by this XMPP entity.
*
* Since no packet is actually sent to the server it is safe to perform this operation
* before logging to the server.
*
* @param feature the feature to remove from the supported features.
*/
public void removeFeature(String feature) {
synchronized (features) {
features.remove(feature);
renewEntityCapsVersion();
}
}
/**
* Returns true if the specified feature is registered in the ServiceDiscoveryManager.
*
* @param feature the feature to look for.
* @return a boolean indicating if the specified featured is registered or not.
*/
public boolean includesFeature(String feature) {
synchronized (features) {
return features.contains(feature);
}
}
/**
* Registers extended discovery information of this XMPP entity. When this
* client is queried for its information this data form will be returned as
* specified by XEP-0128.
*
*
* Since no packet is actually sent to the server it is safe to perform this
* operation before logging to the server. In fact, you may want to
* configure the extended info before logging to the server so that the
* information is already available if it is required upon login.
*
* @param info
* the data form that contains the extend service discovery
* information.
*/
public void setExtendedInfo(DataForm info) {
extendedInfo = info;
renewEntityCapsVersion();
}
/**
* Returns the data form that is set as extended information for this Service Discovery instance (XEP-0128)
*
* @see XEP-128: Service Discovery Extensions
* @return
*/
public DataForm getExtendedInfo() {
return extendedInfo;
}
/**
* Returns the data form as List of PacketExtensions, or null if no data form is set.
* This representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider)
*
* @return
*/
public List
*
* Since no packet is actually sent to the server it is safe to perform this
* operation before logging to the server.
*/
public void removeExtendedInfo() {
extendedInfo = null;
renewEntityCapsVersion();
}
/**
* Returns the discovered information of a given XMPP entity addressed by its JID.
* Use null as entityID to query the server
*
* @param entityID the address of the XMPP entity or null.
* @return the discovered information.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
if (entityID == null)
return discoverInfo(null, null);
// Check if the have it cached in the Entity Capabilities Manager
DiscoverInfo info = EntityCapsManager.getDiscoverInfoByUser(entityID);
if (info != null) {
// We were able to retrieve the information from Entity Caps and
// avoided a disco request, hurray!
return info;
}
// Try to get the newest node#version if it's known, otherwise null is
// returned
EntityCapsManager.NodeVerHash nvh = EntityCapsManager.getNodeVerHashByJid(entityID);
// Discover by requesting the information from the remote entity
// Note that wee need to use NodeVer as argument for Node if it exists
info = discoverInfo(entityID, nvh != null ? nvh.getNodeVer() : null);
// If the node version is known, store the new entry.
if (nvh != null) {
if (EntityCapsManager.verifyDiscoverInfoVersion(nvh.getVer(), nvh.getHash(), info))
EntityCapsManager.addDiscoverInfoByNode(nvh.getNodeVer(), info);
}
return info;
}
/**
* Returns the discovered information of a given XMPP entity addressed by its JID and
* note attribute. Use this message only when trying to query information which is not
* directly addressable.
*
* @see XEP-30 Basic Protocol
* @see XEP-30 Info Nodes
*
* @param entityID the address of the XMPP entity.
* @param node the optional attribute that supplements the 'jid' attribute.
* @return the discovered information.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException {
Connection connection = ServiceDiscoveryManager.this.connection.get();
if (connection == null) throw new XMPPException("Connection instance already gc'ed");
// Discover the entity's info
DiscoverInfo disco = new DiscoverInfo();
disco.setType(IQ.Type.GET);
disco.setTo(entityID);
disco.setNode(node);
// Create a packet collector to listen for a response.
PacketCollector collector =
connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
connection.sendPacket(disco);
// Wait up to 5 seconds for a result.
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from the server.");
}
if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
return (DiscoverInfo) result;
}
/**
* Returns the discovered items of a given XMPP entity addressed by its JID.
*
* @param entityID the address of the XMPP entity.
* @return the discovered information.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverItems discoverItems(String entityID) throws XMPPException {
return discoverItems(entityID, null);
}
/**
* Returns the discovered items of a given XMPP entity addressed by its JID and
* note attribute. Use this message only when trying to query information which is not
* directly addressable.
*
* @param entityID the address of the XMPP entity.
* @param node the optional attribute that supplements the 'jid' attribute.
* @return the discovered items.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverItems discoverItems(String entityID, String node) throws XMPPException {
Connection connection = ServiceDiscoveryManager.this.connection.get();
if (connection == null) throw new XMPPException("Connection instance already gc'ed");
// Discover the entity's items
DiscoverItems disco = new DiscoverItems();
disco.setType(IQ.Type.GET);
disco.setTo(entityID);
disco.setNode(node);
// Create a packet collector to listen for a response.
PacketCollector collector =
connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
connection.sendPacket(disco);
// Wait up to 5 seconds for a result.
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from the server.");
}
if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
return (DiscoverItems) result;
}
/**
* Returns true if the server supports publishing of items. A client may wish to publish items
* to the server so that the server can provide items associated to the client. These items will
* be returned by the server whenever the server receives a disco request targeted to the bare
* address of the client (i.e. user@host.com).
*
* @param entityID the address of the XMPP entity.
* @return true if the server supports publishing of items.
* @throws XMPPException if the operation failed for some reason.
*/
public boolean canPublishItems(String entityID) throws XMPPException {
DiscoverInfo info = discoverInfo(entityID);
return canPublishItems(info);
}
/**
* Returns true if the server supports publishing of items. A client may wish to publish items
* to the server so that the server can provide items associated to the client. These items will
* be returned by the server whenever the server receives a disco request targeted to the bare
* address of the client (i.e. user@host.com).
*
* @param DiscoverInfo the discover info packet to check.
* @return true if the server supports publishing of items.
*/
public static boolean canPublishItems(DiscoverInfo info) {
return info.containsFeature("http://jabber.org/protocol/disco#publish");
}
/**
* Publishes new items to a parent entity. The item elements to publish MUST have at least
* a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
* specifies the action being taken for that item. Possible action values are: "update" and
* "remove".
*
* @param entityID the address of the XMPP entity.
* @param discoverItems the DiscoveryItems to publish.
* @throws XMPPException if the operation failed for some reason.
*/
public void publishItems(String entityID, DiscoverItems discoverItems)
throws XMPPException {
publishItems(entityID, null, discoverItems);
}
/**
* Publishes new items to a parent entity and node. The item elements to publish MUST have at
* least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
* specifies the action being taken for that item. Possible action values are: "update" and
* "remove".
*
* @param entityID the address of the XMPP entity.
* @param node the attribute that supplements the 'jid' attribute.
* @param discoverItems the DiscoveryItems to publish.
* @throws XMPPException if the operation failed for some reason.
*/
public void publishItems(String entityID, String node, DiscoverItems discoverItems)
throws XMPPException {
Connection connection = ServiceDiscoveryManager.this.connection.get();
if (connection == null) throw new XMPPException("Connection instance already gc'ed");
discoverItems.setType(IQ.Type.SET);
discoverItems.setTo(entityID);
discoverItems.setNode(node);
// Create a packet collector to listen for a response.
PacketCollector collector =
connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID()));
connection.sendPacket(discoverItems);
// Wait up to 5 seconds for a result.
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from the server.");
}
if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
}
/**
* Entity Capabilities
*/
/**
* Loads the ServiceDiscoveryManager with an EntityCapsManger
* that speeds up certain lookups
* @param manager
*/
public void setEntityCapsManager(EntityCapsManager manager) {
capsManager = manager;
}
/**
* Updates the Entity Capabilities Verification String
* if EntityCaps is enabled
*/
private void renewEntityCapsVersion() {
if (capsManager != null && capsManager.entityCapsEnabled())
capsManager.updateLocalEntityCaps();
}
}