Java tutorial
/** * $RCSfile$ * $Revision: 6565 $ * $Date: 2007-01-04 16:25:02 -0800 (Thu, 04 Jan 2007) $ * * Copyright 2004 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.xmpp.packet; import org.dom4j.DocumentFactory; import org.dom4j.Element; import org.dom4j.QName; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.util.List; /** * An XMPP packet (also referred to as a stanza). Each packet is backed by a * DOM4J Element. A set of convenience methods allows easy manipulation of * the Element, or the Element can be accessed directly and manipulated.<p> * * There are three core packet types:<ul> * <li>{@link Message} -- used to send data between users. * <li>{@link Presence} -- contains user presence information or is used * to manage presence subscriptions. * <li>{@link IQ} -- exchange information and perform queries using a * request/response protocol. * </ul> * * @author Matt Tucker */ public abstract class Packet { protected static DocumentFactory docFactory = DocumentFactory.getInstance(); protected Element element; // Cache to and from JIDs protected JID toJID; protected JID fromJID; /** * Constructs a new Packet. The TO address contained in the XML Element will only be * validated. In other words, stringprep operations will only be performed on the TO JID to * verify that it is well-formed. The FROM address is assigned by the server so there is no * need to verify it. * * @param element the XML Element that contains the packet contents. */ public Packet(Element element) { this(element, false); } /** * Constructs a new Packet. The JID address contained in the XML Element may not be * validated. When validation can be skipped then stringprep operations will not be performed * on the JIDs to verify that addresses are well-formed. However, when validation cannot be * skipped then <tt>only</tt> the TO address will be verified. The FROM address is assigned by * the server so there is no need to verify it. * * @param element the XML Element that contains the packet contents. * @param skipValidation true if stringprep should not be applied to the TO address. */ public Packet(Element element, boolean skipValidation) { this.element = element; // Apply stringprep profiles to the "to" and "from" values. String to = element.attributeValue("to"); if (to != null) { String[] parts = JID.getParts(to); toJID = new JID(parts[0], parts[1], parts[2], skipValidation); element.addAttribute("to", toJID.toString()); } String from = element.attributeValue("from"); if (from != null) { String[] parts = JID.getParts(from); fromJID = new JID(parts[0], parts[1], parts[2], true); element.addAttribute("from", fromJID.toString()); } } /** * Constructs a new Packet with no element data. This method is used by * extensions of this class that require a more optimized path for creating * new packets. */ protected Packet() { } /** * Returns the packet ID, or <tt>null</tt> if the packet does not have an ID. * Packet ID's are optional, except for IQ packets. * * @return the packet ID. */ public String getID() { return element.attributeValue("id"); } /** * Sets the packet ID. Packet ID's are optional, except for IQ packets. * * @param ID the packet ID. */ public void setID(String ID) { element.addAttribute("id", ID); } /** * Returns the XMPP address (JID) that the packet is addressed to, or <tt>null</tt> * if the "to" attribute is not set. The XMPP protocol often makes the "to" * attribute optional, so it does not always need to be set. * * @return the XMPP address (JID) that the packet is addressed to, or <tt>null</tt> * if not set. */ public JID getTo() { String to = element.attributeValue("to"); if (to == null) { return null; } else { if (toJID != null && to.equals(toJID.toString())) { return toJID; } else { // Return a new JID that bypasses stringprep profile checking. // This improves speed and is safe as long as the user doesn't // directly manipulate the attributes of the underlying Element // that represent JID's. String[] parts = JID.getParts(to); toJID = new JID(parts[0], parts[1], parts[2], true); return toJID; } } } /** * Sets the XMPP address (JID) that the packet is addressed to. The XMPP protocol * often makes the "to" attribute optional, so it does not always need to be set. * * @param to the XMPP address (JID) that the packet is addressed to. */ public void setTo(String to) { // Apply stringprep profiles to value. if (to != null) { toJID = new JID(to); to = toJID.toString(); } element.addAttribute("to", to); } /** * Sets the XMPP address (JID) that the packet is address to. The XMPP protocol * often makes the "to" attribute optional, so it does not always need to be set. * * @param to the XMPP address (JID) that the packet is addressed to. */ public void setTo(JID to) { toJID = to; if (to == null) { element.addAttribute("to", null); } else { element.addAttribute("to", to.toString()); } } /** * Returns the XMPP address (JID) that the packet is from, or <tt>null</tt> * if the "from" attribute is not set. The XMPP protocol often makes the "from" * attribute optional, so it does not always need to be set. * * @return the XMPP address that the packet is from, or <tt>null</tt> * if not set. */ public JID getFrom() { String from = element.attributeValue("from"); if (from == null) { return null; } else { if (fromJID != null && from.equals(fromJID.toString())) { return fromJID; } else { // Return a new JID that bypasses stringprep profile checking. // This improves speed and is safe as long as the user doesn't // directly manipulate the attributes of the underlying Element // that represent JID's. String[] parts = JID.getParts(from); fromJID = new JID(parts[0], parts[1], parts[2], true); return fromJID; } } } /** * Sets the XMPP address (JID) that the packet comes from. The XMPP protocol * often makes the "from" attribute optional, so it does not always need to be set. * * @param from the XMPP address (JID) that the packet comes from. */ public void setFrom(String from) { // Apply stringprep profiles to value. if (from != null) { fromJID = new JID(from); from = fromJID.toString(); } element.addAttribute("from", from); } /** * Sets the XMPP address (JID) that the packet comes from. The XMPP protocol * often makes the "from" attribute optional, so it does not always need to be set. * * @param from the XMPP address (JID) that the packet comes from. */ public void setFrom(JID from) { fromJID = from; if (from == null) { element.addAttribute("from", null); } else { element.addAttribute("from", from.toString()); } } /** * Adds the element contained in the PacketExtension to the element of this packet. * It is important that this is the first and last time the element contained in * PacketExtension is added to another Packet. Otherwise, a runtime error will be * thrown when trying to add the PacketExtension's element to the Packet's element. * Future modifications to the PacketExtension will be reflected in this Packet. * * @param extension the PacketExtension whose element will be added to this Packet's element. */ public void addExtension(PacketExtension extension) { element.add(extension.getElement()); } /** * Returns a {@link PacketExtension} on the first element found in this packet * for the specified <tt>name</tt> and <tt>namespace</tt> or <tt>null</tt> if * none was found. * * @param name the child element name. * @param namespace the child element namespace. * @return a PacketExtension on the first element found in this packet for the specified * name and namespace or null if none was found. */ public PacketExtension getExtension(String name, String namespace) { List extensions = element.elements(QName.get(name, namespace)); if (!extensions.isEmpty()) { Class extensionClass = PacketExtension.getExtensionClass(name, namespace); // If a specific PacketExtension implementation has been registered, use that. if (extensionClass != null) { try { Constructor constructor = extensionClass.getDeclaredConstructor(Element.class); return (PacketExtension) constructor.newInstance(extensions.get(0)); } catch (Exception e) { // Ignore. } } // Otherwise, use a normal PacketExtension. else { return new PacketExtension((Element) extensions.get(0)); } } return null; } /** * Deletes the first element whose element name and namespace matches the specified * element name and namespace.<p> * * Notice that this method may remove any child element that matches the specified * element name and namespace even if that element was not added to the Packet using a * {@link PacketExtension}. * * * @param name the child element name. * @param namespace the child element namespace. * @return true if a child element was removed. */ public boolean deleteExtension(String name, String namespace) { List extensions = element.elements(QName.get(name, namespace)); if (!extensions.isEmpty()) { element.remove((Element) extensions.get(0)); return true; } return false; } /** * Returns the packet error, or <tt>null</tt> if there is no packet error. * * @return the packet error. */ public PacketError getError() { Element error = element.element("error"); if (error != null) { return new PacketError(error); } return null; } /** * Sets the packet error. Calling this method will automatically set * the packet "type" attribute to "error". * * @param error the packet error. */ public void setError(PacketError error) { if (element == null) { throw new NullPointerException("Error cannot be null"); } // Force the packet type to "error". element.addAttribute("type", "error"); // Remove an existing error packet. if (element.element("error") != null) { element.remove(element.element("error")); } // Add the error element. element.add(error.getElement()); } /** * Sets the packet error using the specified condition. Calling this * method will automatically set the packet "type" attribute to "error". * This is a convenience method equivalent to calling: * * <tt>setError(new PacketError(condition));</tt> * * @param condition the error condition. */ public void setError(PacketError.Condition condition) { setError(new PacketError(condition)); } /** * Creates a deep copy of this packet. * * @return a deep copy of this packet. */ public abstract Packet createCopy(); /** * Returns the DOM4J Element that backs the packet. The element is the definitive * representation of the packet and can be manipulated directly to change * packet contents. * * @return the DOM4J Element that represents the packet. */ public Element getElement() { return element; } /** * Returns the textual XML representation of this packet. * * @return the textual XML representation of this packet. */ public String toXML() { return element.asXML(); } public String toString() { StringWriter out = new StringWriter(); XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint()); try { writer.write(element); } catch (Exception e) { // Ignore. } return out.toString(); } }