001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smackx.iqprivate; 019 020import org.jivesoftware.smack.Manager; 021import org.jivesoftware.smack.SmackException.NoResponseException; 022import org.jivesoftware.smack.SmackException.NotConnectedException; 023import org.jivesoftware.smack.XMPPConnection; 024import org.jivesoftware.smack.XMPPException.XMPPErrorException; 025import org.jivesoftware.smack.packet.IQ; 026import org.jivesoftware.smack.provider.IQProvider; 027import org.jivesoftware.smackx.iqprivate.packet.DefaultPrivateData; 028import org.jivesoftware.smackx.iqprivate.packet.PrivateData; 029import org.jivesoftware.smackx.iqprivate.provider.PrivateDataProvider; 030import org.xmlpull.v1.XmlPullParser; 031 032import java.util.Hashtable; 033import java.util.Map; 034import java.util.WeakHashMap; 035 036/** 037 * Manages private data, which is a mechanism to allow users to store arbitrary XML 038 * data on an XMPP server. Each private data chunk is defined by a element name and 039 * XML namespace. Example private data: 040 * 041 * <pre> 042 * <color xmlns="http://example.com/xmpp/color"> 043 * <favorite>blue</blue> 044 * <leastFavorite>puce</leastFavorite> 045 * </color> 046 * </pre> 047 * 048 * {@link PrivateDataProvider} instances are responsible for translating the XML into objects. 049 * If no PrivateDataProvider is registered for a given element name and namespace, then 050 * a {@link DefaultPrivateData} instance will be returned.<p> 051 * 052 * Warning: this is an non-standard protocol documented by 053 * <a href="http://www.xmpp.org/extensions/jep-0049.html">XEP-49</a>. Because this is a 054 * non-standard protocol, it is subject to change. 055 * 056 * @author Matt Tucker 057 */ 058public class PrivateDataManager extends Manager { 059 private static final Map<XMPPConnection, PrivateDataManager> instances = new WeakHashMap<XMPPConnection, PrivateDataManager>(); 060 061 public static synchronized PrivateDataManager getInstanceFor(XMPPConnection connection) { 062 PrivateDataManager privateDataManager = instances.get(connection); 063 if (privateDataManager == null) { 064 privateDataManager = new PrivateDataManager(connection); 065 } 066 return privateDataManager; 067 } 068 069 /** 070 * Map of provider instances. 071 */ 072 private static Map<String, PrivateDataProvider> privateDataProviders = new Hashtable<String, PrivateDataProvider>(); 073 074 /** 075 * Returns the private data provider registered to the specified XML element name and namespace. 076 * For example, if a provider was registered to the element name "prefs" and the 077 * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger 078 * the provider: 079 * 080 * <pre> 081 * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> 082 * <query xmlns='jabber:iq:private'> 083 * <prefs xmlns='http://www.xmppclient.com/prefs'> 084 * <value1>ABC</value1> 085 * <value2>XYZ</value2> 086 * </prefs> 087 * </query> 088 * </iq></pre> 089 * 090 * <p>Note: this method is generally only called by the internal Smack classes. 091 * 092 * @param elementName the XML element name. 093 * @param namespace the XML namespace. 094 * @return the PrivateData provider. 095 */ 096 public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) { 097 String key = getProviderKey(elementName, namespace); 098 return (PrivateDataProvider)privateDataProviders.get(key); 099 } 100 101 /** 102 * Adds a private data provider with the specified element name and name space. The provider 103 * will override any providers loaded through the classpath. 104 * 105 * @param elementName the XML element name. 106 * @param namespace the XML namespace. 107 * @param provider the private data provider. 108 */ 109 public static void addPrivateDataProvider(String elementName, String namespace, 110 PrivateDataProvider provider) 111 { 112 String key = getProviderKey(elementName, namespace); 113 privateDataProviders.put(key, provider); 114 } 115 116 /** 117 * Removes a private data provider with the specified element name and namespace. 118 * 119 * @param elementName The XML element name. 120 * @param namespace The XML namespace. 121 */ 122 public static void removePrivateDataProvider(String elementName, String namespace) { 123 String key = getProviderKey(elementName, namespace); 124 privateDataProviders.remove(key); 125 } 126 127 /** 128 * Creates a new private data manager. 129 * 130 * @param connection an XMPP connection which must have already undergone a 131 * successful login. 132 */ 133 private PrivateDataManager(XMPPConnection connection) { 134 super(connection); 135 instances.put(connection, this); 136 } 137 138 /** 139 * Returns the private data specified by the given element name and namespace. Each chunk 140 * of private data is uniquely identified by an element name and namespace pair.<p> 141 * 142 * If a PrivateDataProvider is registered for the specified element name/namespace pair then 143 * that provider will determine the specific object type that is returned. If no provider 144 * is registered, a {@link DefaultPrivateData} instance will be returned. 145 * 146 * @param elementName the element name. 147 * @param namespace the namespace. 148 * @return the private data. 149 * @throws XMPPErrorException 150 * @throws NoResponseException 151 * @throws NotConnectedException 152 */ 153 public PrivateData getPrivateData(final String elementName, final String namespace) throws NoResponseException, XMPPErrorException, NotConnectedException 154 { 155 // Create an IQ packet to get the private data. 156 IQ privateDataGet = new IQ() { 157 public String getChildElementXML() { 158 StringBuilder buf = new StringBuilder(); 159 buf.append("<query xmlns=\"jabber:iq:private\">"); 160 buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>"); 161 buf.append("</query>"); 162 return buf.toString(); 163 } 164 }; 165 privateDataGet.setType(IQ.Type.GET); 166 167 PrivateDataResult response = (PrivateDataResult) connection().createPacketCollectorAndSend( 168 privateDataGet).nextResultOrThrow(); 169 return response.getPrivateData(); 170 } 171 172 /** 173 * Sets a private data value. Each chunk of private data is uniquely identified by an 174 * element name and namespace pair. If private data has already been set with the 175 * element name and namespace, then the new private data will overwrite the old value. 176 * 177 * @param privateData the private data. 178 * @throws XMPPErrorException 179 * @throws NoResponseException 180 * @throws NotConnectedException 181 */ 182 public void setPrivateData(final PrivateData privateData) throws NoResponseException, XMPPErrorException, NotConnectedException { 183 // Create an IQ packet to set the private data. 184 IQ privateDataSet = new IQ() { 185 public String getChildElementXML() { 186 StringBuilder buf = new StringBuilder(); 187 buf.append("<query xmlns=\"jabber:iq:private\">"); 188 buf.append(privateData.toXML()); 189 buf.append("</query>"); 190 return buf.toString(); 191 } 192 }; 193 privateDataSet.setType(IQ.Type.SET); 194 195 connection().createPacketCollectorAndSend(privateDataSet).nextResultOrThrow(); 196 } 197 198 /** 199 * Returns a String key for a given element name and namespace. 200 * 201 * @param elementName the element name. 202 * @param namespace the namespace. 203 * @return a unique key for the element name and namespace pair. 204 */ 205 private static String getProviderKey(String elementName, String namespace) { 206 StringBuilder buf = new StringBuilder(); 207 buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); 208 return buf.toString(); 209 } 210 211 /** 212 * An IQ provider to parse IQ results containing private data. 213 */ 214 public static class PrivateDataIQProvider implements IQProvider { 215 public IQ parseIQ(XmlPullParser parser) throws Exception { 216 PrivateData privateData = null; 217 boolean done = false; 218 while (!done) { 219 int eventType = parser.next(); 220 if (eventType == XmlPullParser.START_TAG) { 221 String elementName = parser.getName(); 222 String namespace = parser.getNamespace(); 223 // See if any objects are registered to handle this private data type. 224 PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace); 225 // If there is a registered provider, use it. 226 if (provider != null) { 227 privateData = provider.parsePrivateData(parser); 228 } 229 // Otherwise, use a DefaultPrivateData instance to store the private data. 230 else { 231 DefaultPrivateData data = new DefaultPrivateData(elementName, namespace); 232 boolean finished = false; 233 while (!finished) { 234 int event = parser.next(); 235 if (event == XmlPullParser.START_TAG) { 236 String name = parser.getName(); 237 // If an empty element, set the value with the empty string. 238 if (parser.isEmptyElementTag()) { 239 data.setValue(name,""); 240 } 241 // Otherwise, get the the element text. 242 else { 243 event = parser.next(); 244 if (event == XmlPullParser.TEXT) { 245 String value = parser.getText(); 246 data.setValue(name, value); 247 } 248 } 249 } 250 else if (event == XmlPullParser.END_TAG) { 251 if (parser.getName().equals(elementName)) { 252 finished = true; 253 } 254 } 255 } 256 privateData = data; 257 } 258 } 259 else if (eventType == XmlPullParser.END_TAG) { 260 if (parser.getName().equals("query")) { 261 done = true; 262 } 263 } 264 } 265 return new PrivateDataResult(privateData); 266 } 267 } 268 269 /** 270 * An IQ packet to hold PrivateData GET results. 271 */ 272 private static class PrivateDataResult extends IQ { 273 274 private PrivateData privateData; 275 276 PrivateDataResult(PrivateData privateData) { 277 this.privateData = privateData; 278 } 279 280 public PrivateData getPrivateData() { 281 return privateData; 282 } 283 284 public String getChildElementXML() { 285 StringBuilder buf = new StringBuilder(); 286 buf.append("<query xmlns=\"jabber:iq:private\">"); 287 if (privateData != null) { 288 buf.append(privateData.toXML()); 289 } 290 buf.append("</query>"); 291 return buf.toString(); 292 } 293 } 294}