• 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;
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@example.com' from='mary@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      */
getPrivateDataProvider(String elementName, String namespace)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      */
addPrivateDataProvider(String elementName, String namespace, PrivateDataProvider provider)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      */
removePrivateDataProvider(String elementName, String namespace)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      */
PrivateDataManager(Connection connection)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      */
PrivateDataManager(Connection connection, String user)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      */
getPrivateData(final String elementName, final String namespace)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      */
setPrivateData(final PrivateData privateData)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      */
getProviderKey(String elementName, String namespace)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 {
parseIQ(XmlPullParser parser)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 
PrivateDataResult(PrivateData privateData)341         PrivateDataResult(PrivateData privateData) {
342             this.privateData = privateData;
343         }
344 
getPrivateData()345         public PrivateData getPrivateData() {
346             return privateData;
347         }
348 
getChildElementXML()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 }