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; 022import org.jivesoftware.smack.SmackException.NoResponseException; 023import org.jivesoftware.smack.SmackException.NotConnectedException; 024import org.jivesoftware.smack.XMPPConnection; 025import org.jivesoftware.smack.XMPPException.XMPPErrorException; 026import org.jivesoftware.smack.packet.IQ; 027import org.jivesoftware.smack.provider.IQProvider; 028import org.jivesoftware.smackx.iqprivate.packet.DefaultPrivateData; 029import org.jivesoftware.smackx.iqprivate.packet.PrivateData; 030import org.jivesoftware.smackx.iqprivate.packet.PrivateDataIQ; 031import org.jivesoftware.smackx.iqprivate.provider.PrivateDataProvider; 032import org.xmlpull.v1.XmlPullParser; 033import org.xmlpull.v1.XmlPullParserException; 034 035import java.io.IOException; 036import java.util.Hashtable; 037import java.util.Map; 038import java.util.WeakHashMap; 039 040/** 041 * Manages private data, which is a mechanism to allow users to store arbitrary XML 042 * data on an XMPP server. Each private data chunk is defined by a element name and 043 * XML namespace. Example private data: 044 * 045 * <pre> 046 * <color xmlns="http://example.com/xmpp/color"> 047 * <favorite>blue</blue> 048 * <leastFavorite>puce</leastFavorite> 049 * </color> 050 * </pre> 051 * 052 * {@link PrivateDataProvider} instances are responsible for translating the XML into objects. 053 * If no PrivateDataProvider is registered for a given element name and namespace, then 054 * a {@link DefaultPrivateData} instance will be returned.<p> 055 * 056 * Warning: this is an non-standard protocol documented by 057 * <a href="http://www.xmpp.org/extensions/jep-0049.html">XEP-49</a>. Because this is a 058 * non-standard protocol, it is subject to change. 059 * 060 * @author Matt Tucker 061 */ 062public class PrivateDataManager extends Manager { 063 private static final Map<XMPPConnection, PrivateDataManager> instances = new WeakHashMap<XMPPConnection, PrivateDataManager>(); 064 065 public static synchronized PrivateDataManager getInstanceFor(XMPPConnection connection) { 066 PrivateDataManager privateDataManager = instances.get(connection); 067 if (privateDataManager == null) { 068 privateDataManager = new PrivateDataManager(connection); 069 } 070 return privateDataManager; 071 } 072 073 /** 074 * Map of provider instances. 075 */ 076 private static Map<String, PrivateDataProvider> privateDataProviders = new Hashtable<String, PrivateDataProvider>(); 077 078 /** 079 * Returns the private data provider registered to the specified XML element name and namespace. 080 * For example, if a provider was registered to the element name "prefs" and the 081 * namespace "http://www.xmppclient.com/prefs", then the following stanza(/packet) would trigger 082 * the provider: 083 * 084 * <pre> 085 * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> 086 * <query xmlns='jabber:iq:private'> 087 * <prefs xmlns='http://www.xmppclient.com/prefs'> 088 * <value1>ABC</value1> 089 * <value2>XYZ</value2> 090 * </prefs> 091 * </query> 092 * </iq></pre> 093 * 094 * <p>Note: this method is generally only called by the internal Smack classes. 095 * 096 * @param elementName the XML element name. 097 * @param namespace the XML namespace. 098 * @return the PrivateData provider. 099 */ 100 public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) { 101 String key = getProviderKey(elementName, namespace); 102 return (PrivateDataProvider)privateDataProviders.get(key); 103 } 104 105 /** 106 * Adds a private data provider with the specified element name and name space. The provider 107 * will override any providers loaded through the classpath. 108 * 109 * @param elementName the XML element name. 110 * @param namespace the XML namespace. 111 * @param provider the private data provider. 112 */ 113 public static void addPrivateDataProvider(String elementName, String namespace, 114 PrivateDataProvider provider) 115 { 116 String key = getProviderKey(elementName, namespace); 117 privateDataProviders.put(key, provider); 118 } 119 120 /** 121 * Removes a private data provider with the specified element name and namespace. 122 * 123 * @param elementName The XML element name. 124 * @param namespace The XML namespace. 125 */ 126 public static void removePrivateDataProvider(String elementName, String namespace) { 127 String key = getProviderKey(elementName, namespace); 128 privateDataProviders.remove(key); 129 } 130 131 /** 132 * Creates a new private data manager. 133 * 134 * @param connection an XMPP connection which must have already undergone a 135 * successful login. 136 */ 137 private PrivateDataManager(XMPPConnection connection) { 138 super(connection); 139 instances.put(connection, this); 140 } 141 142 /** 143 * Returns the private data specified by the given element name and namespace. Each chunk 144 * of private data is uniquely identified by an element name and namespace pair.<p> 145 * 146 * If a PrivateDataProvider is registered for the specified element name/namespace pair then 147 * that provider will determine the specific object type that is returned. If no provider 148 * is registered, a {@link DefaultPrivateData} instance will be returned. 149 * 150 * @param elementName the element name. 151 * @param namespace the namespace. 152 * @return the private data. 153 * @throws XMPPErrorException 154 * @throws NoResponseException 155 * @throws NotConnectedException 156 */ 157 public PrivateData getPrivateData(final String elementName, final String namespace) throws NoResponseException, XMPPErrorException, NotConnectedException 158 { 159 // Create an IQ packet to get the private data. 160 IQ privateDataGet = new PrivateDataIQ(elementName, namespace); 161 162 PrivateDataIQ response = connection().createPacketCollectorAndSend( 163 privateDataGet).nextResultOrThrow(); 164 return response.getPrivateData(); 165 } 166 167 /** 168 * Sets a private data value. Each chunk of private data is uniquely identified by an 169 * element name and namespace pair. If private data has already been set with the 170 * element name and namespace, then the new private data will overwrite the old value. 171 * 172 * @param privateData the private data. 173 * @throws XMPPErrorException 174 * @throws NoResponseException 175 * @throws NotConnectedException 176 */ 177 public void setPrivateData(final PrivateData privateData) throws NoResponseException, XMPPErrorException, NotConnectedException { 178 // Create an IQ packet to set the private data. 179 IQ privateDataSet = new PrivateDataIQ(privateData); 180 181 connection().createPacketCollectorAndSend(privateDataSet).nextResultOrThrow(); 182 } 183 184 /** 185 * Returns a String key for a given element name and namespace. 186 * 187 * @param elementName the element name. 188 * @param namespace the namespace. 189 * @return a unique key for the element name and namespace pair. 190 */ 191 private static String getProviderKey(String elementName, String namespace) { 192 StringBuilder buf = new StringBuilder(); 193 buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); 194 return buf.toString(); 195 } 196 197 /** 198 * An IQ provider to parse IQ results containing private data. 199 */ 200 public static class PrivateDataIQProvider extends IQProvider<PrivateDataIQ> { 201 202 @Override 203 public PrivateDataIQ parse(XmlPullParser parser, int initialDepth) 204 throws XmlPullParserException, IOException, SmackException { 205 PrivateData privateData = null; 206 boolean done = false; 207 while (!done) { 208 int eventType = parser.next(); 209 if (eventType == XmlPullParser.START_TAG) { 210 String elementName = parser.getName(); 211 String namespace = parser.getNamespace(); 212 // See if any objects are registered to handle this private data type. 213 PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace); 214 // If there is a registered provider, use it. 215 if (provider != null) { 216 privateData = provider.parsePrivateData(parser); 217 } 218 // Otherwise, use a DefaultPrivateData instance to store the private data. 219 else { 220 DefaultPrivateData data = new DefaultPrivateData(elementName, namespace); 221 boolean finished = false; 222 while (!finished) { 223 int event = parser.next(); 224 if (event == XmlPullParser.START_TAG) { 225 String name = parser.getName(); 226 // If an empty element, set the value with the empty string. 227 if (parser.isEmptyElementTag()) { 228 data.setValue(name,""); 229 } 230 // Otherwise, get the the element text. 231 else { 232 event = parser.next(); 233 if (event == XmlPullParser.TEXT) { 234 String value = parser.getText(); 235 data.setValue(name, value); 236 } 237 } 238 } 239 else if (event == XmlPullParser.END_TAG) { 240 if (parser.getName().equals(elementName)) { 241 finished = true; 242 } 243 } 244 } 245 privateData = data; 246 } 247 } 248 else if (eventType == XmlPullParser.END_TAG) { 249 if (parser.getName().equals("query")) { 250 done = true; 251 } 252 } 253 } 254 return new PrivateDataIQ(privateData); 255 } 256 } 257}