• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * $RCSfile$
3  * $Revision$
4  * $Date$
5  *
6  * Copyright 2003-2007 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 package org.jivesoftware.smack.packet;
22 
23 import java.io.ByteArrayOutputStream;
24 import java.io.ObjectOutputStream;
25 import java.io.Serializable;
26 import java.text.DateFormat;
27 import java.text.SimpleDateFormat;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.TimeZone;
36 import java.util.concurrent.CopyOnWriteArrayList;
37 
38 import org.jivesoftware.smack.util.StringUtils;
39 
40 /**
41  * Base class for XMPP packets. Every packet has a unique ID (which is automatically
42  * generated, but can be overriden). Optionally, the "to" and "from" fields can be set,
43  * as well as an arbitrary number of properties.
44  *
45  * Properties provide an easy mechanism for clients to share data. Each property has a
46  * String name, and a value that is a Java primitive (int, long, float, double, boolean)
47  * or any Serializable object (a Java object is Serializable when it implements the
48  * Serializable interface).
49  *
50  * @author Matt Tucker
51  */
52 public abstract class Packet {
53 
54     protected static final String DEFAULT_LANGUAGE =
55             java.util.Locale.getDefault().getLanguage().toLowerCase();
56 
57     private static String DEFAULT_XML_NS = null;
58 
59     /**
60      * Constant used as packetID to indicate that a packet has no id. To indicate that a packet
61      * has no id set this constant as the packet's id. When the packet is asked for its id the
62      * answer will be <tt>null</tt>.
63      */
64     public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE";
65 
66     /**
67      * Date format as defined in XEP-0082 - XMPP Date and Time Profiles.
68      * The time zone is set to UTC.
69      * <p>
70      * Date formats are not synchronized. Since multiple threads access the format concurrently,
71      * it must be synchronized externally.
72      */
73     public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat(
74                     "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
75     static {
76         XEP_0082_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
77     }
78 
79 
80     /**
81      * A prefix helps to make sure that ID's are unique across mutliple instances.
82      */
83     private static String prefix = StringUtils.randomString(5) + "-";
84 
85     /**
86      * Keeps track of the current increment, which is appended to the prefix to
87      * forum a unique ID.
88      */
89     private static long id = 0;
90 
91     private String xmlns = DEFAULT_XML_NS;
92 
93     /**
94      * Returns the next unique id. Each id made up of a short alphanumeric
95      * prefix along with a unique numeric value.
96      *
97      * @return the next id.
98      */
nextID()99     public static synchronized String nextID() {
100         return prefix + Long.toString(id++);
101     }
102 
setDefaultXmlns(String defaultXmlns)103     public static void setDefaultXmlns(String defaultXmlns) {
104         DEFAULT_XML_NS = defaultXmlns;
105     }
106 
107     private String packetID = null;
108     private String to = null;
109     private String from = null;
110     private final List<PacketExtension> packetExtensions
111             = new CopyOnWriteArrayList<PacketExtension>();
112 
113     private final Map<String,Object> properties = new HashMap<String, Object>();
114     private XMPPError error = null;
115 
Packet()116     public Packet() {
117     }
118 
Packet(Packet p)119     public Packet(Packet p) {
120         packetID = p.getPacketID();
121         to = p.getTo();
122         from = p.getFrom();
123         xmlns = p.xmlns;
124         error = p.error;
125 
126         // Copy extensions
127         for (PacketExtension pe : p.getExtensions()) {
128             addExtension(pe);
129         }
130     }
131 
132     /**
133      * Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
134      * ID_NOT_AVAILABLE was set as the packet's id.
135      *
136      * @return the packet's unique ID or <tt>null</tt> if the packet's id is not available.
137      */
getPacketID()138     public String getPacketID() {
139         if (ID_NOT_AVAILABLE.equals(packetID)) {
140             return null;
141         }
142 
143         if (packetID == null) {
144             packetID = nextID();
145         }
146         return packetID;
147     }
148 
149     /**
150      * Sets the unique ID of the packet. To indicate that a packet has no id
151      * pass the constant ID_NOT_AVAILABLE as the packet's id value.
152      *
153      * @param packetID the unique ID for the packet.
154      */
setPacketID(String packetID)155     public void setPacketID(String packetID) {
156         this.packetID = packetID;
157     }
158 
159     /**
160      * Returns who the packet is being sent "to", or <tt>null</tt> if
161      * the value is not set. The XMPP protocol often makes the "to"
162      * attribute optional, so it does not always need to be set.<p>
163      *
164      * The StringUtils class provides several useful methods for dealing with
165      * XMPP addresses such as parsing the
166      * {@link StringUtils#parseBareAddress(String) bare address},
167      * {@link StringUtils#parseName(String) user name},
168      * {@link StringUtils#parseServer(String) server}, and
169      * {@link StringUtils#parseResource(String) resource}.
170      *
171      * @return who the packet is being sent to, or <tt>null</tt> if the
172      *      value has not been set.
173      */
getTo()174     public String getTo() {
175         return to;
176     }
177 
178     /**
179      * Sets who the packet is being sent "to". The XMPP protocol often makes
180      * the "to" attribute optional, so it does not always need to be set.
181      *
182      * @param to who the packet is being sent to.
183      */
setTo(String to)184     public void setTo(String to) {
185         this.to = to;
186     }
187 
188     /**
189      * Returns who the packet is being sent "from" or <tt>null</tt> if
190      * the value is not set. The XMPP protocol often makes the "from"
191      * attribute optional, so it does not always need to be set.<p>
192      *
193      * The StringUtils class provides several useful methods for dealing with
194      * XMPP addresses such as parsing the
195      * {@link StringUtils#parseBareAddress(String) bare address},
196      * {@link StringUtils#parseName(String) user name},
197      * {@link StringUtils#parseServer(String) server}, and
198      * {@link StringUtils#parseResource(String) resource}.
199      *
200      * @return who the packet is being sent from, or <tt>null</tt> if the
201      *      value has not been set.
202      */
getFrom()203     public String getFrom() {
204         return from;
205     }
206 
207     /**
208      * Sets who the packet is being sent "from". The XMPP protocol often
209      * makes the "from" attribute optional, so it does not always need to
210      * be set.
211      *
212      * @param from who the packet is being sent to.
213      */
setFrom(String from)214     public void setFrom(String from) {
215         this.from = from;
216     }
217 
218     /**
219      * Returns the error associated with this packet, or <tt>null</tt> if there are
220      * no errors.
221      *
222      * @return the error sub-packet or <tt>null</tt> if there isn't an error.
223      */
getError()224     public XMPPError getError() {
225         return error;
226     }
227 
228     /**
229      * Sets the error for this packet.
230      *
231      * @param error the error to associate with this packet.
232      */
setError(XMPPError error)233     public void setError(XMPPError error) {
234         this.error = error;
235     }
236 
237     /**
238      * Returns an unmodifiable collection of the packet extensions attached to the packet.
239      *
240      * @return the packet extensions.
241      */
getExtensions()242     public synchronized Collection<PacketExtension> getExtensions() {
243         if (packetExtensions == null) {
244             return Collections.emptyList();
245         }
246         return Collections.unmodifiableList(new ArrayList<PacketExtension>(packetExtensions));
247     }
248 
249     /**
250      * Returns the first extension of this packet that has the given namespace.
251      *
252      * @param namespace the namespace of the extension that is desired.
253      * @return the packet extension with the given namespace.
254      */
getExtension(String namespace)255     public PacketExtension getExtension(String namespace) {
256         return getExtension(null, namespace);
257     }
258 
259     /**
260      * Returns the first packet extension that matches the specified element name and
261      * namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null
262      * than only the provided namespace is attempted to be matched. Packet extensions are
263      * are arbitrary XML sub-documents in standard XMPP packets. By default, a
264      * DefaultPacketExtension instance will be returned for each extension. However,
265      * PacketExtensionProvider instances can be registered with the
266      * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
267      * class to handle custom parsing. In that case, the type of the Object
268      * will be determined by the provider.
269      *
270      * @param elementName the XML element name of the packet extension. (May be null)
271      * @param namespace the XML element namespace of the packet extension.
272      * @return the extension, or <tt>null</tt> if it doesn't exist.
273      */
getExtension(String elementName, String namespace)274     public PacketExtension getExtension(String elementName, String namespace) {
275         if (namespace == null) {
276             return null;
277         }
278         for (PacketExtension ext : packetExtensions) {
279             if ((elementName == null || elementName.equals(ext.getElementName()))
280                     && namespace.equals(ext.getNamespace()))
281             {
282                 return ext;
283             }
284         }
285         return null;
286     }
287 
288     /**
289      * Adds a packet extension to the packet. Does nothing if extension is null.
290      *
291      * @param extension a packet extension.
292      */
addExtension(PacketExtension extension)293     public void addExtension(PacketExtension extension) {
294         if (extension == null) return;
295         packetExtensions.add(extension);
296     }
297 
298     /**
299      * Adds a collection of packet extensions to the packet. Does nothing if extensions is null.
300      *
301      * @param extensions a collection of packet extensions
302      */
addExtensions(Collection<PacketExtension> extensions)303     public void addExtensions(Collection<PacketExtension> extensions) {
304         if (extensions == null) return;
305         packetExtensions.addAll(extensions);
306     }
307 
308     /**
309      * Removes a packet extension from the packet.
310      *
311      * @param extension the packet extension to remove.
312      */
removeExtension(PacketExtension extension)313     public void removeExtension(PacketExtension extension)  {
314         packetExtensions.remove(extension);
315     }
316 
317     /**
318      * Returns the packet property with the specified name or <tt>null</tt> if the
319      * property doesn't exist. Property values that were originally primitives will
320      * be returned as their object equivalent. For example, an int property will be
321      * returned as an Integer, a double as a Double, etc.
322      *
323      * @param name the name of the property.
324      * @return the property, or <tt>null</tt> if the property doesn't exist.
325      */
getProperty(String name)326     public synchronized Object getProperty(String name) {
327         if (properties == null) {
328             return null;
329         }
330         return properties.get(name);
331     }
332 
333     /**
334      * Sets a property with an Object as the value. The value must be Serializable
335      * or an IllegalArgumentException will be thrown.
336      *
337      * @param name the name of the property.
338      * @param value the value of the property.
339      */
setProperty(String name, Object value)340     public synchronized void setProperty(String name, Object value) {
341         if (!(value instanceof Serializable)) {
342             throw new IllegalArgumentException("Value must be serialiazble");
343         }
344         properties.put(name, value);
345     }
346 
347     /**
348      * Deletes a property.
349      *
350      * @param name the name of the property to delete.
351      */
deleteProperty(String name)352     public synchronized void deleteProperty(String name) {
353         if (properties == null) {
354             return;
355         }
356         properties.remove(name);
357     }
358 
359     /**
360      * Returns an unmodifiable collection of all the property names that are set.
361      *
362      * @return all property names.
363      */
getPropertyNames()364     public synchronized Collection<String> getPropertyNames() {
365         if (properties == null) {
366             return Collections.emptySet();
367         }
368         return Collections.unmodifiableSet(new HashSet<String>(properties.keySet()));
369     }
370 
371     /**
372      * Returns the packet as XML. Every concrete extension of Packet must implement
373      * this method. In addition to writing out packet-specific data, every sub-class
374      * should also write out the error and the extensions data if they are defined.
375      *
376      * @return the XML format of the packet as a String.
377      */
toXML()378     public abstract String toXML();
379 
380     /**
381      * Returns the extension sub-packets (including properties data) as an XML
382      * String, or the Empty String if there are no packet extensions.
383      *
384      * @return the extension sub-packets as XML or the Empty String if there
385      * are no packet extensions.
386      */
getExtensionsXML()387     protected synchronized String getExtensionsXML() {
388         StringBuilder buf = new StringBuilder();
389         // Add in all standard extension sub-packets.
390         for (PacketExtension extension : getExtensions()) {
391             buf.append(extension.toXML());
392         }
393         // Add in packet properties.
394         if (properties != null && !properties.isEmpty()) {
395             buf.append("<properties xmlns=\"http://www.jivesoftware.com/xmlns/xmpp/properties\">");
396             // Loop through all properties and write them out.
397             for (String name : getPropertyNames()) {
398                 Object value = getProperty(name);
399                 buf.append("<property>");
400                 buf.append("<name>").append(StringUtils.escapeForXML(name)).append("</name>");
401                 buf.append("<value type=\"");
402                 if (value instanceof Integer) {
403                     buf.append("integer\">").append(value).append("</value>");
404                 }
405                 else if (value instanceof Long) {
406                     buf.append("long\">").append(value).append("</value>");
407                 }
408                 else if (value instanceof Float) {
409                     buf.append("float\">").append(value).append("</value>");
410                 }
411                 else if (value instanceof Double) {
412                     buf.append("double\">").append(value).append("</value>");
413                 }
414                 else if (value instanceof Boolean) {
415                     buf.append("boolean\">").append(value).append("</value>");
416                 }
417                 else if (value instanceof String) {
418                     buf.append("string\">");
419                     buf.append(StringUtils.escapeForXML((String)value));
420                     buf.append("</value>");
421                 }
422                 // Otherwise, it's a generic Serializable object. Serialized objects are in
423                 // a binary format, which won't work well inside of XML. Therefore, we base-64
424                 // encode the binary data before adding it.
425                 else {
426                     ByteArrayOutputStream byteStream = null;
427                     ObjectOutputStream out = null;
428                     try {
429                         byteStream = new ByteArrayOutputStream();
430                         out = new ObjectOutputStream(byteStream);
431                         out.writeObject(value);
432                         buf.append("java-object\">");
433                         String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray());
434                         buf.append(encodedVal).append("</value>");
435                     }
436                     catch (Exception e) {
437                         e.printStackTrace();
438                     }
439                     finally {
440                         if (out != null) {
441                             try {
442                                 out.close();
443                             }
444                             catch (Exception e) {
445                                 // Ignore.
446                             }
447                         }
448                         if (byteStream != null) {
449                             try {
450                                 byteStream.close();
451                             }
452                             catch (Exception e) {
453                                 // Ignore.
454                             }
455                         }
456                     }
457                 }
458                 buf.append("</property>");
459             }
460             buf.append("</properties>");
461         }
462         return buf.toString();
463     }
464 
getXmlns()465     public String getXmlns() {
466         return this.xmlns;
467     }
468 
469     /**
470      * Returns the default language used for all messages containing localized content.
471      *
472      * @return the default language
473      */
getDefaultLanguage()474     public static String getDefaultLanguage() {
475         return DEFAULT_LANGUAGE;
476     }
477 
equals(Object o)478     public boolean equals(Object o) {
479         if (this == o) return true;
480         if (o == null || getClass() != o.getClass()) return false;
481 
482         Packet packet = (Packet) o;
483 
484         if (error != null ? !error.equals(packet.error) : packet.error != null) { return false; }
485         if (from != null ? !from.equals(packet.from) : packet.from != null) { return false; }
486         if (!packetExtensions.equals(packet.packetExtensions)) { return false; }
487         if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) {
488             return false;
489         }
490         if (properties != null ? !properties.equals(packet.properties)
491                 : packet.properties != null) {
492             return false;
493         }
494         if (to != null ? !to.equals(packet.to) : packet.to != null)  { return false; }
495         return !(xmlns != null ? !xmlns.equals(packet.xmlns) : packet.xmlns != null);
496     }
497 
hashCode()498     public int hashCode() {
499         int result;
500         result = (xmlns != null ? xmlns.hashCode() : 0);
501         result = 31 * result + (packetID != null ? packetID.hashCode() : 0);
502         result = 31 * result + (to != null ? to.hashCode() : 0);
503         result = 31 * result + (from != null ? from.hashCode() : 0);
504         result = 31 * result + packetExtensions.hashCode();
505         result = 31 * result + properties.hashCode();
506         result = 31 * result + (error != null ? error.hashCode() : 0);
507         return result;
508     }
509 }
510