Home | History | Annotate | Download | only in packet
      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 
     50     public DiscoverInfo() {
     51         super();
     52     }
     53 
     54     /**
     55      * Copy constructor
     56      *
     57      * @param d
     58      */
     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      */
     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      */
     94     public void addFeatures(Collection<String> featuresToAdd) {
     95         if (featuresToAdd == null) return;
     96         for (String feature : featuresToAdd) {
     97             addFeature(feature);
     98         }
     99     }
    100 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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          */
    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          */
    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          */
    303         public String getCategory() {
    304             return category;
    305         }
    306 
    307         /**
    308          * Returns the identity's name.
    309          *
    310          * @return the identity's name.
    311          */
    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          */
    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          */
    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          */
    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          */
    351         public String getLanguage() {
    352             return lang;
    353         }
    354 
    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          */
    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
    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          */
    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          */
    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          */
    480         public String getVar() {
    481             return variable;
    482         }
    483 
    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 
    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
    503         public int hashCode() {
    504             return 37 * variable.hashCode();
    505         }
    506     }
    507 }
    508