PrivateDataManager.java

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

  17. package org.jivesoftware.smackx.iqprivate;

  18. import org.jivesoftware.smack.Manager;
  19. import org.jivesoftware.smack.SmackException;
  20. import org.jivesoftware.smack.SmackException.NoResponseException;
  21. import org.jivesoftware.smack.SmackException.NotConnectedException;
  22. import org.jivesoftware.smack.XMPPConnection;
  23. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  24. import org.jivesoftware.smack.packet.IQ;
  25. import org.jivesoftware.smack.provider.IQProvider;
  26. import org.jivesoftware.smackx.iqprivate.packet.DefaultPrivateData;
  27. import org.jivesoftware.smackx.iqprivate.packet.PrivateData;
  28. import org.jivesoftware.smackx.iqprivate.packet.PrivateDataIQ;
  29. import org.jivesoftware.smackx.iqprivate.provider.PrivateDataProvider;
  30. import org.xmlpull.v1.XmlPullParser;
  31. import org.xmlpull.v1.XmlPullParserException;

  32. import java.io.IOException;
  33. import java.util.Hashtable;
  34. import java.util.Map;
  35. import java.util.WeakHashMap;

  36. /**
  37.  * Manages private data, which is a mechanism to allow users to store arbitrary XML
  38.  * data on an XMPP server. Each private data chunk is defined by a element name and
  39.  * XML namespace. Example private data:
  40.  *
  41.  * <pre>
  42.  * &lt;color xmlns="http://example.com/xmpp/color"&gt;
  43.  *     &lt;favorite&gt;blue&lt;/blue&gt;
  44.  *     &lt;leastFavorite&gt;puce&lt;/leastFavorite&gt;
  45.  * &lt;/color&gt;
  46.  * </pre>
  47.  *
  48.  * {@link PrivateDataProvider} instances are responsible for translating the XML into objects.
  49.  * If no PrivateDataProvider is registered for a given element name and namespace, then
  50.  * a {@link DefaultPrivateData} instance will be returned.<p>
  51.  *
  52.  * Warning: this is an non-standard protocol documented by
  53.  * <a href="http://www.xmpp.org/extensions/jep-0049.html">XEP-49</a>. Because this is a
  54.  * non-standard protocol, it is subject to change.
  55.  *
  56.  * @author Matt Tucker
  57.  */
  58. public class PrivateDataManager extends Manager {
  59.     private static final Map<XMPPConnection, PrivateDataManager> instances = new WeakHashMap<XMPPConnection, PrivateDataManager>();

  60.     public static synchronized PrivateDataManager getInstanceFor(XMPPConnection connection) {
  61.         PrivateDataManager privateDataManager = instances.get(connection);
  62.         if (privateDataManager == null) {
  63.             privateDataManager = new PrivateDataManager(connection);
  64.         }
  65.         return privateDataManager;
  66.     }

  67.     /**
  68.      * Map of provider instances.
  69.      */
  70.     private static Map<String, PrivateDataProvider> privateDataProviders = new Hashtable<String, PrivateDataProvider>();

  71.     /**
  72.      * Returns the private data provider registered to the specified XML element name and namespace.
  73.      * For example, if a provider was registered to the element name "prefs" and the
  74.      * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger
  75.      * the provider:
  76.      *
  77.      * <pre>
  78.      * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
  79.      *     &lt;query xmlns='jabber:iq:private'&gt;
  80.      *         &lt;prefs xmlns='http://www.xmppclient.com/prefs'&gt;
  81.      *             &lt;value1&gt;ABC&lt;/value1&gt;
  82.      *             &lt;value2&gt;XYZ&lt;/value2&gt;
  83.      *         &lt;/prefs&gt;
  84.      *     &lt;/query&gt;
  85.      * &lt;/iq&gt;</pre>
  86.      *
  87.      * <p>Note: this method is generally only called by the internal Smack classes.
  88.      *
  89.      * @param elementName the XML element name.
  90.      * @param namespace the XML namespace.
  91.      * @return the PrivateData provider.
  92.      */
  93.     public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) {
  94.         String key = getProviderKey(elementName, namespace);
  95.         return (PrivateDataProvider)privateDataProviders.get(key);
  96.     }

  97.     /**
  98.      * Adds a private data provider with the specified element name and name space. The provider
  99.      * will override any providers loaded through the classpath.
  100.      *
  101.      * @param elementName the XML element name.
  102.      * @param namespace the XML namespace.
  103.      * @param provider the private data provider.
  104.      */
  105.     public static void addPrivateDataProvider(String elementName, String namespace,
  106.             PrivateDataProvider provider)
  107.     {
  108.         String key = getProviderKey(elementName, namespace);
  109.         privateDataProviders.put(key, provider);
  110.     }

  111.     /**
  112.      * Removes a private data provider with the specified element name and namespace.
  113.      *
  114.      * @param elementName The XML element name.
  115.      * @param namespace The XML namespace.
  116.      */
  117.     public static void removePrivateDataProvider(String elementName, String namespace) {
  118.         String key = getProviderKey(elementName, namespace);
  119.         privateDataProviders.remove(key);
  120.     }

  121.     /**
  122.      * Creates a new private data manager.
  123.      *
  124.      * @param connection an XMPP connection which must have already undergone a
  125.      *      successful login.
  126.      */
  127.     private PrivateDataManager(XMPPConnection connection) {
  128.         super(connection);
  129.         instances.put(connection, this);
  130.     }

  131.     /**
  132.      * Returns the private data specified by the given element name and namespace. Each chunk
  133.      * of private data is uniquely identified by an element name and namespace pair.<p>
  134.      *
  135.      * If a PrivateDataProvider is registered for the specified element name/namespace pair then
  136.      * that provider will determine the specific object type that is returned. If no provider
  137.      * is registered, a {@link DefaultPrivateData} instance will be returned.
  138.      *
  139.      * @param elementName the element name.
  140.      * @param namespace the namespace.
  141.      * @return the private data.
  142.      * @throws XMPPErrorException
  143.      * @throws NoResponseException
  144.      * @throws NotConnectedException
  145.      * @throws InterruptedException
  146.      */
  147.     public PrivateData getPrivateData(final String elementName, final String namespace) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  148.     {
  149.         // Create an IQ packet to get the private data.
  150.         IQ privateDataGet = new PrivateDataIQ(elementName, namespace);

  151.         PrivateDataIQ response = connection().createPacketCollectorAndSend(
  152.                         privateDataGet).nextResultOrThrow();
  153.         return response.getPrivateData();
  154.     }

  155.     /**
  156.      * Sets a private data value. Each chunk of private data is uniquely identified by an
  157.      * element name and namespace pair. If private data has already been set with the
  158.      * element name and namespace, then the new private data will overwrite the old value.
  159.      *
  160.      * @param privateData the private data.
  161.      * @throws XMPPErrorException
  162.      * @throws NoResponseException
  163.      * @throws NotConnectedException
  164.      * @throws InterruptedException
  165.      */
  166.     public void setPrivateData(final PrivateData privateData) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  167.         // Create an IQ packet to set the private data.
  168.         IQ privateDataSet = new PrivateDataIQ(privateData);

  169.         connection().createPacketCollectorAndSend(privateDataSet).nextResultOrThrow();
  170.     }

  171.     /**
  172.      * Returns a String key for a given element name and namespace.
  173.      *
  174.      * @param elementName the element name.
  175.      * @param namespace the namespace.
  176.      * @return a unique key for the element name and namespace pair.
  177.      */
  178.     private static String getProviderKey(String elementName, String namespace) {
  179.         StringBuilder buf = new StringBuilder();
  180.         buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
  181.         return buf.toString();
  182.     }

  183.     /**
  184.      * An IQ provider to parse IQ results containing private data.
  185.      */
  186.     public static class PrivateDataIQProvider extends IQProvider<PrivateDataIQ> {

  187.         @Override
  188.         public PrivateDataIQ parse(XmlPullParser parser, int initialDepth)
  189.                         throws XmlPullParserException, IOException, SmackException {
  190.             PrivateData privateData = null;
  191.             boolean done = false;
  192.             while (!done) {
  193.                 int eventType = parser.next();
  194.                 if (eventType == XmlPullParser.START_TAG) {
  195.                     String elementName = parser.getName();
  196.                     String namespace = parser.getNamespace();
  197.                     // See if any objects are registered to handle this private data type.
  198.                     PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace);
  199.                     // If there is a registered provider, use it.
  200.                     if (provider != null) {
  201.                         privateData = provider.parsePrivateData(parser);
  202.                     }
  203.                     // Otherwise, use a DefaultPrivateData instance to store the private data.
  204.                     else {
  205.                         DefaultPrivateData data = new DefaultPrivateData(elementName, namespace);
  206.                         boolean finished = false;
  207.                         while (!finished) {
  208.                             int event = parser.next();
  209.                             if (event == XmlPullParser.START_TAG) {
  210.                                 String name = parser.getName();
  211.                                 // If an empty element, set the value with the empty string.
  212.                                 if (parser.isEmptyElementTag()) {
  213.                                     data.setValue(name,"");
  214.                                 }
  215.                                 // Otherwise, get the the element text.
  216.                                 else {
  217.                                     event = parser.next();
  218.                                     if (event == XmlPullParser.TEXT) {
  219.                                         String value = parser.getText();
  220.                                         data.setValue(name, value);
  221.                                     }
  222.                                 }
  223.                             }
  224.                             else if (event == XmlPullParser.END_TAG) {
  225.                                 if (parser.getName().equals(elementName)) {
  226.                                     finished = true;
  227.                                 }
  228.                             }
  229.                         }
  230.                         privateData = data;
  231.                     }
  232.                 }
  233.                 else if (eventType == XmlPullParser.END_TAG) {
  234.                     if (parser.getName().equals("query")) {
  235.                         done = true;
  236.                     }
  237.                 }
  238.             }
  239.             return new PrivateDataIQ(privateData);
  240.         }
  241.     }
  242. }