2 * Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
4 * The Sun Project JXTA(TM) Software License
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
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.
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.
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.
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.
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.
41 * JXTA is a registered trademark of Sun Microsystems, Inc. in the United
42 * States and other countries.
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.
48 * ====================================================================
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.
54 * This license is based on the BSD license adopted by the Apache Foundation.
57 package net.jxta.endpoint;
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;
71 import java.io.UnsupportedEncodingException;
72 import java.util.UUID;
74 import java.util.logging.Level;
75 import net.jxta.logging.Logging;
76 import java.util.logging.Logger;
78 import net.jxta.document.Document;
79 import net.jxta.document.MimeMediaType;
80 import net.jxta.util.CountingOutputStream;
81 import net.jxta.util.DevNullOutputStream;
83 // imported for implementation of {@link #getSequentialName()}
84 import net.jxta.impl.id.UUID.UUIDFactory;
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.
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.
103 * A Message Element is composed of four components:
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
118 * <p/>The data contained within a MessageElement is accessible in four ways:
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>
127 * @see net.jxta.endpoint.Message
129 public abstract class MessageElement implements Document {
134 private static transient final Logger LOG = Logger.getLogger(MessageElement.class.getName());
137 * The name of this element. May be the empty string ("") if the element is
140 protected final String name;
143 * The type of this element.
145 protected final MimeMediaType type;
148 * The optional element which digitally signs or digests this element.
149 * If null then the element is has no signature element.
151 protected final MessageElement sig;
154 * message properties hashmap
156 private Map<Object,Object> properties = null;
159 * cached result of {@link #getByteLength()} operation.
161 protected transient long cachedGetByteLength = -1;
164 * cached result of {@link #getBytes(boolean)} operation.
166 protected transient SoftReference<byte[]> cachedGetBytes = null;
169 * cached result of {@link #toString()} operation.
171 protected transient SoftReference<String> cachedToString = null;
174 * Returns a pseudo-random unique string which can be used as an element
177 * @return String containing a pseudo-random value
179 public static String getUniqueName() {
180 return UUID.randomUUID().toString();
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.
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
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.
198 public static String getSequentialName() {
199 return UUIDFactory.newSeqUUID().toString();
203 * Internal constructor for initializing everything but the data.
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.
212 protected MessageElement(String name, MimeMediaType type, MessageElement sig) {
213 this.name = (null != name) ? name : "";
215 this.type = (null != type) ? type.intern() : MimeMediaType.AOS;
217 if ((null != sig) && (null != sig.sig)) {
218 throw new IllegalArgumentException("Invalid Signature Element. Signatures may not have signatures.");
227 * @deprecated Since Message Elements are immutable this method does
232 public final MessageElement clone() {
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.
244 public boolean equals(Object target) {
245 if (this == target) {
246 return true; // same object
249 if (target instanceof MessageElement) {
250 MessageElement likeMe = (MessageElement) target;
252 // sig is nullable so test seperatly.
253 boolean sigequals = (null != sig) ? sig.equals(likeMe.sig) : (null == likeMe.sig);
255 return sigequals && name.equals(likeMe.name) && type.equals(likeMe.type);
258 return false; // not a MessageElement
265 public int hashCode() {
266 int sigHash = ((null != getSignature()) && (this != getSignature())) ? getSignature().hashCode() : 1;
268 int result = sigHash * 2467 + // a prime
269 getElementName().hashCode() * 3943 + // also a prime
270 getMimeType().hashCode();
272 return (0 != result) ? result : 1;
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.
283 * <p/>synchronized for caching purposes.
286 public synchronized String toString() {
289 if (null != cachedToString) {
290 result = cachedToString.get();
292 if (null != result) {
297 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
298 LOG.fine("creating toString of " + getClass().getName() + '@' + Integer.toHexString(hashCode()));
301 String charset = type.getParameter("charset");
303 StringBuilder theString = new StringBuilder();
308 if (null == charset) {
309 asString = new InputStreamReader(getStream());
312 asString = new InputStreamReader(getStream(), charset);
313 } catch (UnsupportedEncodingException caught) {
314 throw new IllegalStateException("Unsupported charset : " + charset);
318 char[] characters = new char[256];
321 int res = asString.read(characters);
327 theString.append(characters, 0, res);
330 result = theString.toString();
332 cachedToString = new SoftReference<String>(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);
339 throw new IllegalStateException("Could not generate string for element. " + caught);
344 * Returns the name of the MessageElement. Unnamed elements will return
345 * the empty string ("");
347 * @return String containing the name of the MessageElement.
349 public String getElementName() {
356 * <p/>Will return "Application/Octet-Stream" if no type was originally
359 public MimeMediaType getMimeType() {
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.
370 public String getFileExtension() {
375 * Returns the size of the element data in bytes.
377 * @return long containing the size of the element data.
379 public synchronized long getByteLength() {
380 if (cachedGetByteLength >= 0) {
381 return cachedGetByteLength;
384 CountingOutputStream countBytes = new CountingOutputStream(new DevNullOutputStream());
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());
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.
401 * <p/>This implementation builds the byte array from the stream.
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.
408 public synchronized byte[] getBytes(boolean copy) {
411 if (null != cachedGetBytes) {
412 result = cachedGetBytes.get();
414 if (null != result) {
416 byte[] theCopy = new byte[result.length];
418 System.arraycopy(theCopy, 0, result, 0, result.length);
425 if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
426 LOG.fine("creating getBytes of " + getClass().getName() + '@' + Integer.toHexString(hashCode()));
429 long len = getByteLength();
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.");
436 throw new IllegalStateException("MessageElement is too large to be stored in a byte array.");
439 result = new byte[(int) len];
442 DataInput di = new DataInputStream(getStream());
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);
449 throw new IllegalStateException("Failed to get bytes of Message Element. " + caught);
452 // if this is supposed to be a shared buffer then we can cache it.
454 cachedGetBytes = new SoftReference<byte[]>(result);
463 * <p/>This version probably has sub-optimal performance. Sub-classes
464 * should override this implementation.
466 public void sendToStream(OutputStream sendTo) throws IOException {
467 copyInputStreamToOutputStream(getStream(), sendTo);
471 * Returns the element containing the digest/digital signature for
474 * @return Element containing the digital signature.
476 public MessageElement getSignature() {
482 * Associate a transient property with this element. If there was a previous
483 * value for the key provided then it is returned.
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.
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.
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
499 public synchronized Object setElementProperty(Object key, Object value) {
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();
508 return toCheck.isAssignableFrom( context[2] );
512 if( !secure.checkCallerOfClass( keyClass ) ) {
513 throw new SecurityException( "You can't set that key from this context." );
518 if (null == properties) {
519 properties = new HashMap<Object,Object>();
522 return properties.put(key, value);
526 * Retrieves a transient property from the set for this element.
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.
532 * @param key the property key.
533 * @return value for the property or null if there is no property for this
536 public Object getElementProperty(Object key) {
538 if (null == properties) {
542 return properties.get(key);
546 * Copies an input stream to an output stream with buffering.
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
552 protected static void copyInputStreamToOutputStream(InputStream source, OutputStream sink) throws IOException {
554 byte[] buf = new byte[4096];
557 c = source.read(buf);
563 sink.write(buf, 0, c);