/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.representation;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Date;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Disposition;
import org.restlet.data.MediaType;
import org.restlet.data.Range;
import org.restlet.data.Tag;
import org.restlet.engine.io.IoUtils;
import org.restlet.engine.util.DateUtils;
/**
* Current or intended state of a resource. The content of a representation can
* be retrieved several times if there is a stable and accessible source, like a
* local file or a string. When the representation is obtained via a temporary
* source like a network socket, its content can only be retrieved once. The
* "transient" and "available" properties are available to help you figure out
* those aspects at runtime.<br>
* <br>
* For performance purpose, it is essential that a minimal overhead occurs upon
* initialization. The main overhead must only occur during invocation of
* content processing methods (write, getStream, getChannel and toString).<br>
* <br>
* "REST components perform actions on a resource by using a representation to
* capture the current or intended state of that resource and transferring that
* representation between components. A representation is a sequence of bytes,
* plus representation metadata to describe those bytes. Other commonly used but
* less precise names for a representation include: document, file, and HTTP
* message entity, instance, or variant." Roy T. Fielding
*
* @see <a href=
* "http://roy.gbiv.com/pubs/dissertation/rest_arch_style.htm#sec_5_2_1_2"
* >Source dissertation</a>
* @author Jerome Louvel
*/
public abstract class Representation extends RepresentationInfo {
/**
* Indicates that the size of the representation can't be known in advance.
*/
public static final long UNKNOWN_SIZE = -1L;
/** Indicates if the representation's content is potentially available. */
private volatile boolean available;
// [ifndef gwt] member
/**
* The representation digest if any.
*/
private volatile org.restlet.data.Digest digest;
/** The disposition characteristics of the representation. */
private volatile Disposition disposition;
/** The expiration date. */
private volatile Date expirationDate;
/** Indicates if the representation's content is transient. */
private volatile boolean isTransient;
/**
* Indicates where in the full content the partial content available should
* be applied.
*/
private volatile Range range;
/**
* The expected size. Dynamic representations can have any size, but
* sometimes we can know in advance the expected size. If this expected size
* is specified by the user, it has a higher priority than any size that can
* be guessed by the representation (like a file size).
*/
private volatile long size;
/**
* Default constructor.
*/
public Representation() {
this(null);
}
/**
* Constructor.
*
* @param mediaType
* The media type.
*/
public Representation(MediaType mediaType) {
super(mediaType);
this.available = true;
this.disposition = null;
this.isTransient = false;
this.size = UNKNOWN_SIZE;
this.expirationDate = null;
// [ifndef gwt]
this.digest = null;
this.range = null;
// [enddef]
}
/**
* Constructor.
*
* @param mediaType
* The media type.
* @param modificationDate
* The modification date.
*/
public Representation(MediaType mediaType, Date modificationDate) {
this(mediaType, modificationDate, null);
}
/**
* Constructor.
*
* @param mediaType
* The media type.
* @param modificationDate
* The modification date.
* @param tag
* The tag.
*/
public Representation(MediaType mediaType, Date modificationDate, Tag tag) {
super(mediaType, modificationDate, tag);
}
/**
* Constructor.
*
* @param mediaType
* The media type.
* @param tag
* The tag.
*/
public Representation(MediaType mediaType, Tag tag) {
this(mediaType, null, tag);
}
/**
* Constructor from a variant.
*
* @param variant
* The variant to copy.
* @param modificationDate
* The modification date.
*/
public Representation(Variant variant, Date modificationDate) {
this(variant, modificationDate, null);
}
/**
* Constructor from a variant.
*
* @param variant
* The variant to copy.
* @param modificationDate
* The modification date.
* @param tag
* The tag.
*/
public Representation(Variant variant, Date modificationDate, Tag tag) {
setCharacterSet(variant.getCharacterSet());
setEncodings(variant.getEncodings());
setLocationRef(variant.getLocationRef());
setLanguages(variant.getLanguages());
setMediaType(variant.getMediaType());
setModificationDate(modificationDate);
setTag(tag);
}
/**
* Constructor from a variant.
*
* @param variant
* The variant to copy.
* @param tag
* The tag.
*/
public Representation(Variant variant, Tag tag) {
this(variant, null, tag);
}
/**
* Appends the representation to an appendable sequence of characters. This
* method is ensured to write the full content for each invocation unless it
* is a transient representation, in which case an exception is thrown.<br>
* <br>
* Note that {@link #getText()} is used by the default implementation.
*
* @param appendable
* The appendable sequence of characters.
* @throws IOException
*/
public void append(Appendable appendable) throws IOException {
appendable.append(getText());
}
/**
* Exhaust the content of the representation by reading it and silently
* discarding anything read. By default, it relies on {@link #getStream()}
* and closes the retrieved stream in the end.
*
* @return The number of bytes consumed or -1 if unknown.
*/
public long exhaust() throws IOException {
long result = -1L;
// [ifndef gwt]
if (isAvailable()) {
InputStream is = getStream();
result = IoUtils.exhaust(is);
is.close();
}
// [enddef]
return result;
}
/**
* Returns the size effectively available. This returns the same value as
* {@link #getSize()} if no range is defined, otherwise it returns the size
* of the range using {@link Range#getSize()}.
*
* @return The available size.
*/
public long getAvailableSize() {
return IoUtils.getAvailableSize(this);
}
// [ifndef gwt] member
/**
* Returns a channel with the representation's content.<br>
* If it is supported by a file, a read-only instance of FileChannel is
* returned.<br>
* This method is ensured to return a fresh channel for each invocation
* unless it is a transient representation, in which case null is returned.
*
* @return A channel with the representation's content.
* @throws IOException
*/
public abstract java.nio.channels.ReadableByteChannel getChannel()
throws IOException;
// [ifndef gwt] method
/**
* Returns the representation digest if any.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Content-MD5" header.
*
* @return The representation digest or null.
*/
public org.restlet.data.Digest getDigest() {
return this.digest;
}
/**
* Returns the disposition characteristics of the representation.
*
* @return The disposition characteristics of the representation.
*/
public Disposition getDisposition() {
return disposition;
}
/**
* Returns the future date when this representation expire. If this
* information is not known, returns null.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Expires" header.
*
* @return The expiration date.
*/
public Date getExpirationDate() {
return this.expirationDate;
}
/**
* Returns the range where in the full content the partial content available
* should be applied.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Content-Range" header.
*
* @return The content range or null if the full content is available.
*/
public Range getRange() {
return this.range;
}
/**
* Returns a characters reader with the representation's content. This
* method is ensured to return a fresh reader for each invocation unless it
* is a transient representation, in which case null is returned. If the
* representation has no character set defined, the system's default one
* will be used.
*
* @return A reader with the representation's content.
* @throws IOException
*/
public abstract Reader getReader() throws IOException;
/**
* Returns the total size in bytes if known, UNKNOWN_SIZE (-1) otherwise.
* When ranges are used, this might not be the actual size available. For
* this purpose, you can use the {@link #getAvailableSize()} method.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Content-Length" header.
*
* @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise.
* @see #isEmpty()
*/
public long getSize() {
return this.size;
}
/**
* Returns a stream with the representation's content. This method is
* ensured to return a fresh stream for each invocation unless it is a
* transient representation, in which case null is returned.
*
* @return A stream with the representation's content.
* @throws IOException
*/
public abstract InputStream getStream() throws IOException;
// [ifndef gwt] method
/**
* Converts the representation to a bytes array. Be careful when using this
* method as the conversion of large content to a string fully stored in
* memory can result in OutOfMemoryErrors being thrown.
*
* @return The representation as a bytes array.
*/
public byte[] getBytes() throws IOException {
byte[] result = null;
if (isEmpty()) {
result = new byte[0];
} else if (isAvailable()) {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
write(baos);
baos.flush();
result = baos.toByteArray();
}
return result;
}
// [ifndef gwt] method
/**
* Converts the representation to a string value. Be careful when using this
* method as the conversion of large content to a string fully stored in
* memory can result in OutOfMemoryErrors being thrown.
*
* @return The representation as a string value.
*/
public String getText() throws IOException {
String result = null;
if (isEmpty()) {
result = "";
} else if (isAvailable()) {
java.io.StringWriter sw = new java.io.StringWriter();
write(sw);
sw.flush();
result = sw.toString();
}
return result;
}
/**
* Indicates if the size of representation is known. It basically means that
* its size 0 or superior.
*
* @return True if the representation has content.
*/
public boolean hasKnownSize() {
return getSize() >= 0;
}
/**
* Indicates if some fresh content is potentially available, without having
* to actually call one of the content manipulation method like getStream()
* that would actually consume it. Note that when the size of a
* representation is 0 is a not considered available. However, sometimes the
* size isn't known until a read attempt is made, so availability doesn't
* guarantee a non empty content.<br>
* <br>
* This is especially useful for transient representation whose content can
* only be accessed once and also when the size of the representation is not
* known in advance.
*
* @return True if some fresh content is available.
*/
public boolean isAvailable() {
return this.available && (getSize() != 0);
}
/**
* Indicates if the representation is empty. It basically means that its
* size is 0.
*
* @return True if the representation has no content.
*/
public boolean isEmpty() {
return getSize() == 0;
}
// [ifdef gwt] method uncomment
// /**
// * Converts the representation to a string value. Be careful when using
// * this method as the conversion of large content to a string fully
// * stored in memory can result in OutOfMemoryErrors being thrown.
// *
// * @return The representation as a string value.
// */
// public abstract String getText() throws IOException;
/**
* Indicates if the representation's content is transient, which means that
* it can be obtained only once. This is often the case with representations
* transmitted via network sockets for example. In such case, if you need to
* read the content several times, you need to cache it first, for example
* into memory or into a file.
*
* @return True if the representation's content is transient.
*/
public boolean isTransient() {
return this.isTransient;
}
/**
* Releases the representation and all associated objects like streams,
* channels or files which are used to produce its content, transient or
* not. This method must be systematically called when the representation is
* no longer intended to be used. The framework automatically calls back
* this method via its connectors on the server-side when sending responses
* with an entity and on the client-side when sending a request with an
* entity. By default, it calls the {@link #setAvailable(boolean)} method
* with "false" as a value.<br>
* <br>
* Note that for transient socket-bound representations, calling this method
* after consuming the whole content shouldn't prevent the reuse of
* underlying socket via persistent connections for example. However, if the
* content hasn't been read, or has been partially read, the impact should
* be to discard the remaining content and to close the underlying
* connections.<br>
* <br>
* Therefore, if you are not interested in the content, or in the remaining
* content, you should first call the {@link #exhaust()} method or if this
* could be too costly, you should instead explicitly abort the parent
* request and the underlying connections using the {@link Request#abort()}
* method or a shortcut one like
* {@link org.restlet.resource.ServerResource#abort()} or
* {@link Response#abort()}.
*/
public void release() {
setAvailable(false);
}
/**
* Indicates if some fresh content is available.
*
* @param available
* True if some fresh content is available.
*/
public void setAvailable(boolean available) {
this.available = available;
}
// [ifndef gwt] method
/**
* Sets the representation digest.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Content-MD5" header.
*
* @param digest
* The representation digest.
*/
public void setDigest(org.restlet.data.Digest digest) {
this.digest = digest;
}
/**
* Sets the disposition characteristics of the representation.
*
* @param disposition
* The disposition characteristics of the representation.
*/
public void setDisposition(Disposition disposition) {
this.disposition = disposition;
}
/**
* Sets the future date when this representation expire. If this information
* is not known, pass null.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Expires" header.
*
* @param expirationDate
* The expiration date.
*/
public void setExpirationDate(Date expirationDate) {
this.expirationDate = DateUtils.unmodifiable(expirationDate);
}
/**
* Sets the range where in the full content the partial content available
* should be applied.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Content-Range" header.
*
* @param range
* The content range.
*/
public void setRange(Range range) {
this.range = range;
}
/**
* Sets the expected size in bytes if known, -1 otherwise. For this purpose,
* you can use the {@link #getAvailableSize()} method.<br>
* <br>
* Note that when used with HTTP connectors, this property maps to the
* "Content-Length" header.
*
* @param expectedSize
* The expected size in bytes if known, -1 otherwise.
*/
public void setSize(long expectedSize) {
this.size = expectedSize;
}
/**
* Indicates if the representation's content is transient.
*
* @param isTransient
* True if the representation's content is transient.
*/
public void setTransient(boolean isTransient) {
this.isTransient = isTransient;
}
// [ifndef gwt] member
/**
* Writes the representation to a characters writer. This method is ensured
* to write the full content for each invocation unless it is a transient
* representation, in which case an exception is thrown.<br>
* <br>
* Note that the class implementing this method shouldn't flush or close the
* given {@link java.io.Writer} after writing to it as this will be handled
* by the Restlet connectors automatically.
*
* @param writer
* The characters writer.
* @throws IOException
*/
public abstract void write(java.io.Writer writer) throws IOException;
// [ifndef gwt] member
/**
* Writes the representation to a byte channel. This method is ensured to
* write the full content for each invocation unless it is a transient
* representation, in which case an exception is thrown.
*
* @param writableChannel
* A writable byte channel.
* @throws IOException
*/
public abstract void write(
java.nio.channels.WritableByteChannel writableChannel)
throws IOException;
// [ifndef gwt] member
/**
* Writes the representation to a byte stream. This method is ensured to
* write the full content for each invocation unless it is a transient
* representation, in which case an exception is thrown.<br>
* <br>
* Note that the class implementing this method shouldn't flush or close the
* given {@link OutputStream} after writing to it as this will be handled by
* the Restlet connectors automatically.
*
* @param outputStream
* The output stream.
* @throws IOException
*/
public abstract void write(OutputStream outputStream) throws IOException;
}