Shuyi Chen | d7955ce | 2013-05-22 14:51:55 -0700 | [diff] [blame] | 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 | */ |
| 99 | public static synchronized String nextID() { |
| 100 | return prefix + Long.toString(id++); |
| 101 | } |
| 102 | |
| 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 | |
| 116 | public Packet() { |
| 117 | } |
| 118 | |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | |
| 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 | */ |
| 474 | public static String getDefaultLanguage() { |
| 475 | return DEFAULT_LANGUAGE; |
| 476 | } |
| 477 | |
| 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 | |
| 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 | } |