/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.endpoint;
import net.jxta.document.Document;
import net.jxta.document.MimeMediaType;
import net.jxta.logging.Logger;
import net.jxta.logging.Logging;
import net.jxta.util.CountingOutputStream;
import net.jxta.util.DevNullOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.jxta.util.UUIDUtilities;
/**
* JXTA Message Elements are used to add data to a JXTA Message. Message
* Elements are immutable objects. A Message Element may be shared amongst as
* many messages as is desired.
* <p/>
* Several Message Element sub-classes are provided for handling various types
* of data. All Message Elements are internally converted to raw bytes when sent
* as part of a Message. The various Message Element flavors are provided for
* convenience and efficiency. They enable the simplest creation and most
* efficient conversion from the individual data types to the appropriate binary
* data. <b>Because Message Elements are merely a convenient representation for
* binary data the object type of Message Element received by a peer may not
* be the same as was sent by the sending peer.</b> Even though the Message
* Element container may change during transmission the data contained in the
* Message Element is faithfully preserved.
* <p/>
* A Message Element is composed of four components:
* <p/>
* <ul>
* <li>An optional name. This may be any {@link java.lang.String}. Unnamed
* elements are assumed to have the name "" (the empty string).</li>
* <li>An optional {@link net.jxta.document.MimeMediaType}. If not specified
* the Mime Media Type is assumed to be "Application/Octet-Stream".</li>
* <li>Data. Sub-classes of MessageElement allow you to create elements based
* on a variety of data formats.</li>
* <li>An optional signature. This is a Message Element that is associated to
* this element and may contain a cryptographic signature/hash of this message
* element.
* </li>
* </ul>
* <p/>
* <p/>The data contained within a MessageElement is accessible in four ways:
* <p/>
* <ul>
* <li>As an {@link java.io.InputStream} from {@link #getStream()}</li>
* <li>Sending the data a {@link java.io.OutputStream} via {@link #sendToStream(OutputStream)}</li>
* <li>As a {@link java.lang.String} from {@link #toString()}</li>
* <li>As a byte array from from {@link #getBytes(boolean)}</li>
* </ul>
*
* @see net.jxta.endpoint.Message
*/
public abstract class MessageElement implements Document {
private static transient final Logger LOG = Logging.getLogger(MessageElement.class.getName());
/**
* The name of this element. May be the empty string ("") if the element is
* unnamed.
*/
protected final String name;
/**
* The type of this element.
*/
protected final MimeMediaType type;
/**
* The optional element which digitally signs or digests this element.
* If null then the element is has no signature element.
*/
protected final MessageElement sig;
/**
* message properties hashmap
*/
private Map<Object,Object> properties = null;
/**
* cached result of {@link #getByteLength()} operation.
*/
protected transient long cachedGetByteLength = -1;
/**
* cached result of {@link #getBytes(boolean)} operation.
*/
protected transient SoftReference<byte[]> cachedGetBytes = null;
/**
* cached result of {@link #toString()} operation.
*/
protected transient SoftReference<String> cachedToString = null;
/**
* Returns a pseudo-random unique string which can be used as an element
* name.
*
* @return String containing a pseudo-random value
*/
public static String getUniqueName() {
return UUID.randomUUID().toString();
}
/**
* Returns a string containing a pseudo-random unique string. The result of
* <code>String.compare()</code> will be consistent with the order in which
* results were returned from this function.
* <p/>
* <p/>Security Consideration : Be aware that the pseudo random portion of
* the names generated by this string are shared amongst all peer groups
* running in the same classloader. You may be at a risk for loss of
* anonymity if you use the element names produced in more than one peer
* group.
*
* @return String containing a pseudo-random value. The result of
* <code>String.compare()</code> will be consistent with the order in
* which results were returned from this function.
*/
public static String getSequentialName() {
return UUIDUtilities.newSeqUUID().toString();
}
/**
* Internal constructor for initializing everything but the data.
*
* @param name Name of the Element. May be the empty string ("") if
* the Element is not named.
* @param type Type of the Element. null is equivalent to specifying
* the type "Application/Octet-stream"
* @param sig optional message digest/digital signature element. If
* no signature is to be specified, pass null.
*/
protected MessageElement(String name, MimeMediaType type, MessageElement sig) {
this.name = (null != name) ? name : "";
this.type = (null != type) ? type.intern() : MimeMediaType.AOS;
if ((null != sig) && (null != sig.sig)) {
throw new IllegalArgumentException("Invalid Signature Element. Signatures may not have signatures.");
}
this.sig = sig;
}
/**
* {@inheritDoc}
* <p/>
* Elements are considered equal if they have the same name, same basic
* MIME type and signatures. Element data is not considered by this
* implementation as it is mostly intended for subclass use.
*/
@Override
public boolean equals(Object target) {
if (this == target) {
return true; // same object
}
if (target instanceof MessageElement) {
MessageElement likeMe = (MessageElement) target;
// sig is nullable so test separately.
boolean sigequals = (null != sig) ? sig.equals(likeMe.sig) : (null == likeMe.sig);
return sigequals && name.equals(likeMe.name) && type.equalsIngoringParams(likeMe.type);
}
return false; // not a MessageElement
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int sigHash = ((null != getSignature()) && (this != getSignature())) ? getSignature().hashCode() : 1;
int result = sigHash * 2467 + // a prime
getElementName().hashCode() * 3943 + // also a prime
getMimeType().getBaseMimeMediaType().hashCode();
return (0 != result) ? result : 1;
}
/**
* {@inheritDoc}
* <p/>
* <p/>Returns a String representation of the element data. The
* <code>'charset'</code> parameter of the message element's mimetype, if
* any, is used to determine encoding. If the charset specified is
* unsupported then the default encoding will be used.
* <p/>
* <p/>synchronized for caching purposes.
*/
@Override
public synchronized String toString() {
String result;
if (null != cachedToString) {
result = cachedToString.get();
if (null != result) {
return result;
}
}
Logging.logCheckedDebug(LOG, "creating toString of ", getClass().getName(), '@', Integer.toHexString(hashCode()));
String charset = type.getParameter("charset");
StringBuilder theString = new StringBuilder();
Reader asString;
try {
if (null == charset) {
asString = new InputStreamReader(getStream());
} else {
try {
asString = new InputStreamReader(getStream(), charset);
} catch (UnsupportedEncodingException caught) {
throw new IllegalStateException("Unsupported charset : " + charset);
}
}
char[] characters = new char[256];
do {
int res = asString.read(characters);
if (res < 0) {
break;
}
theString.append(characters, 0, res);
} while (true);
result = theString.toString();
cachedToString = new SoftReference<String>(result);
return result;
} catch (IOException caught) {
Logging.logCheckedError(LOG, "Could not generate string for element. \n", caught);
throw new IllegalStateException("Could not generate string for element. " + caught);
}
}
/**
* Returns the name of the MessageElement. Unnamed elements will return
* the empty string ("");
*
* @return String containing the name of the MessageElement.
*/
public String getElementName() {
return name;
}
/**
* {@inheritDoc}
* <p/>
* <p/>Will return "Application/Octet-Stream" if no type was originally
* specified.
*/
public MimeMediaType getMimeType() {
return type;
}
/**
* {@inheritDoc}
* <p/>
* <p/>We use the "unknown" extension and leave it to sub-classes to
* extend this. If we had a mailcap facility we could do better
* classification based on mimetype.
*/
public String getFileExtension() {
return "???";
}
/**
* Returns the size of the element data in bytes.
*
* @return long containing the size of the element data.
*/
public synchronized long getByteLength() {
if (cachedGetByteLength >= 0) {
return cachedGetByteLength;
}
CountingOutputStream countBytes = new CountingOutputStream(new DevNullOutputStream());
try {
sendToStream(countBytes);
cachedGetByteLength = countBytes.getBytesWritten();
return cachedGetByteLength;
} catch (IOException caught) {
throw new IllegalStateException("Could not get length of element : " + caught.toString());
}
}
/**
* Returns a byte array which contains the element data. The byte array
* returned <b>may be shared amongst all copies of the element</b>,
* do not modify it. The <code>copy</code> parameter allows you to request a
* private, modifiable copy of the element data.
* <p/>
* <p/>This implementation builds the byte array from the stream.
*
* @param copy If true then the result can be modified without damaging the state of this
* MessageElement. If false, then the result may be a shared copy of the data and
* should be considered read-only.
* @return byte[] Contents of message element.
*/
public synchronized byte[] getBytes(boolean copy) {
byte[] result;
if (null != cachedGetBytes) {
result = cachedGetBytes.get();
if (null != result) {
return copy ? result.clone() : result;
}
}
Logging.logCheckedDebug(LOG, "creating getBytes of ", getClass().getName(), '@', Integer.toHexString(hashCode()));
long len = getByteLength();
if (len > Integer.MAX_VALUE) {
Logging.logCheckedError(LOG, "MessageElement is too large to be stored in a byte array.");
throw new IllegalStateException("MessageElement is too large to be stored in a byte array.");
}
result = new byte[(int) len];
try {
DataInput di = new DataInputStream(getStream());
di.readFully(result);
} catch (IOException caught) {
Logging.logCheckedError(LOG, "Failed to get bytes of Message Element. \n", caught);
throw new IllegalStateException("Failed to get bytes of Message Element. " + caught);
}
// if this is supposed to be a shared buffer then we can cache it.
if (!copy) {
cachedGetBytes = new SoftReference<byte[]>(result);
}
return result;
}
/**
* {@inheritDoc}
* <p/>
* <p/>This version probably has sub-optimal performance. Sub-classes
* should override this implementation.
*/
public void sendToStream(OutputStream sendTo) throws IOException {
copyInputStreamToOutputStream(getStream(), sendTo);
}
/**
* Returns the element containing the digest/digital signature for
* this element
*
* @return Element containing the digital signature.
*/
public MessageElement getSignature() {
return sig;
}
/**
* Associate a transient property with this element. If there was a previous
* value for the key provided then it is returned.
* <p/>
* <p/>Element properties are useful for managing the state of element
* during processing. Element properties are not transmitted with the
* message element when the message element is sent as part of a message.
* <p/>
* <p/>The setting of particular keys may be controlled by a Java Security
* Manager. Keys of type 'java.lang.Class' are checked against the caller of
* this method. Only callers which are instances of the key class may modify
* the property. This check is not possible through reflection. All other
* types of keys are unchecked.
*
* @param key the property key
* @param value the value for the property
* @return previous value for the property or null if no previous
*/
public synchronized Object setElementProperty(Object key, Object value) {
/*
if( key instanceof java.lang.Class ) {
Class keyClass = (Class) key;
SecurityManager secure = new SecurityManager() {
public boolean checkCallerOfClass( Class toCheck ) {
Class [] context = getClassContext();
return toCheck.isAssignableFrom( context[2] );
}
};
if( !secure.checkCallerOfClass( keyClass ) ) {
throw new SecurityException( "You can't set that key from this context." );
}
}
*/
if (null == properties) {
properties = new HashMap<Object,Object>();
}
return properties.put(key, value);
}
/**
* Retrieves a transient property from the set for this element.
*
* <p/>Element properties are useful for managing the state of element
* during processing. Element properties are not transmitted with the
* message element when the message element is sent as part of a message.
*
* @param key the property key.
* @return value for the property or null if there is no property for this
* key.
*/
public Object getElementProperty(Object key) {
if (null == properties) {
return null;
}
return properties.get(key);
}
/**
* Copies an input stream to an output stream with buffering.
*
* @param source The stream to copy from.
* @param sink The stream to send the data to.
* @throws IOException if there is a problem copying the data
*/
protected static void copyInputStreamToOutputStream(InputStream source, OutputStream sink) throws IOException {
int c;
byte[] buf = new byte[4096];
do {
c = source.read(buf);
if (-1 == c) {
break;
}
sink.write(buf, 0, c);
} while (true);
}
}