/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package org.fcrepo.server.storage.types; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; import org.fcrepo.server.errors.RangeNotSatisfiableException; import org.fcrepo.server.errors.ServerException; import org.fcrepo.server.errors.StreamIOException; import org.fcrepo.utilities.io.ByteRangeInputStream; import org.fcrepo.utilities.io.NullInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Data structure for holding a MIME-typed stream. * * @author Ross Wayland * @version $Id$ */ public class MIMETypedStream { private static final Logger logger = LoggerFactory.getLogger(MIMETypedStream.class); public static final String MIME_INTERNAL_REDIRECT = "application/fedora-redirect"; public static final String MIME_INTERNAL_NOT_MODIFIED = "application/fedora-unmodified"; public static final long NO_CONTENT_LENGTH = -1L; private String MIMEType; private InputStream m_stream; public Property[] header; private long m_size = -1; private boolean m_gotStream = false; private int m_httpStatus = HttpStatus.SC_OK; /** * Constructs a MIMETypedStream. * * @param MIMEType * The MIME type of the byte stream. * @param stream * The byte stream. */ public MIMETypedStream(String MIMEType, InputStream stream, Property[] header) { this(MIMEType, stream, header, -1L); } /** * Constructs a MIMETypedStream. * * @param MIMEType * The MIME type of the byte stream. * @param stream * The byte stream. */ public MIMETypedStream(String MIMEType, InputStream stream, Property[] header, long size) { this(MIMEType, stream, header, size, 200); } private MIMETypedStream(String MIMEType, InputStream stream, Property[] header, long size, int status) { this.MIMEType = MIMEType; this.header = header; this.m_size = size; this.m_httpStatus = status; setStream(stream); } public String getMIMEType() { return this.MIMEType; } /** * Retrieves the underlying stream. * Caller is responsible to close the stream, * either by calling MIMETypedStream.close() * or by calling close() on the stream. * * @return The byte stream */ public synchronized InputStream getStream() { m_gotStream = true; return m_stream; } public synchronized void setStream(InputStream stream) { m_gotStream = false; this.m_stream = stream; } /** * Closes the underlying stream if it's not already closed. * * In the event of an error, a warning will be logged. */ public void close() { if (this.m_stream != null) { try { this.m_stream.close(); this.m_stream = null; } catch (IOException e) { logger.warn("Error closing stream", e); } } } /** * Ensures the underlying stream is closed at garbage-collection time * if the stream has not been retrieved. If getStream() has been called * the caller is responsible to close the stream. * * {@inheritDoc} */ @Override public void finalize() { if(!m_gotStream) { close(); } } public long getSize() { return m_size; } public void setRange(String rangeRequest) throws ServerException { if(rangeRequest != null && !rangeRequest.isEmpty() && !m_gotStream){ try{ ByteRangeInputStream range = new ByteRangeInputStream(m_stream, m_size, rangeRequest); setStream(range); m_size = range.length; m_httpStatus = HttpStatus.SC_PARTIAL_CONTENT; setContentRange(range.contentRange); } catch (IOException e) { throw new StreamIOException(e.getMessage(),e); } catch (IndexOutOfBoundsException e) { throw new RangeNotSatisfiableException(e.getMessage()); } } } /** * Typically 200, but control group R datastream content responses will use * 302, and conditional GET of datastream contents may return a 304 * @return int status code to use for the response */ public int getStatusCode() { return m_httpStatus; } public void setStatusCode(int status) { m_httpStatus = status; } private void setContentRange(String val) { boolean found = false; for(Property prop: this.header){ if (prop.name.equalsIgnoreCase(HttpHeaders.CONTENT_RANGE)) { prop.value = val; found = true; } } if (!found){ Property[] newHeader = new Property[this.header.length + 1]; System.arraycopy(this.header, 0, newHeader,0,this.header.length); newHeader[newHeader.length - 1] = new Property(HttpHeaders.CONTENT_RANGE, val); this.header = newHeader; } } public static MIMETypedStream getRedirect(Property[] header) { return new MIMETypedStream( MIME_INTERNAL_REDIRECT, NullInputStream.NULL_STREAM, header, NO_CONTENT_LENGTH, HttpStatus.SC_MOVED_TEMPORARILY); } public static MIMETypedStream getRedirect(String location) { MIMETypedStream result = getRedirect(new Property[]{new Property(HttpHeaders.LOCATION, location)}); result.setStream(new ByteArrayInputStream(location.getBytes(Charset.forName("UTF-8")))); return result; } public static MIMETypedStream getNotModified(Property[] header) { return new MIMETypedStream( MIME_INTERNAL_NOT_MODIFIED, NullInputStream.NULL_STREAM, header, NO_CONTENT_LENGTH, HttpStatus.SC_NOT_MODIFIED); } }