001: /**
002: * Copyright (C) 2004-2007 Jive Software. All rights reserved.
003: *
004: * This software is published under the terms of the GNU Public License (GPL),
005: * a copy of which is included in this distribution.
006: */package org.xmpp.packet;
007:
008: import org.dom4j.DocumentFactory;
009: import org.dom4j.Element;
010: import org.dom4j.QName;
011: import org.dom4j.io.OutputFormat;
012: import org.dom4j.io.XMLWriter;
013:
014: import java.io.StringWriter;
015: import java.lang.reflect.Constructor;
016: import java.util.List;
017:
018: /**
019: * An XMPP packet (also referred to as a stanza). Each packet is backed by a
020: * DOM4J Element. A set of convenience methods allows easy manipulation of
021: * the Element, or the Element can be accessed directly and manipulated.<p>
022: *
023: * There are three core packet types:<ul>
024: * <li>{@link Message} -- used to send data between users.
025: * <li>{@link Presence} -- contains user presence information or is used
026: * to manage presence subscriptions.
027: * <li>{@link IQ} -- exchange information and perform queries using a
028: * request/response protocol.
029: * </ul>
030: *
031: * @author Matt Tucker
032: */
033: public abstract class Packet {
034:
035: protected static DocumentFactory docFactory = DocumentFactory
036: .getInstance();
037:
038: protected Element element;
039:
040: // Cache to and from JIDs
041: protected JID toJID;
042: protected JID fromJID;
043:
044: /**
045: * Constructs a new Packet. The TO address contained in the XML Element will only be
046: * validated. In other words, stringprep operations will only be performed on the TO JID to
047: * verify that it is well-formed. The FROM address is assigned by the server so there is no
048: * need to verify it.
049: *
050: * @param element the XML Element that contains the packet contents.
051: */
052: public Packet(Element element) {
053: this (element, false);
054: }
055:
056: /**
057: * Constructs a new Packet. The JID address contained in the XML Element may not be
058: * validated. When validation can be skipped then stringprep operations will not be performed
059: * on the JIDs to verify that addresses are well-formed. However, when validation cannot be
060: * skipped then <tt>only</tt> the TO address will be verified. The FROM address is assigned by
061: * the server so there is no need to verify it.
062: *
063: * @param element the XML Element that contains the packet contents.
064: * @param skipValidation true if stringprep should not be applied to the TO address.
065: */
066: public Packet(Element element, boolean skipValidation) {
067: this .element = element;
068: // Apply stringprep profiles to the "to" and "from" values.
069: String to = element.attributeValue("to");
070: if (to != null) {
071: if (to.length() == 0) {
072: // Remove empty TO values
073: element.addAttribute("to", null);
074: } else {
075: String[] parts = JID.getParts(to);
076: toJID = new JID(parts[0], parts[1], parts[2],
077: skipValidation);
078: element.addAttribute("to", toJID.toString());
079: }
080: }
081: String from = element.attributeValue("from");
082: if (from != null) {
083: if (from.length() == 0) {
084: // Remove empty FROM values
085: element.addAttribute("from", null);
086: } else {
087: String[] parts = JID.getParts(from);
088: fromJID = new JID(parts[0], parts[1], parts[2], true);
089: element.addAttribute("from", fromJID.toString());
090: }
091: }
092: }
093:
094: /**
095: * Constructs a new Packet with no element data. This method is used by
096: * extensions of this class that require a more optimized path for creating
097: * new packets.
098: */
099: protected Packet() {
100:
101: }
102:
103: /**
104: * Returns the packet ID, or <tt>null</tt> if the packet does not have an ID.
105: * Packet ID's are optional, except for IQ packets.
106: *
107: * @return the packet ID.
108: */
109: public String getID() {
110: return element.attributeValue("id");
111: }
112:
113: /**
114: * Sets the packet ID. Packet ID's are optional, except for IQ packets.
115: *
116: * @param ID the packet ID.
117: */
118: public void setID(String ID) {
119: element.addAttribute("id", ID);
120: }
121:
122: /**
123: * Returns the XMPP address (JID) that the packet is addressed to, or <tt>null</tt>
124: * if the "to" attribute is not set. The XMPP protocol often makes the "to"
125: * attribute optional, so it does not always need to be set.
126: *
127: * @return the XMPP address (JID) that the packet is addressed to, or <tt>null</tt>
128: * if not set.
129: */
130: public JID getTo() {
131: String to = element.attributeValue("to");
132: if (to == null || to.length() == 0) {
133: return null;
134: } else {
135: if (toJID != null && to.equals(toJID.toString())) {
136: return toJID;
137: } else {
138: // Return a new JID that bypasses stringprep profile checking.
139: // This improves speed and is safe as long as the user doesn't
140: // directly manipulate the attributes of the underlying Element
141: // that represent JID's.
142: String[] parts = JID.getParts(to);
143: toJID = new JID(parts[0], parts[1], parts[2], true);
144: return toJID;
145: }
146: }
147: }
148:
149: /**
150: * Sets the XMPP address (JID) that the packet is addressed to. The XMPP protocol
151: * often makes the "to" attribute optional, so it does not always need to be set.
152: *
153: * @param to the XMPP address (JID) that the packet is addressed to.
154: */
155: public void setTo(String to) {
156: // Apply stringprep profiles to value.
157: if (to != null) {
158: toJID = new JID(to);
159: to = toJID.toString();
160: }
161: element.addAttribute("to", to);
162: }
163:
164: /**
165: * Sets the XMPP address (JID) that the packet is address to. The XMPP protocol
166: * often makes the "to" attribute optional, so it does not always need to be set.
167: *
168: * @param to the XMPP address (JID) that the packet is addressed to.
169: */
170: public void setTo(JID to) {
171: toJID = to;
172: if (to == null) {
173: element.addAttribute("to", null);
174: } else {
175: element.addAttribute("to", to.toString());
176: }
177: }
178:
179: /**
180: * Returns the XMPP address (JID) that the packet is from, or <tt>null</tt>
181: * if the "from" attribute is not set. The XMPP protocol often makes the "from"
182: * attribute optional, so it does not always need to be set.
183: *
184: * @return the XMPP address that the packet is from, or <tt>null</tt>
185: * if not set.
186: */
187: public JID getFrom() {
188: String from = element.attributeValue("from");
189: if (from == null || from.length() == 0) {
190: return null;
191: } else {
192: if (fromJID != null && from.equals(fromJID.toString())) {
193: return fromJID;
194: } else {
195: // Return a new JID that bypasses stringprep profile checking.
196: // This improves speed and is safe as long as the user doesn't
197: // directly manipulate the attributes of the underlying Element
198: // that represent JID's.
199: String[] parts = JID.getParts(from);
200: fromJID = new JID(parts[0], parts[1], parts[2], true);
201: return fromJID;
202: }
203: }
204: }
205:
206: /**
207: * Sets the XMPP address (JID) that the packet comes from. The XMPP protocol
208: * often makes the "from" attribute optional, so it does not always need to be set.
209: *
210: * @param from the XMPP address (JID) that the packet comes from.
211: */
212: public void setFrom(String from) {
213: // Apply stringprep profiles to value.
214: if (from != null) {
215: fromJID = new JID(from);
216: from = fromJID.toString();
217: }
218: element.addAttribute("from", from);
219: }
220:
221: /**
222: * Sets the XMPP address (JID) that the packet comes from. The XMPP protocol
223: * often makes the "from" attribute optional, so it does not always need to be set.
224: *
225: * @param from the XMPP address (JID) that the packet comes from.
226: */
227: public void setFrom(JID from) {
228: fromJID = from;
229: if (from == null) {
230: element.addAttribute("from", null);
231: } else {
232: element.addAttribute("from", from.toString());
233: }
234: }
235:
236: /**
237: * Adds the element contained in the PacketExtension to the element of this packet.
238: * It is important that this is the first and last time the element contained in
239: * PacketExtension is added to another Packet. Otherwise, a runtime error will be
240: * thrown when trying to add the PacketExtension's element to the Packet's element.
241: * Future modifications to the PacketExtension will be reflected in this Packet.
242: *
243: * @param extension the PacketExtension whose element will be added to this Packet's element.
244: */
245: public void addExtension(PacketExtension extension) {
246: element.add(extension.getElement());
247: }
248:
249: /**
250: * Returns a {@link PacketExtension} on the first element found in this packet
251: * for the specified <tt>name</tt> and <tt>namespace</tt> or <tt>null</tt> if
252: * none was found.
253: *
254: * @param name the child element name.
255: * @param namespace the child element namespace.
256: * @return a PacketExtension on the first element found in this packet for the specified
257: * name and namespace or null if none was found.
258: */
259: public PacketExtension getExtension(String name, String namespace) {
260: List extensions = element.elements(QName.get(name, namespace));
261: if (!extensions.isEmpty()) {
262: Class extensionClass = PacketExtension.getExtensionClass(
263: name, namespace);
264: // If a specific PacketExtension implementation has been registered, use that.
265: if (extensionClass != null) {
266: try {
267: Constructor constructor = extensionClass
268: .getDeclaredConstructor(Element.class);
269: return (PacketExtension) constructor
270: .newInstance(extensions.get(0));
271: } catch (Exception e) {
272: // Ignore.
273: }
274: }
275: // Otherwise, use a normal PacketExtension.
276: else {
277: return new PacketExtension((Element) extensions.get(0));
278: }
279: }
280: return null;
281: }
282:
283: /**
284: * Deletes the first element whose element name and namespace matches the specified
285: * element name and namespace.<p>
286: *
287: * Notice that this method may remove any child element that matches the specified
288: * element name and namespace even if that element was not added to the Packet using a
289: * {@link PacketExtension}.
290: *
291: *
292: * @param name the child element name.
293: * @param namespace the child element namespace.
294: * @return true if a child element was removed.
295: */
296: public boolean deleteExtension(String name, String namespace) {
297: List extensions = element.elements(QName.get(name, namespace));
298: if (!extensions.isEmpty()) {
299: element.remove((Element) extensions.get(0));
300: return true;
301: }
302: return false;
303: }
304:
305: /**
306: * Returns the packet error, or <tt>null</tt> if there is no packet error.
307: *
308: * @return the packet error.
309: */
310: public PacketError getError() {
311: Element error = element.element("error");
312: if (error != null) {
313: return new PacketError(error);
314: }
315: return null;
316: }
317:
318: /**
319: * Sets the packet error. Calling this method will automatically set
320: * the packet "type" attribute to "error".
321: *
322: * @param error the packet error.
323: */
324: public void setError(PacketError error) {
325: if (element == null) {
326: throw new NullPointerException("Error cannot be null");
327: }
328: // Force the packet type to "error".
329: element.addAttribute("type", "error");
330: // Remove an existing error packet.
331: if (element.element("error") != null) {
332: element.remove(element.element("error"));
333: }
334: // Add the error element.
335: element.add(error.getElement());
336: }
337:
338: /**
339: * Sets the packet error using the specified condition. Calling this
340: * method will automatically set the packet "type" attribute to "error".
341: * This is a convenience method equivalent to calling:
342: *
343: * <tt>setError(new PacketError(condition));</tt>
344: *
345: * @param condition the error condition.
346: */
347: public void setError(PacketError.Condition condition) {
348: setError(new PacketError(condition));
349: }
350:
351: /**
352: * Creates a deep copy of this packet.
353: *
354: * @return a deep copy of this packet.
355: */
356: public abstract Packet createCopy();
357:
358: /**
359: * Returns the DOM4J Element that backs the packet. The element is the definitive
360: * representation of the packet and can be manipulated directly to change
361: * packet contents.
362: *
363: * @return the DOM4J Element that represents the packet.
364: */
365: public Element getElement() {
366: return element;
367: }
368:
369: /**
370: * Returns the textual XML representation of this packet.
371: *
372: * @return the textual XML representation of this packet.
373: */
374: public String toXML() {
375: return element.asXML();
376: }
377:
378: public String toString() {
379: StringWriter out = new StringWriter();
380: XMLWriter writer = new XMLWriter(out, OutputFormat
381: .createPrettyPrint());
382: try {
383: writer.write(element);
384: } catch (Exception e) {
385: // Ignore.
386: }
387: return out.toString();
388: }
389: }
|