From cdd78ecbe6972d9d3cdfe1150d12ac5d28a9d7bc Mon Sep 17 00:00:00 2001 From: Matt Tucker Date: Thu, 8 May 2003 15:53:40 +0000 Subject: [PATCH] Initial check-in. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@1924 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smack/packet/DefaultPacketExtension.java | 156 ++++++++++ .../smack/packet/PacketExtension.java | 14 + .../smack/provider/IQProvider.java | 79 +++++ .../provider/PacketExtensionProvider.java | 78 +++++ .../smack/provider/ProviderManager.java | 279 ++++++++++++++++++ 5 files changed, 606 insertions(+) create mode 100644 source/org/jivesoftware/smack/packet/DefaultPacketExtension.java create mode 100644 source/org/jivesoftware/smack/packet/PacketExtension.java create mode 100644 source/org/jivesoftware/smack/provider/IQProvider.java create mode 100644 source/org/jivesoftware/smack/provider/PacketExtensionProvider.java create mode 100644 source/org/jivesoftware/smack/provider/ProviderManager.java diff --git a/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java b/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java new file mode 100644 index 000000000..df14aa08b --- /dev/null +++ b/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java @@ -0,0 +1,156 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.packet; + +import java.util.*; + +/** + * Default implementation of the PacketExtension interface. Unless a PacketExtensionProvider + * is registered with {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}, + * instances of this class will be returned when getting packet extensions.

+ * + * This class provides a very simple representation of an XML sub-document. Each element + * is a key in a Map with its CDATA being the value. For example, given the following + * XML sub-document: + * + *

+ * <foo xmlns="http://bar.com">
+ *     <color>blue</color>
+ *     <food>pizza</food>
+ * </foo>
+ * + * In this case, getValue("color") would return "blue", and getValue("food") would + * return "pizza". This parsing mechanism mechanism is very simplistic and will not work + * as desired in all cases (for example, if some of the elements have attributes. In those + * cases, a custom PacketExtensionProvider should be used. + * + * @author Matt Tucker + */ +public class DefaultPacketExtension implements PacketExtension { + + private String elementName; + private String namespace; + private Map map; + + /** + * Creates a new generic packet extension. + * + * @param elementName the name of the element of the XML sub-document. + * @param namespace the namespace of the element. + */ + public DefaultPacketExtension(String elementName, String namespace) { + this.elementName = elementName; + this.namespace = namespace; + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return elementName; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return namespace; + } + + public String toXML() { + return null; + } + + /** + * Returns an Iterator for the names that can be used to get + * values of the packet extension. + * + * @return an Iterator for the names. + */ + public synchronized Iterator getNames() { + if (map == null) { + Collections.EMPTY_LIST.iterator(); + } + return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator(); + } + + /** + * Returns a packet extension value given a name. + * + * @param name the name. + * @return the value. + */ + public synchronized String getValue(String name) { + if (map == null) { + return null; + } + return (String)map.get(name); + } + + /** + * Sets a packet extension value using the given name. + * + * @param name the name. + * @param value the value. + */ + public synchronized void setValue(String name, String value) { + if (map == null) { + map = new HashMap(); + } + map.put(name, value); + } +} diff --git a/source/org/jivesoftware/smack/packet/PacketExtension.java b/source/org/jivesoftware/smack/packet/PacketExtension.java new file mode 100644 index 000000000..29746c299 --- /dev/null +++ b/source/org/jivesoftware/smack/packet/PacketExtension.java @@ -0,0 +1,14 @@ +package org.jivesoftware.smack.packet; + +/** + * + * @author Matt Tucker + */ +public interface PacketExtension { + + public String getElementName(); + + public String getNamespace(); + + public String toXML(); +} diff --git a/source/org/jivesoftware/smack/provider/IQProvider.java b/source/org/jivesoftware/smack/provider/IQProvider.java new file mode 100644 index 000000000..464601930 --- /dev/null +++ b/source/org/jivesoftware/smack/provider/IQProvider.java @@ -0,0 +1,79 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.xmlpull.v1.XmlPullParser; + +/** + * An interface for parsing custom IQ packets. Each IQProvider must be registered with + * the ProviderManager class for it to be used. Every implementation of this + * interface must have a public, no-argument constructor. + * + * @author Matt Tucker + */ +public interface IQProvider { + + /** + * Parse the IQ sub-document and create an IQ instance. Each IQ must have a single + * sub-element At + * the beginning of the method call, the xml parser will be positioned directly + * after the opening <query> tag of the IQ packet. At the end of the method call, + * the parser must be positioned directly after the closing <query/> tag. + * + * @param parser an XML parser. + * @return a new IQ instance. + * @throws Exception if an error occurs parsing the XML. + */ + public IQ parseIQ(XmlPullParser parser) throws Exception; +} \ No newline at end of file diff --git a/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java b/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java new file mode 100644 index 000000000..099b0ffec --- /dev/null +++ b/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java @@ -0,0 +1,78 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.xmlpull.v1.XmlPullParser; + +/** + * An interface for parsing custom packets extensions. Each PacketExtensionProvider must + * be registered with the ProviderManager class for it to be used. Every implementation + * of this interface must have a public, no-argument constructor. + * + * @author Matt Tucker + */ +public interface PacketExtensionProvider { + + /** + * Parse an extension sub-packet and create a PacketExtension instance. At + * the beginning of the method call, the xml parser will be positioned on the + * opening element of the packet extension. At the end of the method call, the + * parser must be positioned on the closing element of the packet extension. + * + * @param parser an XML parser. + * @return a new IQ instance. + * @throws java.lang.Exception if an error occurs parsing the XML. + */ + public PacketExtension parseExtensions(XmlPullParser parser) throws Exception; +} diff --git a/source/org/jivesoftware/smack/provider/ProviderManager.java b/source/org/jivesoftware/smack/provider/ProviderManager.java new file mode 100644 index 000000000..ab097d6a3 --- /dev/null +++ b/source/org/jivesoftware/smack/provider/ProviderManager.java @@ -0,0 +1,279 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.xmlpull.v1.*; + +import java.util.*; +import java.net.URL; + +/** + * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of + * providers exist: + * + * IQProvider

+ * + * By default, Smack only knows how to process IQ packets with query sub-packets that + * are in a few namespaces:

+ * + * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing + * mechanism is provided. IQ providers are registered by creating a smack.providers file + * in the WEB-INF directory of your JAR file. The file is an XML document that contains + * one or more iqProvider entries, as in the following example: + * + *
+ * <?xml version="1.0"?>
+ * <smackProviders>
+ *     <iqProvider>
+ *         <elementName>query</elementName>
+ *         <namespace>jabber:iq:time</namespace>
+ *         <className>org.jivesoftware.smack.packet.Time</className>
+ *     </iqProvider>
+ * </smackProviders>
+ * + * Each IQ provider is associated with an element name and a namespace. If multiple provider entries attempt to + * register to handle the same namespace, the first entry loaded from the classpath will + * take precedence. The IQ provider class can either implement the IQProvider interface, + * or extend the IQ class. In the former case, each IQProvider is responsible for parsing + * the raw XML stream to create an IQ instance. In the latter case, bean introspection is + * used to try to automatically set properties of the IQ instance using the values found + * in the IQ packet XML. For example, an XMPP time packet resembles the following: + *
+ * <iq type='get' to='joe@example.com' from='mary@example.com' id='time_1'>
+ *     <query xmlns='jabber:iq:time'>
+ *         <utc>20020910T17:58:35</utc>
+ *         <tz>MDT</tz>
+ *         <display>Tue Sep 10 12:58:35 2002</display>
+ *     </query>
+ * </iq>
+ * + * In order for this packet to be automatically mapped to the Time object listed in the + * providers file above, it must have the methods setUtc(String), setTz(String), and + * setDisplay(tz). The introspection service will automatically try to convert the String + * value from the XML into a boolean, int, long, float, double, or Class depending on the + * type the IQ instance expects.

+ * + * A pluggable system for the <x> portion of packets also exists. Each x provider + * is registered with a name space in the smack.providers file as in the following example: + * + *

+ * <?xml version="1.0"?>
+ * <smackProviders>
+ *     <xProvider namespace="jabber:iq:event"
+ *                   className="org.jivesoftware.smack.packet.MessageEvent"/>
+ * </smackProviders>
+ * + * If multiple provider entries attempt to register to handle the same namespace, the + * first entry loaded from the classpath will take precedence. Whenever an <x> element + * is found in a packet, parsing will be passed to the correct provider. Each provider + * can either implement the XProvider or be a standard Java Bean. In the former case, + * each XProvider is responsible for parsing the raw XML stream to contruct an object. + * In the latter case, bean introspection is used to try to automatically set the + * properties of the class using the values in the x sub-element. When a XProvider is + * not registered for a namespace, Smack will store all top-level elements of the sub-packet + * in a Map and attach it to the packet. + * + * @author Matt Tucker + */ +public class ProviderManager { + + private static Map extensionProviders = new Hashtable(); + private static Map iqProviders = new Hashtable(); + + static { + // Load IQ processing providers. + try { + Enumeration enum = ProviderManager.class.getClassLoader().getResources( + "WEB-INF/smack.providers"); + while (enum.hasMoreElements()) { + URL url = (URL)enum.nextElement(); + java.io.InputStream providerStream = null; + try { + providerStream = url.openStream(); + XmlPullParser parser = getParserInstance(); + parser.setInput(providerStream, "UTF-8"); + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("iqProvider")) { + parser.next(); + String elementName = parser.nextText(); + parser.next(); + String namespace = parser.nextText(); + parser.next(); + String className = parser.nextText(); + parser.next(); + // Only add the provider for the namespace if one isn't + // already registered. + String key = getProviderKey(elementName, namespace); + if (!iqProviders.containsKey(key)) { + // Attempt to load the provider class and then create + // a new instance if it's an IQProvider. Otherwise, if it's + // an IQ class, add the class object itself, then we'll use + // reflection later to create instances of the class. + try { + // Add the provider to the map. + Class provider = Class.forName(className); + if (IQProvider.class.isAssignableFrom(provider)) { + iqProviders.put(key, provider.newInstance()); + } + else if (IQ.class.isAssignableFrom(provider)) { + iqProviders.put(key, provider); + } + } + catch (ClassNotFoundException cnfe) { + cnfe.printStackTrace(); + } + } + } + else if (parser.getName().equals("extensionProvider")) { + parser.next(); + String elementName = parser.nextText(); + parser.next(); + String namespace = parser.nextText(); + parser.next(); + String className = parser.nextText(); + parser.next(); + // Only add the provider for the namespace if one isn't + // already registered. + String key = getProviderKey(elementName, namespace); + if (!extensionProviders.containsKey(key)) { + // Attempt to load the provider class and then create + // a new instance if it's a Provider. Otherwise, if it's + // a PacketExtension, add the class object itself and + // then we'll use reflection later to create instances + // of the class. + try { + // Add the provider to the map. + Class provider = Class.forName(className); + if (PacketExtensionProvider.class.isAssignableFrom(provider)) { + extensionProviders.put(key, provider.newInstance()); + } + else if (PacketExtension.class.isAssignableFrom(provider)) { + extensionProviders.put(key, provider); + } + } + catch (ClassNotFoundException cnfe) { + cnfe.printStackTrace(); + } + } + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } + finally { + try { providerStream.close(); } + catch (Exception e) { } + } + } + } + catch (Exception e) { } + } + + public synchronized static Object getIQProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + return iqProviders.get(key); + } + + public synchronized static Object getExtensionProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + return extensionProviders.get(key); + } + + private static String getProviderKey(String elementName, String nameSpace) { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(elementName).append("/><").append(nameSpace).append("/>"); + return buf.toString(); + } + + /** + * Returns an XML parser instance. + * + * @return an XML parser instance. + */ + private static XmlPullParser getParserInstance() { + XmlPullParser parser = null; + try { + final String defaultProviderName = "org.xmlpull.mxp1.MXParserFactory"; + XmlPullParserFactory factory = null; + try { + // Attempt to load a factory implementation using a system property + // and a classloader context. + factory = XmlPullParserFactory.newInstance( + System.getProperty(XmlPullParserFactory.PROPERTY_NAME), + Thread.currentThread().getContextClassLoader().getClass()); + } + catch (Exception e) { + if (factory == null) { + // Loading failed. Therefore, use the hardcoded default. + factory = XmlPullParserFactory.newInstance(defaultProviderName, null); + } + } + factory.setNamespaceAware(true); + parser = factory.newPullParser(); + } + catch (XmlPullParserException xppe) { + xppe.printStackTrace(); + } + return parser; + } +}