KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > smackx > ServiceDiscoveryManager


1 /**
2  * $RCSfile$
3  * $Revision: 2480 $
4  * $Date: 2005-04-04 16:50:09 -0300 (Mon, 04 Apr 2005) $
5  *
6  * Copyright 2003-2004 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */

20
21 package org.jivesoftware.smackx;
22
23 import java.util.*;
24
25 import org.jivesoftware.smack.*;
26 import org.jivesoftware.smack.filter.*;
27 import org.jivesoftware.smack.packet.*;
28 import org.jivesoftware.smackx.packet.*;
29
30 /**
31  * Manages discovery of services in XMPP entities. This class provides:
32  * <ol>
33  * <li>A registry of supported features in this XMPP entity.
34  * <li>Automatic response when this XMPP entity is queried for information.
35  * <li>Ability to discover items and information of remote XMPP entities.
36  * <li>Ability to publish publicly available items.
37  * </ol>
38  *
39  * @author Gaston Dombiak
40  */

41 public class ServiceDiscoveryManager {
42
43     private static String JavaDoc identityName = "Smack";
44     private static String JavaDoc identityType = "pc";
45
46     private static Map instances = new Hashtable();
47
48     private XMPPConnection connection;
49     private List features = new ArrayList();
50     private Map nodeInformationProviders = new Hashtable();
51
52     // Create a new ServiceDiscoveryManager on every established connection
53
static {
54         XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
55             public void connectionEstablished(XMPPConnection connection) {
56                 new ServiceDiscoveryManager(connection);
57             }
58         });
59     }
60
61     /**
62      * Creates a new ServiceDiscoveryManager for a given XMPPConnection. This means that the
63      * service manager will respond to any service discovery request that the connection may
64      * receive.
65      *
66      * @param connection the connection to which a ServiceDiscoveryManager is going to be created.
67      */

68     public ServiceDiscoveryManager(XMPPConnection connection) {
69         this.connection = connection;
70         init();
71     }
72
73     /**
74      * Returns the ServiceDiscoveryManager instance associated with a given XMPPConnection.
75      *
76      * @param connection the connection used to look for the proper ServiceDiscoveryManager.
77      * @return the ServiceDiscoveryManager associated with a given XMPPConnection.
78      */

79     public static ServiceDiscoveryManager getInstanceFor(XMPPConnection connection) {
80         return (ServiceDiscoveryManager) instances.get(connection);
81     }
82
83     /**
84      * Returns the name of the client that will be returned when asked for the client identity
85      * in a disco request. The name could be any value you need to identity this client.
86      *
87      * @return the name of the client that will be returned when asked for the client identity
88      * in a disco request.
89      */

90     public static String JavaDoc getIdentityName() {
91         return identityName;
92     }
93
94     /**
95      * Sets the name of the client that will be returned when asked for the client identity
96      * in a disco request. The name could be any value you need to identity this client.
97      *
98      * @param name the name of the client that will be returned when asked for the client identity
99      * in a disco request.
100      */

101     public static void setIdentityName(String JavaDoc name) {
102         identityName = name;
103     }
104
105     /**
106      * Returns the type of client that will be returned when asked for the client identity in a
107      * disco request. The valid types are defined by the category client. Follow this link to learn
108      * the possible types: <a HREF="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
109      *
110      * @return the type of client that will be returned when asked for the client identity in a
111      * disco request.
112      */

113     public static String JavaDoc getIdentityType() {
114         return identityType;
115     }
116
117     /**
118      * Sets the type of client that will be returned when asked for the client identity in a
119      * disco request. The valid types are defined by the category client. Follow this link to learn
120      * the possible types: <a HREF="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
121      *
122      * @param type the type of client that will be returned when asked for the client identity in a
123      * disco request.
124      */

125     public static void setIdentityType(String JavaDoc type) {
126         identityType = type;
127     }
128
129     /**
130      * Initializes the packet listeners of the connection that will answer to any
131      * service discovery request.
132      */

133     private void init() {
134         // Register the new instance and associate it with the connection
135
instances.put(connection, this);
136         // Add a listener to the connection that removes the registered instance when
137
// the connection is closed
138
connection.addConnectionListener(new ConnectionListener() {
139             public void connectionClosed() {
140                 // Unregister this instance since the connection has been closed
141
instances.remove(connection);
142             }
143
144             public void connectionClosedOnError(Exception JavaDoc e) {
145                 // Unregister this instance since the connection has been closed
146
instances.remove(connection);
147             }
148         });
149
150         // Listen for disco#items requests and answer with an empty result
151
PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
152         PacketListener packetListener = new PacketListener() {
153             public void processPacket(Packet packet) {
154                 DiscoverItems discoverItems = (DiscoverItems) packet;
155                 // Send back the items defined in the client if the request is of type GET
156
if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
157                     DiscoverItems response = new DiscoverItems();
158                     response.setType(IQ.Type.RESULT);
159                     response.setTo(discoverItems.getFrom());
160                     response.setPacketID(discoverItems.getPacketID());
161
162                     // Add the defined items related to the requested node. Look for
163
// the NodeInformationProvider associated with the requested node.
164
if (getNodeInformationProvider(discoverItems.getNode()) != null) {
165                         Iterator items =
166                             getNodeInformationProvider(discoverItems.getNode()).getNodeItems();
167                         while (items.hasNext()) {
168                             response.addItem((DiscoverItems.Item) items.next());
169                         }
170                     }
171                     connection.sendPacket(response);
172                 }
173             }
174         };
175         connection.addPacketListener(packetListener, packetFilter);
176
177         // Listen for disco#info requests and answer the client's supported features
178
// To add a new feature as supported use the #addFeature message
179
packetFilter = new PacketTypeFilter(DiscoverInfo.class);
180         packetListener = new PacketListener() {
181             public void processPacket(Packet packet) {
182                 DiscoverInfo discoverInfo = (DiscoverInfo) packet;
183                 // Answer the client's supported features if the request is of the GET type
184
if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
185                     DiscoverInfo response = new DiscoverInfo();
186                     response.setType(IQ.Type.RESULT);
187                     response.setTo(discoverInfo.getFrom());
188                     response.setPacketID(discoverInfo.getPacketID());
189                     // Add the client's identity and features only if "node" is null
190
if (discoverInfo.getNode() == null) {
191                         // Set this client identity
192
DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client",
193                                 getIdentityName());
194                         identity.setType(getIdentityType());
195                         response.addIdentity(identity);
196                         // Add the registered features to the response
197
synchronized (features) {
198                             for (Iterator it = getFeatures(); it.hasNext();) {
199                                 response.addFeature((String JavaDoc) it.next());
200                             }
201                         }
202                     }
203                     else {
204                         // Return an <item-not-found/> error since a client doesn't have nodes
205
response.setType(IQ.Type.ERROR);
206                         response.setError(new XMPPError(404, "item-not-found"));
207                     }
208                     connection.sendPacket(response);
209                 }
210             }
211         };
212         connection.addPacketListener(packetListener, packetFilter);
213     }
214
215     /**
216      * Returns the NodeInformationProvider responsible for providing information
217      * (ie items) related to a given node or <tt>null</null> if none.<p>
218      *
219      * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
220      * NodeInformationProvider will provide information about the rooms where the user has joined.
221      *
222      * @param node the node that contains items associated with an entity not addressable as a JID.
223      * @return the NodeInformationProvider responsible for providing information related
224      * to a given node.
225      */

226     private NodeInformationProvider getNodeInformationProvider(String JavaDoc node) {
227         if (node == null) {
228             return null;
229         }
230         return (NodeInformationProvider) nodeInformationProviders.get(node);
231     }
232
233     /**
234      * Sets the NodeInformationProvider responsible for providing information
235      * (ie items) related to a given node. Every time this client receives a disco request
236      * regarding the items of a given node, the provider associated to that node will be the
237      * responsible for providing the requested information.<p>
238      *
239      * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
240      * NodeInformationProvider will provide information about the rooms where the user has joined.
241      *
242      * @param node the node whose items will be provided by the NodeInformationProvider.
243      * @param listener the NodeInformationProvider responsible for providing items related
244      * to the node.
245      */

246     public void setNodeInformationProvider(String JavaDoc node, NodeInformationProvider listener) {
247         nodeInformationProviders.put(node, listener);
248     }
249
250     /**
251      * Removes the NodeInformationProvider responsible for providing information
252      * (ie items) related to a given node. This means that no more information will be
253      * available for the specified node.
254      *
255      * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
256      * NodeInformationProvider will provide information about the rooms where the user has joined.
257      *
258      * @param node the node to remove the associated NodeInformationProvider.
259      */

260     public void removeNodeInformationProvider(String JavaDoc node) {
261         nodeInformationProviders.remove(node);
262     }
263
264     /**
265      * Returns the supported features by this XMPP entity.
266      *
267      * @return an Iterator on the supported features by this XMPP entity.
268      */

269     public Iterator getFeatures() {
270         synchronized (features) {
271             return Collections.unmodifiableList(new ArrayList(features)).iterator();
272         }
273     }
274
275     /**
276      * Registers that a new feature is supported by this XMPP entity. When this client is
277      * queried for its information the registered features will be answered.<p>
278      *
279      * Since no packet is actually sent to the server it is safe to perform this operation
280      * before logging to the server. In fact, you may want to configure the supported features
281      * before logging to the server so that the information is already available if it is required
282      * upon login.
283      *
284      * @param feature the feature to register as supported.
285      */

286     public void addFeature(String JavaDoc feature) {
287         synchronized (features) {
288             features.add(feature);
289         }
290     }
291
292     /**
293      * Removes the specified feature from the supported features by this XMPP entity.<p>
294      *
295      * Since no packet is actually sent to the server it is safe to perform this operation
296      * before logging to the server.
297      *
298      * @param feature the feature to remove from the supported features.
299      */

300     public void removeFeature(String JavaDoc feature) {
301         synchronized (features) {
302             features.remove(feature);
303         }
304     }
305
306     /**
307      * Returns true if the specified feature is registered in the ServiceDiscoveryManager.
308      *
309      * @param feature the feature to look for.
310      * @return a boolean indicating if the specified featured is registered or not.
311      */

312     public boolean includesFeature(String JavaDoc feature) {
313         synchronized (features) {
314             return features.contains(feature);
315         }
316     }
317
318     /**
319      * Returns the discovered information of a given XMPP entity addressed by its JID.
320      *
321      * @param entityID the address of the XMPP entity.
322      * @return the discovered information.
323      * @throws XMPPException if the operation failed for some reason.
324      */

325     public DiscoverInfo discoverInfo(String JavaDoc entityID) throws XMPPException {
326         return discoverInfo(entityID, null);
327     }
328
329     /**
330      * Returns the discovered information of a given XMPP entity addressed by its JID and
331      * note attribute. Use this message only when trying to query information which is not
332      * directly addressable.
333      *
334      * @param entityID the address of the XMPP entity.
335      * @param node the attribute that supplements the 'jid' attribute.
336      * @return the discovered information.
337      * @throws XMPPException if the operation failed for some reason.
338      */

339     public DiscoverInfo discoverInfo(String JavaDoc entityID, String JavaDoc node) throws XMPPException {
340         // Discover the entity's info
341
DiscoverInfo disco = new DiscoverInfo();
342         disco.setType(IQ.Type.GET);
343         disco.setTo(entityID);
344         disco.setNode(node);
345
346         // Create a packet collector to listen for a response.
347
PacketCollector collector =
348             connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
349
350         connection.sendPacket(disco);
351
352         // Wait up to 5 seconds for a result.
353
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
354         // Stop queuing results
355
collector.cancel();
356         if (result == null) {
357             throw new XMPPException("No response from the server.");
358         }
359         if (result.getType() == IQ.Type.ERROR) {
360             throw new XMPPException(result.getError());
361         }
362         return (DiscoverInfo) result;
363     }
364
365     /**
366      * Returns the discovered items of a given XMPP entity addressed by its JID.
367      *
368      * @param entityID the address of the XMPP entity.
369      * @return the discovered information.
370      * @throws XMPPException if the operation failed for some reason.
371      */

372     public DiscoverItems discoverItems(String JavaDoc entityID) throws XMPPException {
373         return discoverItems(entityID, null);
374     }
375
376     /**
377      * Returns the discovered items of a given XMPP entity addressed by its JID and
378      * note attribute. Use this message only when trying to query information which is not
379      * directly addressable.
380      *
381      * @param entityID the address of the XMPP entity.
382      * @param node the attribute that supplements the 'jid' attribute.
383      * @return the discovered items.
384      * @throws XMPPException if the operation failed for some reason.
385      */

386     public DiscoverItems discoverItems(String JavaDoc entityID, String JavaDoc node) throws XMPPException {
387         // Discover the entity's items
388
DiscoverItems disco = new DiscoverItems();
389         disco.setType(IQ.Type.GET);
390         disco.setTo(entityID);
391         disco.setNode(node);
392
393         // Create a packet collector to listen for a response.
394
PacketCollector collector =
395             connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
396
397         connection.sendPacket(disco);
398
399         // Wait up to 5 seconds for a result.
400
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
401         // Stop queuing results
402
collector.cancel();
403         if (result == null) {
404             throw new XMPPException("No response from the server.");
405         }
406         if (result.getType() == IQ.Type.ERROR) {
407             throw new XMPPException(result.getError());
408         }
409         return (DiscoverItems) result;
410     }
411
412     /**
413      * Returns true if the server supports publishing of items. A client may wish to publish items
414      * to the server so that the server can provide items associated to the client. These items will
415      * be returned by the server whenever the server receives a disco request targeted to the bare
416      * address of the client (i.e. user@host.com).
417      *
418      * @param entityID the address of the XMPP entity.
419      * @return true if the server supports publishing of items.
420      * @throws XMPPException if the operation failed for some reason.
421      */

422     public boolean canPublishItems(String JavaDoc entityID) throws XMPPException {
423         DiscoverInfo info = discoverInfo(entityID);
424         return info.containsFeature("http://jabber.org/protocol/disco#publish");
425     }
426
427     /**
428      * Publishes new items to a parent entity. The item elements to publish MUST have at least
429      * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
430      * specifies the action being taken for that item. Possible action values are: "update" and
431      * "remove".
432      *
433      * @param entityID the address of the XMPP entity.
434      * @param discoverItems the DiscoveryItems to publish.
435      * @throws XMPPException if the operation failed for some reason.
436      */

437     public void publishItems(String JavaDoc entityID, DiscoverItems discoverItems)
438             throws XMPPException {
439         publishItems(entityID, null, discoverItems);
440     }
441
442     /**
443      * Publishes new items to a parent entity and node. The item elements to publish MUST have at
444      * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
445      * specifies the action being taken for that item. Possible action values are: "update" and
446      * "remove".
447      *
448      * @param entityID the address of the XMPP entity.
449      * @param node the attribute that supplements the 'jid' attribute.
450      * @param discoverItems the DiscoveryItems to publish.
451      * @throws XMPPException if the operation failed for some reason.
452      */

453     public void publishItems(String JavaDoc entityID, String JavaDoc node, DiscoverItems discoverItems)
454             throws XMPPException {
455         discoverItems.setType(IQ.Type.SET);
456         discoverItems.setTo(entityID);
457         discoverItems.setNode(node);
458
459         // Create a packet collector to listen for a response.
460
PacketCollector collector =
461             connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID()));
462
463         connection.sendPacket(discoverItems);
464
465         // Wait up to 5 seconds for a result.
466
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
467         // Stop queuing results
468
collector.cancel();
469         if (result == null) {
470             throw new XMPPException("No response from the server.");
471         }
472         if (result.getType() == IQ.Type.ERROR) {
473             throw new XMPPException(result.getError());
474         }
475     }
476 }
Popular Tags