blob: 3f1185e4f85a14b53d9ee223546d4d7e2e6775e9 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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
21package org.jivesoftware.smack.packet;
22
23import java.io.ByteArrayOutputStream;
24import java.io.ObjectOutputStream;
25import java.io.Serializable;
26import java.text.DateFormat;
27import java.text.SimpleDateFormat;
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.HashSet;
33import java.util.List;
34import java.util.Map;
35import java.util.TimeZone;
36import java.util.concurrent.CopyOnWriteArrayList;
37
38import 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 */
52public 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}