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