• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * $RCSfile$
3  * $Revision$
4  * $Date$
5  *
6  * Copyright 2003-2007 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.packet;
22 
23 import org.jivesoftware.smack.packet.IQ;
24 import org.jivesoftware.smack.util.StringUtils;
25 
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Iterator;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.concurrent.CopyOnWriteArrayList;
32 
33 /**
34  * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information
35  * to/from other XMPP entities.<p>
36  *
37  * The received information may contain one or more identities of the requested XMPP entity, and
38  * a list of supported features by the requested XMPP entity.
39  *
40  * @author Gaston Dombiak
41  */
42 public class DiscoverInfo extends IQ {
43 
44     public static final String NAMESPACE = "http://jabber.org/protocol/disco#info";
45 
46     private final List<Feature> features = new CopyOnWriteArrayList<Feature>();
47     private final List<Identity> identities = new CopyOnWriteArrayList<Identity>();
48     private String node;
49 
DiscoverInfo()50     public DiscoverInfo() {
51         super();
52     }
53 
54     /**
55      * Copy constructor
56      *
57      * @param d
58      */
DiscoverInfo(DiscoverInfo d)59     public DiscoverInfo(DiscoverInfo d) {
60         super(d);
61 
62         // Set node
63         setNode(d.getNode());
64 
65         // Copy features
66         synchronized (d.features) {
67             for (Feature f : d.features) {
68                 addFeature(f);
69             }
70         }
71 
72         // Copy identities
73         synchronized (d.identities) {
74             for (Identity i : d.identities) {
75                 addIdentity(i);
76             }
77         }
78     }
79 
80     /**
81      * Adds a new feature to the discovered information.
82      *
83      * @param feature the discovered feature
84      */
addFeature(String feature)85     public void addFeature(String feature) {
86         addFeature(new Feature(feature));
87     }
88 
89     /**
90      * Adds a collection of features to the packet. Does noting if featuresToAdd is null.
91      *
92      * @param featuresToAdd
93      */
addFeatures(Collection<String> featuresToAdd)94     public void addFeatures(Collection<String> featuresToAdd) {
95         if (featuresToAdd == null) return;
96         for (String feature : featuresToAdd) {
97             addFeature(feature);
98         }
99     }
100 
addFeature(Feature feature)101     private void addFeature(Feature feature) {
102         synchronized (features) {
103             features.add(feature);
104         }
105     }
106 
107     /**
108      * Returns the discovered features of an XMPP entity.
109      *
110      * @return an Iterator on the discovered features of an XMPP entity
111      */
getFeatures()112     public Iterator<Feature> getFeatures() {
113         synchronized (features) {
114             return Collections.unmodifiableList(features).iterator();
115         }
116     }
117 
118     /**
119      * Adds a new identity of the requested entity to the discovered information.
120      *
121      * @param identity the discovered entity's identity
122      */
addIdentity(Identity identity)123     public void addIdentity(Identity identity) {
124         synchronized (identities) {
125             identities.add(identity);
126         }
127     }
128 
129     /**
130      * Adds identities to the DiscoverInfo stanza
131      *
132      * @param identitiesToAdd
133      */
addIdentities(Collection<Identity> identitiesToAdd)134     public void addIdentities(Collection<Identity> identitiesToAdd) {
135         if (identitiesToAdd == null) return;
136         synchronized (identities) {
137             identities.addAll(identitiesToAdd);
138         }
139     }
140 
141     /**
142      * Returns the discovered identities of an XMPP entity.
143      *
144      * @return an Iterator on the discoveted identities
145      */
getIdentities()146     public Iterator<Identity> getIdentities() {
147         synchronized (identities) {
148             return Collections.unmodifiableList(identities).iterator();
149         }
150     }
151 
152     /**
153      * Returns the node attribute that supplements the 'jid' attribute. A node is merely
154      * something that is associated with a JID and for which the JID can provide information.<p>
155      *
156      * Node attributes SHOULD be used only when trying to provide or query information which
157      * is not directly addressable.
158      *
159      * @return the node attribute that supplements the 'jid' attribute
160      */
getNode()161     public String getNode() {
162         return node;
163     }
164 
165     /**
166      * Sets the node attribute that supplements the 'jid' attribute. A node is merely
167      * something that is associated with a JID and for which the JID can provide information.<p>
168      *
169      * Node attributes SHOULD be used only when trying to provide or query information which
170      * is not directly addressable.
171      *
172      * @param node the node attribute that supplements the 'jid' attribute
173      */
setNode(String node)174     public void setNode(String node) {
175         this.node = node;
176     }
177 
178     /**
179      * Returns true if the specified feature is part of the discovered information.
180      *
181      * @param feature the feature to check
182      * @return true if the requestes feature has been discovered
183      */
containsFeature(String feature)184     public boolean containsFeature(String feature) {
185         for (Iterator<Feature> it = getFeatures(); it.hasNext();) {
186             if (feature.equals(it.next().getVar()))
187                 return true;
188         }
189         return false;
190     }
191 
getChildElementXML()192     public String getChildElementXML() {
193         StringBuilder buf = new StringBuilder();
194         buf.append("<query xmlns=\"" + NAMESPACE + "\"");
195         if (getNode() != null) {
196             buf.append(" node=\"");
197             buf.append(StringUtils.escapeForXML(getNode()));
198             buf.append("\"");
199         }
200         buf.append(">");
201         synchronized (identities) {
202             for (Identity identity : identities) {
203                 buf.append(identity.toXML());
204             }
205         }
206         synchronized (features) {
207             for (Feature feature : features) {
208                 buf.append(feature.toXML());
209             }
210         }
211         // Add packet extensions, if any are defined.
212         buf.append(getExtensionsXML());
213         buf.append("</query>");
214         return buf.toString();
215     }
216 
217     /**
218      * Test if a DiscoverInfo response contains duplicate identities.
219      *
220      * @return true if duplicate identities where found, otherwise false
221      */
containsDuplicateIdentities()222     public boolean containsDuplicateIdentities() {
223         List<Identity> checkedIdentities = new LinkedList<Identity>();
224         for (Identity i : identities) {
225             for (Identity i2 : checkedIdentities) {
226                 if (i.equals(i2))
227                     return true;
228             }
229             checkedIdentities.add(i);
230         }
231         return false;
232     }
233 
234     /**
235      * Test if a DiscoverInfo response contains duplicate features.
236      *
237      * @return true if duplicate identities where found, otherwise false
238      */
containsDuplicateFeatures()239     public boolean containsDuplicateFeatures() {
240         List<Feature> checkedFeatures = new LinkedList<Feature>();
241         for (Feature f : features) {
242             for (Feature f2 : checkedFeatures) {
243                 if (f.equals(f2))
244                     return true;
245             }
246             checkedFeatures.add(f);
247         }
248         return false;
249     }
250 
251     /**
252      * Represents the identity of a given XMPP entity. An entity may have many identities but all
253      * the identities SHOULD have the same name.<p>
254      *
255      * Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
256      * in order to get the official registry of values for the <i>category</i> and <i>type</i>
257      * attributes.
258      *
259      */
260     public static class Identity implements Comparable<Identity> {
261 
262         private String category;
263         private String name;
264         private String type;
265         private String lang; // 'xml:lang;
266 
267         /**
268          * Creates a new identity for an XMPP entity.
269          *
270          * @param category the entity's category.
271          * @param name the entity's name.
272          * @deprecated As per the spec, the type field is mandatory and the 3 argument constructor should be used instead.
273          */
Identity(String category, String name)274         public Identity(String category, String name) {
275             this.category = category;
276             this.name = name;
277         }
278 
279         /**
280          * Creates a new identity for an XMPP entity.
281          * 'category' and 'type' are required by
282          * <a href="http://xmpp.org/extensions/xep-0030.html#schemas">XEP-30 XML Schemas</a>
283          *
284          * @param category the entity's category (required as per XEP-30).
285          * @param name the entity's name.
286          * @param type the entity's type (required as per XEP-30).
287          */
Identity(String category, String name, String type)288         public Identity(String category, String name, String type) {
289             if ((category == null) || (type == null))
290                 throw new IllegalArgumentException("category and type cannot be null");
291 
292             this.category = category;
293             this.name = name;
294             this.type = type;
295         }
296 
297         /**
298          * Returns the entity's category. To get the official registry of values for the
299          * 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
300          *
301          * @return the entity's category.
302          */
getCategory()303         public String getCategory() {
304             return category;
305         }
306 
307         /**
308          * Returns the identity's name.
309          *
310          * @return the identity's name.
311          */
getName()312         public String getName() {
313             return name;
314         }
315 
316         /**
317          * Returns the entity's type. To get the official registry of values for the
318          * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
319          *
320          * @return the entity's type.
321          */
getType()322         public String getType() {
323             return type;
324         }
325 
326         /**
327          * Sets the entity's type. To get the official registry of values for the
328          * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
329          *
330          * @param type the identity's type.
331          * @deprecated As per the spec, this field is mandatory and the 3 argument constructor should be used instead.
332          */
setType(String type)333         public void setType(String type) {
334             this.type = type;
335         }
336 
337         /**
338          * Sets the natural language (xml:lang) for this identity (optional)
339          *
340          * @param lang the xml:lang of this Identity
341          */
setLanguage(String lang)342         public void setLanguage(String lang) {
343             this.lang = lang;
344         }
345 
346         /**
347          * Returns the identities natural language if one is set
348          *
349          * @return the value of xml:lang of this Identity
350          */
getLanguage()351         public String getLanguage() {
352             return lang;
353         }
354 
toXML()355         public String toXML() {
356             StringBuilder buf = new StringBuilder();
357             buf.append("<identity");
358             // Check if this packet has 'lang' set and maybe append it to the resulting string
359             if (lang != null)
360                 buf.append(" xml:lang=\"").append(StringUtils.escapeForXML(lang)).append("\"");
361             // Category must always be set
362             buf.append(" category=\"").append(StringUtils.escapeForXML(category)).append("\"");
363             // Name must always be set
364             buf.append(" name=\"").append(StringUtils.escapeForXML(name)).append("\"");
365             // Check if this packet has 'type' set and maybe append it to the resulting string
366             if (type != null) {
367                 buf.append(" type=\"").append(StringUtils.escapeForXML(type)).append("\"");
368             }
369             buf.append("/>");
370             return buf.toString();
371         }
372 
373         /**
374          * Check equality for Identity  for category, type, lang and name
375          * in that order as defined by
376          * <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0015 5.4 Processing Method (Step 3.3)</a>
377          *
378          */
equals(Object obj)379         public boolean equals(Object obj) {
380             if (obj == null)
381                 return false;
382             if (obj == this)
383                 return true;
384             if (obj.getClass() != getClass())
385                 return false;
386 
387             DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj;
388             if (!this.category.equals(other.category))
389                 return false;
390 
391             String otherLang = other.lang == null ? "" : other.lang;
392             String thisLang = lang == null ? "" : lang;
393             if (!otherLang.equals(thisLang))
394                 return false;
395 
396             // This safeguard can be removed once the deprecated constructor is removed.
397             String otherType = other.type == null ? "" : other.type;
398             String thisType = type == null ? "" : type;
399             if (!otherType.equals(thisType))
400                 return false;
401 
402             String otherName = other.name == null ? "" : other.name;
403             String thisName = name == null ? "" : other.name;
404             if (!thisName.equals(otherName))
405                 return false;
406 
407             return true;
408         }
409 
410         @Override
hashCode()411         public int hashCode() {
412             int result = 1;
413             result = 37 * result + category.hashCode();
414             result = 37 * result + (lang == null ? 0 : lang.hashCode());
415             result = 37 * result + (type == null ? 0 : type.hashCode());
416             result = 37 * result + (name == null ? 0 : name.hashCode());
417             return result;
418         }
419 
420         /**
421          * Compares this identity with another one. The comparison order is:
422          * Category, Type, Lang. If all three are identical the other Identity is considered equal.
423          * Name is not used for comparision, as defined by XEP-0115
424          *
425          * @param obj
426          * @return
427          */
compareTo(DiscoverInfo.Identity other)428         public int compareTo(DiscoverInfo.Identity other) {
429             String otherLang = other.lang == null ? "" : other.lang;
430             String thisLang = lang == null ? "" : lang;
431 
432             // This can be removed once the deprecated constructor is removed.
433             String otherType = other.type == null ? "" : other.type;
434             String thisType = type == null ? "" : type;
435 
436             if (category.equals(other.category)) {
437                 if (thisType.equals(otherType)) {
438                     if (thisLang.equals(otherLang)) {
439                         // Don't compare on name, XEP-30 says that name SHOULD
440                         // be equals for all identities of an entity
441                         return 0;
442                     } else {
443                         return thisLang.compareTo(otherLang);
444                     }
445                 } else {
446                     return thisType.compareTo(otherType);
447                 }
448             } else {
449                 return category.compareTo(other.category);
450             }
451         }
452     }
453 
454     /**
455      * Represents the features offered by the item. This information helps requestors determine
456      * what actions are possible with regard to this item (registration, search, join, etc.)
457      * as well as specific feature types of interest, if any (e.g., for the purpose of feature
458      * negotiation).
459      */
460     public static class Feature {
461 
462         private String variable;
463 
464         /**
465          * Creates a new feature offered by an XMPP entity or item.
466          *
467          * @param variable the feature's variable.
468          */
Feature(String variable)469         public Feature(String variable) {
470             if (variable == null)
471                 throw new IllegalArgumentException("variable cannot be null");
472             this.variable = variable;
473         }
474 
475         /**
476          * Returns the feature's variable.
477          *
478          * @return the feature's variable.
479          */
getVar()480         public String getVar() {
481             return variable;
482         }
483 
toXML()484         public String toXML() {
485             StringBuilder buf = new StringBuilder();
486             buf.append("<feature var=\"").append(StringUtils.escapeForXML(variable)).append("\"/>");
487             return buf.toString();
488         }
489 
equals(Object obj)490         public boolean equals(Object obj) {
491             if (obj == null)
492                 return false;
493             if (obj == this)
494                 return true;
495             if (obj.getClass() != getClass())
496                 return false;
497 
498             DiscoverInfo.Feature other = (DiscoverInfo.Feature) obj;
499             return variable.equals(other.variable);
500         }
501 
502         @Override
hashCode()503         public int hashCode() {
504             return 37 * variable.hashCode();
505         }
506     }
507 }
508