Home | History | Annotate | Download | only in smackx
      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;
     22 
     23 import org.jivesoftware.smack.PacketCollector;
     24 import org.jivesoftware.smack.SmackConfiguration;
     25 import org.jivesoftware.smack.Connection;
     26 import org.jivesoftware.smack.XMPPException;
     27 import org.jivesoftware.smack.filter.PacketIDFilter;
     28 import org.jivesoftware.smack.packet.IQ;
     29 import org.jivesoftware.smack.provider.IQProvider;
     30 import org.jivesoftware.smackx.packet.DefaultPrivateData;
     31 import org.jivesoftware.smackx.packet.PrivateData;
     32 import org.jivesoftware.smackx.provider.PrivateDataProvider;
     33 import org.xmlpull.v1.XmlPullParser;
     34 
     35 import java.util.Hashtable;
     36 import java.util.Map;
     37 
     38 /**
     39  * Manages private data, which is a mechanism to allow users to store arbitrary XML
     40  * data on an XMPP server. Each private data chunk is defined by a element name and
     41  * XML namespace. Example private data:
     42  *
     43  * <pre>
     44  * &lt;color xmlns="http://example.com/xmpp/color"&gt;
     45  *     &lt;favorite&gt;blue&lt;/blue&gt;
     46  *     &lt;leastFavorite&gt;puce&lt;/leastFavorite&gt;
     47  * &lt;/color&gt;
     48  * </pre>
     49  *
     50  * {@link PrivateDataProvider} instances are responsible for translating the XML into objects.
     51  * If no PrivateDataProvider is registered for a given element name and namespace, then
     52  * a {@link DefaultPrivateData} instance will be returned.<p>
     53  *
     54  * Warning: this is an non-standard protocol documented by
     55  * <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>. Because this is a
     56  * non-standard protocol, it is subject to change.
     57  *
     58  * @author Matt Tucker
     59  */
     60 public class PrivateDataManager {
     61 
     62     /**
     63      * Map of provider instances.
     64      */
     65     private static Map<String, PrivateDataProvider> privateDataProviders = new Hashtable<String, PrivateDataProvider>();
     66 
     67     /**
     68      * Returns the private data provider registered to the specified XML element name and namespace.
     69      * For example, if a provider was registered to the element name "prefs" and the
     70      * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger
     71      * the provider:
     72      *
     73      * <pre>
     74      * &lt;iq type='result' to='joe (at) example.com' from='mary (at) example.com' id='time_1'&gt;
     75      *     &lt;query xmlns='jabber:iq:private'&gt;
     76      *         &lt;prefs xmlns='http://www.xmppclient.com/prefs'&gt;
     77      *             &lt;value1&gt;ABC&lt;/value1&gt;
     78      *             &lt;value2&gt;XYZ&lt;/value2&gt;
     79      *         &lt;/prefs&gt;
     80      *     &lt;/query&gt;
     81      * &lt;/iq&gt;</pre>
     82      *
     83      * <p>Note: this method is generally only called by the internal Smack classes.
     84      *
     85      * @param elementName the XML element name.
     86      * @param namespace the XML namespace.
     87      * @return the PrivateData provider.
     88      */
     89     public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) {
     90         String key = getProviderKey(elementName, namespace);
     91         return (PrivateDataProvider)privateDataProviders.get(key);
     92     }
     93 
     94     /**
     95      * Adds a private data provider with the specified element name and name space. The provider
     96      * will override any providers loaded through the classpath.
     97      *
     98      * @param elementName the XML element name.
     99      * @param namespace the XML namespace.
    100      * @param provider the private data provider.
    101      */
    102     public static void addPrivateDataProvider(String elementName, String namespace,
    103             PrivateDataProvider provider)
    104     {
    105         String key = getProviderKey(elementName, namespace);
    106         privateDataProviders.put(key, provider);
    107     }
    108 
    109     /**
    110      * Removes a private data provider with the specified element name and namespace.
    111      *
    112      * @param elementName The XML element name.
    113      * @param namespace The XML namespace.
    114      */
    115     public static void removePrivateDataProvider(String elementName, String namespace) {
    116         String key = getProviderKey(elementName, namespace);
    117         privateDataProviders.remove(key);
    118     }
    119 
    120 
    121     private Connection connection;
    122 
    123     /**
    124      * The user to get and set private data for. In most cases, this value should
    125      * be <tt>null</tt>, as the typical use of private data is to get and set
    126      * your own private data and not others.
    127      */
    128     private String user;
    129 
    130     /**
    131      * Creates a new private data manager. The connection must have
    132      * undergone a successful login before being used to construct an instance of
    133      * this class.
    134      *
    135      * @param connection an XMPP connection which must have already undergone a
    136      *      successful login.
    137      */
    138     public PrivateDataManager(Connection connection) {
    139         if (!connection.isAuthenticated()) {
    140             throw new IllegalStateException("Must be logged in to XMPP server.");
    141         }
    142         this.connection = connection;
    143     }
    144 
    145     /**
    146      * Creates a new private data manager for a specific user (special case). Most
    147      * servers only support getting and setting private data for the user that
    148      * authenticated via the connection. However, some servers support the ability
    149      * to get and set private data for other users (for example, if you are the
    150      * administrator). The connection must have undergone a successful login before
    151      * being used to construct an instance of this class.
    152      *
    153      * @param connection an XMPP connection which must have already undergone a
    154      *      successful login.
    155      * @param user the XMPP address of the user to get and set private data for.
    156      */
    157     public PrivateDataManager(Connection connection, String user) {
    158         if (!connection.isAuthenticated()) {
    159             throw new IllegalStateException("Must be logged in to XMPP server.");
    160         }
    161         this.connection = connection;
    162         this.user = user;
    163     }
    164 
    165     /**
    166      * Returns the private data specified by the given element name and namespace. Each chunk
    167      * of private data is uniquely identified by an element name and namespace pair.<p>
    168      *
    169      * If a PrivateDataProvider is registered for the specified element name/namespace pair then
    170      * that provider will determine the specific object type that is returned. If no provider
    171      * is registered, a {@link DefaultPrivateData} instance will be returned.
    172      *
    173      * @param elementName the element name.
    174      * @param namespace the namespace.
    175      * @return the private data.
    176      * @throws XMPPException if an error occurs getting the private data.
    177      */
    178     public PrivateData getPrivateData(final String elementName, final String namespace)
    179             throws XMPPException
    180     {
    181         // Create an IQ packet to get the private data.
    182         IQ privateDataGet = new IQ() {
    183             public String getChildElementXML() {
    184                 StringBuilder buf = new StringBuilder();
    185                 buf.append("<query xmlns=\"jabber:iq:private\">");
    186                 buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>");
    187                 buf.append("</query>");
    188                 return buf.toString();
    189             }
    190         };
    191         privateDataGet.setType(IQ.Type.GET);
    192         // Address the packet to the other account if user has been set.
    193         if (user != null) {
    194             privateDataGet.setTo(user);
    195         }
    196 
    197         // Setup a listener for the reply to the set operation.
    198         String packetID = privateDataGet.getPacketID();
    199         PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID));
    200 
    201         // Send the private data.
    202         connection.sendPacket(privateDataGet);
    203 
    204         // Wait up to five seconds for a response from the server.
    205         IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
    206         // Stop queuing results
    207         collector.cancel();
    208         if (response == null) {
    209             throw new XMPPException("No response from the server.");
    210         }
    211         // If the server replied with an error, throw an exception.
    212         else if (response.getType() == IQ.Type.ERROR) {
    213             throw new XMPPException(response.getError());
    214         }
    215         return ((PrivateDataResult)response).getPrivateData();
    216     }
    217 
    218     /**
    219      * Sets a private data value. Each chunk of private data is uniquely identified by an
    220      * element name and namespace pair. If private data has already been set with the
    221      * element name and namespace, then the new private data will overwrite the old value.
    222      *
    223      * @param privateData the private data.
    224      * @throws XMPPException if setting the private data fails.
    225      */
    226     public void setPrivateData(final PrivateData privateData) throws XMPPException {
    227         // Create an IQ packet to set the private data.
    228         IQ privateDataSet = new IQ() {
    229             public String getChildElementXML() {
    230                 StringBuilder buf = new StringBuilder();
    231                 buf.append("<query xmlns=\"jabber:iq:private\">");
    232                 buf.append(privateData.toXML());
    233                 buf.append("</query>");
    234                 return buf.toString();
    235             }
    236         };
    237         privateDataSet.setType(IQ.Type.SET);
    238         // Address the packet to the other account if user has been set.
    239         if (user != null) {
    240             privateDataSet.setTo(user);
    241         }
    242 
    243         // Setup a listener for the reply to the set operation.
    244         String packetID = privateDataSet.getPacketID();
    245         PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID));
    246 
    247         // Send the private data.
    248         connection.sendPacket(privateDataSet);
    249 
    250         // Wait up to five seconds for a response from the server.
    251         IQ response = (IQ)collector.nextResult(5000);
    252         // Stop queuing results
    253         collector.cancel();
    254         if (response == null) {
    255             throw new XMPPException("No response from the server.");
    256         }
    257         // If the server replied with an error, throw an exception.
    258         else if (response.getType() == IQ.Type.ERROR) {
    259             throw new XMPPException(response.getError());
    260         }
    261     }
    262 
    263     /**
    264      * Returns a String key for a given element name and namespace.
    265      *
    266      * @param elementName the element name.
    267      * @param namespace the namespace.
    268      * @return a unique key for the element name and namespace pair.
    269      */
    270     private static String getProviderKey(String elementName, String namespace) {
    271         StringBuilder buf = new StringBuilder();
    272         buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
    273         return buf.toString();
    274     }
    275 
    276     /**
    277      * An IQ provider to parse IQ results containing private data.
    278      */
    279     public static class PrivateDataIQProvider implements IQProvider {
    280         public IQ parseIQ(XmlPullParser parser) throws Exception {
    281             PrivateData privateData = null;
    282             boolean done = false;
    283             while (!done) {
    284                 int eventType = parser.next();
    285                 if (eventType == XmlPullParser.START_TAG) {
    286                     String elementName = parser.getName();
    287                     String namespace = parser.getNamespace();
    288                     // See if any objects are registered to handle this private data type.
    289                     PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace);
    290                     // If there is a registered provider, use it.
    291                     if (provider != null) {
    292                         privateData = provider.parsePrivateData(parser);
    293                     }
    294                     // Otherwise, use a DefaultPrivateData instance to store the private data.
    295                     else {
    296                         DefaultPrivateData data = new DefaultPrivateData(elementName, namespace);
    297                         boolean finished = false;
    298                         while (!finished) {
    299                             int event = parser.next();
    300                             if (event == XmlPullParser.START_TAG) {
    301                                 String name = parser.getName();
    302                                 // If an empty element, set the value with the empty string.
    303                                 if (parser.isEmptyElementTag()) {
    304                                     data.setValue(name,"");
    305                                 }
    306                                 // Otherwise, get the the element text.
    307                                 else {
    308                                     event = parser.next();
    309                                     if (event == XmlPullParser.TEXT) {
    310                                         String value = parser.getText();
    311                                         data.setValue(name, value);
    312                                     }
    313                                 }
    314                             }
    315                             else if (event == XmlPullParser.END_TAG) {
    316                                 if (parser.getName().equals(elementName)) {
    317                                     finished = true;
    318                                 }
    319                             }
    320                         }
    321                         privateData = data;
    322                     }
    323                 }
    324                 else if (eventType == XmlPullParser.END_TAG) {
    325                     if (parser.getName().equals("query")) {
    326                         done = true;
    327                     }
    328                 }
    329             }
    330             return new PrivateDataResult(privateData);
    331         }
    332     }
    333 
    334     /**
    335      * An IQ packet to hold PrivateData GET results.
    336      */
    337     private static class PrivateDataResult extends IQ {
    338 
    339         private PrivateData privateData;
    340 
    341         PrivateDataResult(PrivateData privateData) {
    342             this.privateData = privateData;
    343         }
    344 
    345         public PrivateData getPrivateData() {
    346             return privateData;
    347         }
    348 
    349         public String getChildElementXML() {
    350             StringBuilder buf = new StringBuilder();
    351             buf.append("<query xmlns=\"jabber:iq:private\">");
    352             if (privateData != null) {
    353                 privateData.toXML();
    354             }
    355             buf.append("</query>");
    356             return buf.toString();
    357         }
    358     }
    359 }