]> sjero.net Git - linphone/blob - p2pproxy/dependencies-src/jxse-src-2.5/api/src/net/jxta/endpoint/MessageElement.java
5c004c975d9c9386494ae2509a1555606490e7c8
[linphone] / p2pproxy / dependencies-src / jxse-src-2.5 / api / src / net / jxta / endpoint / MessageElement.java
1 /*
2  * Copyright (c) 2001-2007 Sun Microsystems, Inc.  All rights reserved.
3  *  
4  *  The Sun Project JXTA(TM) Software License
5  *  
6  *  Redistribution and use in source and binary forms, with or without 
7  *  modification, are permitted provided that the following conditions are met:
8  *  
9  *  1. Redistributions of source code must retain the above copyright notice,
10  *     this list of conditions and the following disclaimer.
11  *  
12  *  2. Redistributions in binary form must reproduce the above copyright notice, 
13  *     this list of conditions and the following disclaimer in the documentation 
14  *     and/or other materials provided with the distribution.
15  *  
16  *  3. The end-user documentation included with the redistribution, if any, must 
17  *     include the following acknowledgment: "This product includes software 
18  *     developed by Sun Microsystems, Inc. for JXTA(TM) technology." 
19  *     Alternately, this acknowledgment may appear in the software itself, if 
20  *     and wherever such third-party acknowledgments normally appear.
21  *  
22  *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must 
23  *     not be used to endorse or promote products derived from this software 
24  *     without prior written permission. For written permission, please contact 
25  *     Project JXTA at http://www.jxta.org.
26  *  
27  *  5. Products derived from this software may not be called "JXTA", nor may 
28  *     "JXTA" appear in their name, without prior written permission of Sun.
29  *  
30  *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
31  *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
32  *  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN 
33  *  MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
34  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
35  *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
36  *  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
37  *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
38  *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
39  *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40  *  
41  *  JXTA is a registered trademark of Sun Microsystems, Inc. in the United 
42  *  States and other countries.
43  *  
44  *  Please see the license information page at :
45  *  <http://www.jxta.org/project/www/license.html> for instructions on use of 
46  *  the license in source files.
47  *  
48  *  ====================================================================
49  *  
50  *  This software consists of voluntary contributions made by many individuals 
51  *  on behalf of Project JXTA. For more information on Project JXTA, please see 
52  *  http://www.jxta.org.
53  *  
54  *  This license is based on the BSD license adopted by the Apache Foundation. 
55  */
56
57 package net.jxta.endpoint;
58
59
60 import java.io.DataInput;
61 import java.io.DataInputStream;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.io.InputStreamReader;
65 import java.io.OutputStream;
66 import java.io.Reader;
67 import java.lang.ref.SoftReference;
68 import java.util.HashMap;
69 import java.util.Map;
70
71 import java.io.UnsupportedEncodingException;
72 import java.util.UUID;
73
74 import java.util.logging.Level;
75 import net.jxta.logging.Logging;
76 import java.util.logging.Logger;
77
78 import net.jxta.document.Document;
79 import net.jxta.document.MimeMediaType;
80 import net.jxta.util.CountingOutputStream;
81 import net.jxta.util.DevNullOutputStream;
82
83 // imported for implementation of {@link #getSequentialName()}
84 import net.jxta.impl.id.UUID.UUIDFactory;
85
86
87 /**
88  * JXTA Message Elements are used to add data to a JXTA Message. Message 
89  * Elements are immutable objects. A Message Element may be shared amongst as 
90  * many messages as is desired. 
91  * <p/>
92  * Several Message Element sub-classes are provided for handling various types
93  * of data. All Message Elements are internally converted to raw bytes when sent
94  * as part of a Message. The various Message Element flavors are provided for
95  * convenience and efficiency. They enable the simplest creation and most 
96  * efficient conversion from the individual data types to the appropriate binary 
97  * data. <b>Because Message Elements are merely a convenient representation for 
98  * binary data the object type of Message Element received by a peer may not 
99  * be the same as was sent by the sending peer.</b> Even though the Message 
100  * Element container may change during transmission the data contained in the 
101  * Message Element is faithfully preserved.
102  * <p/>
103  * A Message Element is composed of four components:
104  * <p/>
105  * <ul>
106  * <li>An optional name. This may be any {@link java.lang.String}. Unnamed
107  * elements are assumed to have the name "" (the empty string).</li>
108  * <li>An optional {@link net.jxta.document.MimeMediaType}. If not specified
109  * the Mime Media Type is assumed to be "Application/Octet-Stream".</li>
110  * <li>Data. Sub-classes of MessageElement allow you to create elements based
111  * on a variety of data formats.</li>
112  * <li>An optional signature. This is a Message Element that is associated to
113  * this element and may contain a cryptographic signature/hash of this message
114  * element.
115  * </li>
116  * </ul>
117  * <p/>
118  * <p/>The data contained within a MessageElement is accessible in four ways:
119  * <p/>
120  * <ul>
121  * <li>As an {@link java.io.InputStream} from {@link #getStream()}</li>
122  * <li>Sending the data a {@link java.io.OutputStream} via {@link #sendToStream(OutputStream)}</li>
123  * <li>As a {@link java.lang.String} from {@link #toString()}</li>
124  * <li>As a byte array from  from {@link #getBytes(boolean)}</li>
125  * </ul>
126  *
127  * @see net.jxta.endpoint.Message
128  */
129 public abstract class MessageElement implements Document {
130
131     /**
132      * Logger
133      */
134     private static transient final Logger LOG = Logger.getLogger(MessageElement.class.getName());
135
136     /**
137      * The name of this element. May be the empty string ("") if the element is
138      * unnamed.
139      */
140     protected final String name;
141
142     /**
143      * The type of this element.
144      */
145     protected final MimeMediaType type;
146
147     /**
148      * The optional element which digitally signs or digests this element.
149      * If null then the element is has no signature element.
150      */
151     protected final MessageElement sig;
152
153     /**
154      * message properties hashmap
155      */
156     private Map<Object,Object> properties = null;
157
158     /**
159      * cached result of {@link #getByteLength()} operation.
160      */
161     protected transient long cachedGetByteLength = -1;
162
163     /**
164      * cached result of {@link #getBytes(boolean)} operation.
165      */
166     protected transient SoftReference<byte[]> cachedGetBytes = null;
167
168     /**
169      * cached result of {@link #toString()} operation.
170      */
171     protected transient SoftReference<String> cachedToString = null;
172
173     /**
174      * Returns a pseudo-random unique string which can be used as an element
175      * name.
176      *
177      * @return String containing a pseudo-random value
178      */
179     public static String getUniqueName() {
180         return UUID.randomUUID().toString();
181     }
182
183     /**
184      * Returns a string containing a pseudo-random unique string. The result of
185      * <code>String.compare()</code> will be consistent with the order in which
186      * results were returned from this function.
187      * <p/>
188      * <p/>Security Consideration : Be aware that the pseudo random portion of
189      * the names generated by this string are shared amongst all peer groups
190      * running in the same classloader. You may be at a risk for loss of
191      * anonymity if you use the element names produced in more than one peer
192      * group.
193      *
194      * @return String containing a pseudo-random value. The result of
195      *         <code>String.compare()</code> will be consistent with the order in
196      *         which results were returned from this function.
197      */
198     public static String getSequentialName() {
199         return UUIDFactory.newSeqUUID().toString();
200     }
201
202     /**
203      * Internal constructor for initializing everything but the data.
204      *
205      * @param name Name of the Element. May be the empty string ("") if
206      *             the Element is not named.
207      * @param type Type of the Element. null is equivalent to specifying
208      *             the type "Application/Octet-stream"
209      * @param sig  optional message digest/digital signature element. If
210      *             no signature is to be specified, pass null.
211      */
212     protected MessageElement(String name, MimeMediaType type, MessageElement sig) {
213         this.name = (null != name) ? name : "";
214
215         this.type = (null != type) ? type.intern() : MimeMediaType.AOS;
216
217         if ((null != sig) && (null != sig.sig)) {
218             throw new IllegalArgumentException("Invalid Signature Element. Signatures may not have signatures.");
219         }
220
221         this.sig = sig;
222     }
223
224     /**
225      * {@inheritDoc}
226      *
227      * @deprecated Since Message Elements are immutable this method does
228      *             nothing useful.
229      */
230     @Override
231     @Deprecated
232     public final MessageElement clone() {
233         return this;
234     }
235
236     /**
237      * {@inheritDoc}
238      * <p/>
239      * <p/>Elements are considered equal if they have the same name, type and
240      * signatures. Element data is not considered by this implementation as
241      * it is mostly intended for subclass use.
242      */
243     @Override
244     public boolean equals(Object target) {
245         if (this == target) {
246             return true; // same object
247         }
248
249         if (target instanceof MessageElement) {
250             MessageElement likeMe = (MessageElement) target;
251
252             // sig is nullable so test seperatly.
253             boolean sigequals = (null != sig) ? sig.equals(likeMe.sig) : (null == likeMe.sig);
254
255             return sigequals && name.equals(likeMe.name) && type.equals(likeMe.type);
256         }
257
258         return false; // not a MessageElement
259     }
260
261     /**
262      * {@inheritDoc}
263      */
264     @Override
265     public int hashCode() {
266         int sigHash = ((null != getSignature()) && (this != getSignature())) ? getSignature().hashCode() : 1;
267
268         int result = sigHash * 2467 + // a prime
269                 getElementName().hashCode() * 3943 + // also a prime
270                 getMimeType().hashCode();
271
272         return (0 != result) ? result : 1;
273     }
274
275     /**
276      * {@inheritDoc}
277      * <p/>
278      * <p/>Returns a String representation of the element data. The
279      * <code>'charset'</code> parameter of the message element's mimetype, if
280      * any, is used to determine encoding. If the charset specified is
281      * unsupported then the default encoding will be used.
282      * <p/>
283      * <p/>synchronized for caching purposes.
284      */
285     @Override
286     public synchronized String toString() {
287         String result;
288
289         if (null != cachedToString) {
290             result = cachedToString.get();
291
292             if (null != result) {
293                 return result;
294             }
295         }
296
297         if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
298             LOG.fine("creating toString of " + getClass().getName() + '@' + Integer.toHexString(hashCode()));
299         }
300
301         String charset = type.getParameter("charset");
302
303         StringBuilder theString = new StringBuilder();
304
305         Reader asString;
306
307         try {
308             if (null == charset) {
309                 asString = new InputStreamReader(getStream());
310             } else {
311                 try {
312                     asString = new InputStreamReader(getStream(), charset);
313                 } catch (UnsupportedEncodingException caught) {
314                     throw new IllegalStateException("Unsupported charset : " + charset);
315                 }
316             }
317
318             char[] characters = new char[256];
319
320             do {
321                 int res = asString.read(characters);
322
323                 if (res < 0) {
324                     break;
325                 }
326
327                 theString.append(characters, 0, res);
328             } while (true);
329
330             result = theString.toString();
331
332             cachedToString = new SoftReference<String>(result);
333             return result;
334         } catch (IOException caught) {
335             if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
336                 LOG.log(Level.SEVERE, "Could not generate string for element. ", caught);
337             }
338
339             throw new IllegalStateException("Could not generate string for element. " + caught);
340         }
341     }
342
343     /**
344      * Returns the name of the MessageElement. Unnamed elements will return
345      * the empty string ("");
346      *
347      * @return String containing the name of the MessageElement.
348      */
349     public String getElementName() {
350         return name;
351     }
352
353     /**
354      * {@inheritDoc}
355      * <p/>
356      * <p/>Will return "Application/Octet-Stream" if no type was originally
357      * specified.
358      */
359     public MimeMediaType getMimeType() {
360         return type;
361     }
362
363     /**
364      * {@inheritDoc}
365      * <p/>
366      * <p/>We use the "unknown" extension and leave it to sub-classes to
367      * extend this. If we had a mailcap facility we could do better
368      * classification based on mimetype.
369      */
370     public String getFileExtension() {
371         return "???";
372     }
373
374     /**
375      * Returns the size of the element data in bytes.
376      *
377      * @return long containing the size of the element data.
378      */
379     public synchronized long getByteLength() {
380         if (cachedGetByteLength >= 0) {
381             return cachedGetByteLength;
382         }
383
384         CountingOutputStream countBytes = new CountingOutputStream(new DevNullOutputStream());
385
386         try {
387             sendToStream(countBytes);
388             cachedGetByteLength = countBytes.getBytesWritten();
389             return cachedGetByteLength;
390         } catch (IOException caught) {
391             throw new IllegalStateException("Could not get length of element : " + caught.toString());
392         }
393     }
394
395     /**
396      * Returns a byte array which contains the element data. The byte array
397      * returned <b>may be shared amongst all copies of the element</b>,
398      * do not modify it. The <code>copy</code> parameter allows you to request a
399      * private, modifiable copy of the element data.
400      * <p/>
401      * <p/>This implementation builds the byte array from the stream.
402      *
403      * @param copy If true then the result can be modified without damaging the state of this
404      *             MessageElement. If false, then the result may be a shared copy of the data and
405      *             should be considered read-only.
406      * @return byte[] Contents of message element.
407      */
408     public synchronized byte[] getBytes(boolean copy) {
409         byte[] result;
410
411         if (null != cachedGetBytes) {
412             result = cachedGetBytes.get();
413
414             if (null != result) {
415                 if (copy) {
416                     byte[] theCopy = new byte[result.length];
417
418                     System.arraycopy(theCopy, 0, result, 0, result.length);
419                 } else {
420                     return result;
421                 }
422             }
423         }
424
425         if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
426             LOG.fine("creating getBytes of " + getClass().getName() + '@' + Integer.toHexString(hashCode()));
427         }
428
429         long len = getByteLength();
430
431         if (len > Integer.MAX_VALUE) {
432             if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
433                 LOG.severe("MessageElement is too large to be stored in a byte array.");
434             }
435
436             throw new IllegalStateException("MessageElement is too large to be stored in a byte array.");
437         }
438
439         result = new byte[(int) len];
440
441         try {
442             DataInput di = new DataInputStream(getStream());
443
444             di.readFully(result);
445         } catch (IOException caught) {
446             if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
447                 LOG.log(Level.SEVERE, "Failed to get bytes of Message Element. ", caught);
448             }
449             throw new IllegalStateException("Failed to get bytes of Message Element. " + caught);
450         }
451
452         // if this is supposed to be a shared buffer then we can cache it.
453         if (!copy) {
454             cachedGetBytes = new SoftReference<byte[]>(result);
455         }
456
457         return result;
458     }
459
460     /**
461      * {@inheritDoc}
462      * <p/>
463      * <p/>This version probably has sub-optimal performance. Sub-classes
464      * should override this implementation.
465      */
466     public void sendToStream(OutputStream sendTo) throws IOException {
467         copyInputStreamToOutputStream(getStream(), sendTo);
468     }
469
470     /**
471      * Returns the element containing the digest/digital signature for
472      * this element
473      *
474      * @return Element containing the digital signature.
475      */
476     public MessageElement getSignature() {
477
478         return sig;
479     }
480
481     /**
482      * Associate a transient property with this element. If there was a previous
483      * value for the key provided then it is returned.
484      * <p/>
485      * <p/>Element properties are useful for managing the state of element
486      * during processing. Element properties are not transmitted with the
487      * message element when the message element is sent as part of a message.
488      * <p/>
489      * <p/>The setting of particular keys may be controlled by a Java Security
490      * Manager. Keys of type 'java.lang.Class' are checked against the caller of
491      * this method. Only callers which are instances of the key class may modify
492      * the property. This check is not possible through reflection. All other
493      * types of keys are unchecked.
494      *
495      * @param key   the property key
496      * @param value the value for the property
497      * @return previous value for the property or null if no previous
498      */
499     public synchronized Object setElementProperty(Object key, Object value) {
500
501         /*
502          if( key instanceof java.lang.Class ) {
503          Class keyClass = (Class) key;
504          SecurityManager secure =  new SecurityManager() {
505          public boolean checkCallerOfClass( Class toCheck ) {
506          Class [] context = getClassContext();
507
508          return toCheck.isAssignableFrom( context[2] );
509          }
510          };
511
512          if( !secure.checkCallerOfClass( keyClass ) ) {
513          throw new SecurityException( "You can't set that key from this context." );
514          }
515          }
516          */
517
518         if (null == properties) {
519             properties = new HashMap<Object,Object>();
520         }
521         
522         return properties.put(key, value);
523     }
524
525     /**
526      * Retrieves a transient property from the set for this element.
527      * 
528      * <p/>Element properties are useful for managing the state of element
529      * during processing. Element properties are not transmitted with the
530      * message element when the message element is sent as part of a message.
531      *
532      * @param key the property key.
533      * @return value for the property or null if there is no property for this
534      *         key.
535      */
536     public Object getElementProperty(Object key) {
537
538         if (null == properties) {
539             return null;
540         }
541         
542         return properties.get(key);
543     }
544
545     /**
546      * Copies an input stream to an output stream with buffering.
547      *
548      * @param source The stream to copy from.
549      * @param sink   The stream to send the data to.
550      * @throws IOException if there is a problem copying the data
551      */
552     protected static void copyInputStreamToOutputStream(InputStream source, OutputStream sink) throws IOException {
553         int c;
554         byte[] buf = new byte[4096];
555
556         do {
557             c = source.read(buf);
558
559             if (-1 == c) {
560                 break;
561             }
562
563             sink.write(buf, 0, c);
564         } while (true);
565     }
566 }
567