diff options
Diffstat (limited to 'src/org/jivesoftware/smackx/PrivateDataManager.java')
-rw-r--r-- | src/org/jivesoftware/smackx/PrivateDataManager.java | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/PrivateDataManager.java b/src/org/jivesoftware/smackx/PrivateDataManager.java new file mode 100644 index 0000000..c6440bc --- /dev/null +++ b/src/org/jivesoftware/smackx/PrivateDataManager.java @@ -0,0 +1,359 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.packet.DefaultPrivateData; +import org.jivesoftware.smackx.packet.PrivateData; +import org.jivesoftware.smackx.provider.PrivateDataProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Hashtable; +import java.util.Map; + +/** + * Manages private data, which is a mechanism to allow users to store arbitrary XML + * data on an XMPP server. Each private data chunk is defined by a element name and + * XML namespace. Example private data: + * + * <pre> + * <color xmlns="http://example.com/xmpp/color"> + * <favorite>blue</blue> + * <leastFavorite>puce</leastFavorite> + * </color> + * </pre> + * + * {@link PrivateDataProvider} instances are responsible for translating the XML into objects. + * If no PrivateDataProvider is registered for a given element name and namespace, then + * a {@link DefaultPrivateData} instance will be returned.<p> + * + * Warning: this is an non-standard protocol documented by + * <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>. Because this is a + * non-standard protocol, it is subject to change. + * + * @author Matt Tucker + */ +public class PrivateDataManager { + + /** + * Map of provider instances. + */ + private static Map<String, PrivateDataProvider> privateDataProviders = new Hashtable<String, PrivateDataProvider>(); + + /** + * Returns the private data provider registered to the specified XML element name and namespace. + * For example, if a provider was registered to the element name "prefs" and the + * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger + * the provider: + * + * <pre> + * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> + * <query xmlns='jabber:iq:private'> + * <prefs xmlns='http://www.xmppclient.com/prefs'> + * <value1>ABC</value1> + * <value2>XYZ</value2> + * </prefs> + * </query> + * </iq></pre> + * + * <p>Note: this method is generally only called by the internal Smack classes. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @return the PrivateData provider. + */ + public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + return (PrivateDataProvider)privateDataProviders.get(key); + } + + /** + * Adds a private data provider with the specified element name and name space. The provider + * will override any providers loaded through the classpath. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @param provider the private data provider. + */ + public static void addPrivateDataProvider(String elementName, String namespace, + PrivateDataProvider provider) + { + String key = getProviderKey(elementName, namespace); + privateDataProviders.put(key, provider); + } + + /** + * Removes a private data provider with the specified element name and namespace. + * + * @param elementName The XML element name. + * @param namespace The XML namespace. + */ + public static void removePrivateDataProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + privateDataProviders.remove(key); + } + + + private Connection connection; + + /** + * The user to get and set private data for. In most cases, this value should + * be <tt>null</tt>, as the typical use of private data is to get and set + * your own private data and not others. + */ + private String user; + + /** + * Creates a new private data manager. The connection must have + * undergone a successful login before being used to construct an instance of + * this class. + * + * @param connection an XMPP connection which must have already undergone a + * successful login. + */ + public PrivateDataManager(Connection connection) { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to XMPP server."); + } + this.connection = connection; + } + + /** + * Creates a new private data manager for a specific user (special case). Most + * servers only support getting and setting private data for the user that + * authenticated via the connection. However, some servers support the ability + * to get and set private data for other users (for example, if you are the + * administrator). The connection must have undergone a successful login before + * being used to construct an instance of this class. + * + * @param connection an XMPP connection which must have already undergone a + * successful login. + * @param user the XMPP address of the user to get and set private data for. + */ + public PrivateDataManager(Connection connection, String user) { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to XMPP server."); + } + this.connection = connection; + this.user = user; + } + + /** + * Returns the private data specified by the given element name and namespace. Each chunk + * of private data is uniquely identified by an element name and namespace pair.<p> + * + * If a PrivateDataProvider is registered for the specified element name/namespace pair then + * that provider will determine the specific object type that is returned. If no provider + * is registered, a {@link DefaultPrivateData} instance will be returned. + * + * @param elementName the element name. + * @param namespace the namespace. + * @return the private data. + * @throws XMPPException if an error occurs getting the private data. + */ + public PrivateData getPrivateData(final String elementName, final String namespace) + throws XMPPException + { + // Create an IQ packet to get the private data. + IQ privateDataGet = new IQ() { + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>"); + buf.append("</query>"); + return buf.toString(); + } + }; + privateDataGet.setType(IQ.Type.GET); + // Address the packet to the other account if user has been set. + if (user != null) { + privateDataGet.setTo(user); + } + + // Setup a listener for the reply to the set operation. + String packetID = privateDataGet.getPacketID(); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); + + // Send the private data. + connection.sendPacket(privateDataGet); + + // Wait up to five seconds for a response from the server. + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + return ((PrivateDataResult)response).getPrivateData(); + } + + /** + * Sets a private data value. Each chunk of private data is uniquely identified by an + * element name and namespace pair. If private data has already been set with the + * element name and namespace, then the new private data will overwrite the old value. + * + * @param privateData the private data. + * @throws XMPPException if setting the private data fails. + */ + public void setPrivateData(final PrivateData privateData) throws XMPPException { + // Create an IQ packet to set the private data. + IQ privateDataSet = new IQ() { + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + buf.append(privateData.toXML()); + buf.append("</query>"); + return buf.toString(); + } + }; + privateDataSet.setType(IQ.Type.SET); + // Address the packet to the other account if user has been set. + if (user != null) { + privateDataSet.setTo(user); + } + + // Setup a listener for the reply to the set operation. + String packetID = privateDataSet.getPacketID(); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); + + // Send the private data. + connection.sendPacket(privateDataSet); + + // Wait up to five seconds for a response from the server. + IQ response = (IQ)collector.nextResult(5000); + // Stop queuing results + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + } + + /** + * Returns a String key for a given element name and namespace. + * + * @param elementName the element name. + * @param namespace the namespace. + * @return a unique key for the element name and namespace pair. + */ + private static String getProviderKey(String elementName, String namespace) { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); + return buf.toString(); + } + + /** + * An IQ provider to parse IQ results containing private data. + */ + public static class PrivateDataIQProvider implements IQProvider { + public IQ parseIQ(XmlPullParser parser) throws Exception { + PrivateData privateData = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + // See if any objects are registered to handle this private data type. + PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace); + // If there is a registered provider, use it. + if (provider != null) { + privateData = provider.parsePrivateData(parser); + } + // Otherwise, use a DefaultPrivateData instance to store the private data. + else { + DefaultPrivateData data = new DefaultPrivateData(elementName, namespace); + boolean finished = false; + while (!finished) { + int event = parser.next(); + if (event == XmlPullParser.START_TAG) { + String name = parser.getName(); + // If an empty element, set the value with the empty string. + if (parser.isEmptyElementTag()) { + data.setValue(name,""); + } + // Otherwise, get the the element text. + else { + event = parser.next(); + if (event == XmlPullParser.TEXT) { + String value = parser.getText(); + data.setValue(name, value); + } + } + } + else if (event == XmlPullParser.END_TAG) { + if (parser.getName().equals(elementName)) { + finished = true; + } + } + } + privateData = data; + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + return new PrivateDataResult(privateData); + } + } + + /** + * An IQ packet to hold PrivateData GET results. + */ + private static class PrivateDataResult extends IQ { + + private PrivateData privateData; + + PrivateDataResult(PrivateData privateData) { + this.privateData = privateData; + } + + public PrivateData getPrivateData() { + return privateData; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + if (privateData != null) { + privateData.toXML(); + } + buf.append("</query>"); + return buf.toString(); + } + } +}
\ No newline at end of file |