From 3ac4b1c121cf5735497aa0436f34596a22fba475 Mon Sep 17 00:00:00 2001 From: Matt Tucker Date: Thu, 8 May 2003 15:55:06 +0000 Subject: [PATCH] Added packet extension support. Properties now use the element name. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@1926 b35dd754-fafc-0310-a699-88a17e54d16e --- .../org/jivesoftware/smack/PacketReader.java | 181 +++++------------ .../org/jivesoftware/smack/packet/Packet.java | 183 ++++++++++++------ 2 files changed, 175 insertions(+), 189 deletions(-) diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java index 99f581466..6e8a5049c 100644 --- a/source/org/jivesoftware/smack/PacketReader.java +++ b/source/org/jivesoftware/smack/PacketReader.java @@ -58,56 +58,18 @@ import java.util.*; import java.util.List; import java.io.ObjectInputStream; import java.io.ByteArrayInputStream; -import java.net.URL; import java.beans.PropertyDescriptor; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.provider.*; /** * Listens for XML traffic from the XMPP server and parses it into packet objects. * The packet reader also manages all packet listeners and collectors.

* - * By default, this class 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 namespace="jabber:iq:time" className="org.jivesoftware.smack.packet.Time"/>
- * </smackProviders>
- * - * Each IQ provider is associated with 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. - * * @see PacketCollector * @see PacketListener * @author Matt Tucker @@ -120,61 +82,6 @@ class PacketReader { private static final String PROPERTIES_NAMESPACE = "http://www.jivesoftware.com/xmlns/xmpp/properties"; - private static Map iqProviders = new Hashtable(); - - static { - // Load IQ processing providers. - try { - Enumeration enum = PacketReader.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")) { - String namespace = parser.getAttributeValue(0); - // Only add the provider for the namespace if one isn't - // already registered. - if (!iqProviders.containsKey(namespace)) { - String providerClass = parser.getAttributeValue(1); - // 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(providerClass); - if (IQProvider.class.isAssignableFrom(provider)) { - iqProviders.put(namespace, provider.newInstance()); - } - else if (IQ.class.isAssignableFrom(provider)) { - iqProviders.put(namespace, provider); - } - } - catch (ClassNotFoundException cnfe) { - cnfe.printStackTrace(); - } - } - } - } - eventType = parser.next(); - } while (eventType != XmlPullParser.END_DOCUMENT); - } - finally { - try { providerStream.close(); } - catch (Exception e) { } - } - } - } - catch (Exception e) { } - } - private Thread readerThread; private Thread listenerThread; @@ -428,39 +335,34 @@ class PacketReader { boolean done = false; while (!done) { int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { - if (parser.getName().equals("query")) { - String namespace = parser.getNamespace(); - if (namespace.equals("jabber:iq:auth")) { - iqPacket = parseAuthentication(parser); - } - else if (namespace.equals("jabber:iq:roster")) { - iqPacket = parseRoster(parser); - } - else if (namespace.equals("jabber:iq:register")) { - iqPacket = parseRegistration(parser); - } - // Otherwise, see if there is a registered provider for - // this namespace. - else { - Object provider = iqProviders.get(namespace); - if (provider != null) { - if (provider instanceof IQProvider) { - iqPacket = ((IQProvider)provider).parseIQ(parser); - } - else if (provider instanceof Class) { - iqPacket = parseIQWithIntrospection((Class)provider, parser); - } - } - } - } - else if (parser.getName().equals("error")) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("error")) { error = parseError(parser); } - else if (parser.getName().equals("x") && - parser.getNamespace().equals(PROPERTIES_NAMESPACE)) - { - properties = parseProperties(parser); + else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) { + iqPacket = parseAuthentication(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) { + iqPacket = parseRoster(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) { + iqPacket = parseRegistration(parser); + } + // Otherwise, see if there is a registered provider for + // this element name and namespace. + else { + Object provider = ProviderManager.getIQProvider(elementName, namespace); + if (provider != null) { + if (provider instanceof IQProvider) { + iqPacket = ((IQProvider)provider).parseIQ(parser); + } + else if (provider instanceof Class) { + iqPacket = parseIQWithIntrospection((Class)provider, parser); + } + } } } else if (eventType == XmlPullParser.END_TAG) { @@ -696,29 +598,44 @@ class PacketReader { while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.START_TAG) { - if (parser.getName().equals("subject")) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("subject")) { if (subject == null) { subject = parser.nextText(); } } - else if (parser.getName().equals("body")) { + else if (elementName.equals("body")) { if (body == null) { body = parser.nextText(); } } - else if (parser.getName().equals("thread")) { + else if (elementName.equals("thread")) { if (thread == null) { thread = parser.nextText(); } } - else if (parser.getName().equals("error")) { + else if (elementName.equals("error")) { message.setError(parseError(parser)); } - else if (parser.getName().equals("x") && - parser.getNamespace().equals(PROPERTIES_NAMESPACE)) + else if (elementName.equals("properties") && + namespace.equals(PROPERTIES_NAMESPACE)) { properties = parseProperties(parser); } + // Otherwise, see if there is a registered provider for + // this element name and namespace. + else { + Object provider = ProviderManager.getExtensionProvider(elementName, namespace); + if (provider != null) { + if (provider instanceof PacketExtensionProvider) { + iqPacket = ((IQProvider)provider).parseIQ(parser); + } + else if (provider instanceof Class) { + iqPacket = parseIQWithIntrospection((Class)provider, parser); + } + } + } } else if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals("message")) { @@ -775,7 +692,7 @@ class PacketReader { else if (parser.getName().equals("error")) { presence.setError(parseError(parser)); } - else if (parser.getName().equals("x") && + else if (parser.getName().equals("properties") && parser.getNamespace().equals(PROPERTIES_NAMESPACE)) { Map properties = parseProperties(parser); @@ -851,7 +768,7 @@ class PacketReader { } } else if (eventType == XmlPullParser.END_TAG) { - if (parser.getName().equals("x")) { + if (parser.getName().equals("properties")) { break; } } diff --git a/source/org/jivesoftware/smack/packet/Packet.java b/source/org/jivesoftware/smack/packet/Packet.java index 0d9d26493..f02317443 100644 --- a/source/org/jivesoftware/smack/packet/Packet.java +++ b/source/org/jivesoftware/smack/packet/Packet.java @@ -95,6 +95,7 @@ public abstract class Packet { private String packetID = null; private String to = null; private String from = null; + private List packetExtensions = null; private Map properties = null; private XMPPError error = null; @@ -183,6 +184,68 @@ public abstract class Packet { this.error = error; } + /** + * Returns an Iterator for the packet extensions attached to the packet. + * + * @return an Iterator for the packet extensions. + */ + public synchronized Iterator getExtensions() { + if (packetExtensions == null) { + return Collections.EMPTY_LIST.iterator(); + } + return Collections.unmodifiableList(new ArrayList(packetExtensions)).iterator(); + } + + /** + * Returns the first packet extension that matches the specified element name and + * namespace, or null if it doesn't exist. Packet extensions are + * are arbitrary XML sub-documents in standard XMPP packets (except for IQ packets, + * which don't allow extensions). By default, a DefaultPacketExtension instance will + * be returned for each extension. However, PacketExtensionProvider instances can be + * registered with the {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager} + * class to handle custom parsing. In that case, the type of the Object + * will be determined by the provider. + * + * @param elementName the XML element name of the packet extension. + * @param namespace the XML element namespace of the packet extension. + * @return the extension, or null if it doesn't exist. + */ + public synchronized PacketExtension getExtension(String elementName, String namespace) { + if (packetExtensions == null || elementName == null || namespace == null) { + return null; + } + for (Iterator i=packetExtensions.iterator(); i.hasNext(); ) { + PacketExtension ext = (PacketExtension)i.next(); + if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) { + return ext; + } + } + return null; + } + + /** + * Adds a packet extension to the packet. + * + * @param extension a packet extension. + */ + public synchronized void addExtension(PacketExtension extension) { + if (packetExtensions == null) { + packetExtensions = new ArrayList(); + } + packetExtensions.add(extension); + } + + /** + * Removes a packet extension from the packet. + * + * @param extension the packet extension to remove. + */ + public synchronized void removeExtension(PacketExtension extension) { + if (packetExtensions != null) { + packetExtensions.remove(extension); + } + } + /** * Returns the packet property with the specified name or null if the * property doesn't exist. Property values that were orginally primitives will @@ -292,72 +355,78 @@ public abstract class Packet { /** * Returns the packet as XML. Every concrete extension of Packet must implement - * this method. In addition to writing out packet-specific data, each extension should - * also write out the error and the properties data if they are defined. + * this method. In addition to writing out packet-specific data, every sub-class + * should also write out the error and the extensions data if they are defined. * * @return the XML format of the packet as a String. */ public abstract String toXML(); /** - * Returns the properties portion of the packet as XML or null if there are - * no properties. + * Returns the extension sub-packets (including properties data) as an XML + * String, or the Empty String if there are no packet extensions. * - * @return the properties data as XML or null if there are no properties. + * @return the extension sub-packets as XML or the Empty String if there + * are no packet extensions. */ - protected synchronized String getPropertiesXML() { - // Return null if there are no properties. - if (properties == null || properties.isEmpty()) { - return null; - } + protected synchronized String getExtentionsXML() { StringBuffer buf = new StringBuffer(); - buf.append(""); - // Loop through all properties and write them out. - for (Iterator i=getPropertyNames(); i.hasNext(); ) { - String name = (String)i.next(); - Object value = getProperty(name); - buf.append(""); - buf.append("").append(StringUtils.escapeForXML(name)).append(""); - buf.append("").append(value).append(""); - } - else if (value instanceof Long) { - buf.append("long\">").append(value).append(""); - } - else if (value instanceof Float) { - buf.append("float\">").append(value).append(""); - } - else if (value instanceof Double) { - buf.append("double\">").append(value).append(""); - } - else if (value instanceof Boolean) { - buf.append("boolean\">").append(value).append(""); - } - else if (value instanceof String) { - buf.append("string\">"); - buf.append(StringUtils.escapeForXML((String)value)); - buf.append(""); - } - // Otherwise, it's a generic Serializable object. Serialized objects are in - // a binary format, which won't work well inside of XML. Therefore, we base-64 - // encode the binary data before adding it to the SOAP payload. - else { - try { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(byteStream); - out.writeObject(value); - String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray()); - buf.append("java-object\">"); - buf.append(encodedVal).append(""); - } - catch (Exception e) { - - } - } - buf.append(""); + // Add in all standard extension sub-packets. + Iterator extensions = getExtensions(); + while (extensions.hasNext()) { + PacketExtension extension = (PacketExtension)extensions.next(); + buf.append(extension.toXML()); + } + // Add in packet properties. + if (properties != null && !properties.isEmpty()) { + buf.append(""); + // Loop through all properties and write them out. + for (Iterator i=getPropertyNames(); i.hasNext(); ) { + String name = (String)i.next(); + Object value = getProperty(name); + buf.append(""); + buf.append("").append(StringUtils.escapeForXML(name)).append(""); + buf.append("").append(value).append(""); + } + else if (value instanceof Long) { + buf.append("long\">").append(value).append(""); + } + else if (value instanceof Float) { + buf.append("float\">").append(value).append(""); + } + else if (value instanceof Double) { + buf.append("double\">").append(value).append(""); + } + else if (value instanceof Boolean) { + buf.append("boolean\">").append(value).append(""); + } + else if (value instanceof String) { + buf.append("string\">"); + buf.append(StringUtils.escapeForXML((String)value)); + buf.append(""); + } + // Otherwise, it's a generic Serializable object. Serialized objects are in + // a binary format, which won't work well inside of XML. Therefore, we base-64 + // encode the binary data before adding it. + else { + try { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(byteStream); + out.writeObject(value); + String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray()); + buf.append("java-object\">"); + buf.append(encodedVal).append(""); + } + catch (Exception e) { + + } + } + buf.append(""); + } + buf.append(""); } - buf.append(""); return buf.toString(); } -} +} \ No newline at end of file