blob: c6440bcf38b5a1fe3cf78d59097fa526834e7c2b [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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
21package org.jivesoftware.smackx;
22
23import org.jivesoftware.smack.PacketCollector;
24import org.jivesoftware.smack.SmackConfiguration;
25import org.jivesoftware.smack.Connection;
26import org.jivesoftware.smack.XMPPException;
27import org.jivesoftware.smack.filter.PacketIDFilter;
28import org.jivesoftware.smack.packet.IQ;
29import org.jivesoftware.smack.provider.IQProvider;
30import org.jivesoftware.smackx.packet.DefaultPrivateData;
31import org.jivesoftware.smackx.packet.PrivateData;
32import org.jivesoftware.smackx.provider.PrivateDataProvider;
33import org.xmlpull.v1.XmlPullParser;
34
35import java.util.Hashtable;
36import 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 */
60public 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 */
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}