/*
* DAVBitstream.java
*
* Version: $Revision: 3705 $
*
* Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $
*
* Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts
* Institute of Technology. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - 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.
*
* - Neither the name of the Hewlett-Packard Company nor the name of the
* Massachusetts Institute of Technology nor the names of their
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS 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 THE COPYRIGHT
* HOLDERS OR 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.
*/
package org.dspace.app.dav;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.List;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.content.Bitstream;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.Bundle;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.core.Utils;
import org.jdom.Element;
import org.jdom.Namespace;
/**
* This defines the behavior of DSpace "resources" in the WebDAV interface; it
* maps DAV operations onto DSpace object.
*/
class DAVBitstream extends DAVDSpaceObject
{
/** log4j category. */
private static Logger log = Logger.getLogger(DAVBitstream.class);
/** The item. */
private Item item = null;
/** The bitstream. */
private Bitstream bitstream = null;
/** The Constant BITSTREAM_INLINE_THRESHOLD.
* The longest bitstream that should be rendered "inline" (base64)
* see makeXmlBitstream
*/
private final static int BITSTREAM_INLINE_THRESHOLD = 2000;
/** The Constant getcontentlengthProperty. */
private static final Element getcontentlengthProperty = new Element(
"getcontentlength", DAV.NS_DAV);
/** The Constant getcontenttypeProperty. */
private static final Element getcontenttypeProperty = new Element(
"getcontenttype", DAV.NS_DAV);
/** The Constant sourceProperty. */
private static final Element sourceProperty = new Element("source",
DAV.NS_DSPACE);
/** The Constant descriptionProperty. */
private static final Element descriptionProperty = new Element(
"description", DAV.NS_DSPACE);
/** The Constant formatProperty. */
private static final Element formatProperty = new Element("format",
DAV.NS_DSPACE);
/** The Constant format_descriptionProperty. */
private static final Element format_descriptionProperty = new Element(
"format_description", DAV.NS_DSPACE);
/** The Constant checksumProperty. */
private static final Element checksumProperty = new Element("checksum",
DAV.NS_DSPACE);
/** The Constant checksum_algorithmProperty. */
private static final Element checksum_algorithmProperty = new Element(
"checksum_algorithm", DAV.NS_DSPACE);
/** The Constant sequence_idProperty. */
private static final Element sequence_idProperty = new Element(
"sequence_id", DAV.NS_DSPACE);
/** The Constant bundleProperty. */
private static final Element bundleProperty = new Element("bundle",
DAV.NS_DSPACE);
/** The all props. */
private static List allProps = new Vector(commonProps);
static
{
allProps.add(getcontentlengthProperty);
allProps.add(getcontenttypeProperty);
allProps.add(sourceProperty);
allProps.add(descriptionProperty);
allProps.add(formatProperty);
allProps.add(format_descriptionProperty);
allProps.add(checksumProperty);
allProps.add(checksum_algorithmProperty);
allProps.add(sequence_idProperty);
allProps.add(bundleProperty);
allProps.add(handleProperty);
}
/**
* Instantiates a new DAV bitstream.
* This gets called by matchResourceURI, for /retrieve_<dbid> format
*
* @param context the context
* @param request the request
* @param response the response
* @param pathElt the path elt
* @param bitstream the bitstream
*/
protected DAVBitstream(Context context, HttpServletRequest request,
HttpServletResponse response, String pathElt[], Bitstream bitstream)
{
super(context, request, response, pathElt, bitstream);
this.bitstream = bitstream;
this.type = TYPE_BITSTREAM;
}
/**
* Instantiates a new DAV bitstream.
*
* @param context the context
* @param request the request
* @param response the response
* @param pathElt the path elt
* @param item the item
* @param bitstream the bitstream
*/
protected DAVBitstream(Context context, HttpServletRequest request,
HttpServletResponse response, String pathElt[], Item item,
Bitstream bitstream)
{
super(context, request, response, pathElt, bitstream);
this.bitstream = bitstream;
this.type = TYPE_BITSTREAM;
this.item = item;
}
/**
* Make bitstream path element with filename extension, if given.
*
* @param sid the sid
* @param ext the ext
*
* @return bitstream path element
*/
protected static String getPathElt(int sid, String ext)
{
return "bitstream_" + String.valueOf(sid)
+ (ext == null ? "" : "." + ext);
}
/**
* Attempt to locate Bitstream object from URI. pathElt is
* "bitstream_{sid}.ext" or "retrieve_{db-id}.ext"
*
* @param context the context
* @param item the item
* @param pathElt the path elt
*
* @return the bitstream found (any errors throw an exception)
*
* @throws SQLException the SQL exception
* @throws DAVStatusException the DAV status exception
*/
protected static Bitstream findBitstream(Context context, Item item,
String pathElt) throws SQLException, DAVStatusException
{
try
{
// get rid of extension, if any, e.g. ".pdf"
int dot = pathElt.indexOf('.');
String strId = (dot >= 0) ? pathElt.substring(0, dot) : new String(
pathElt);
Bitstream result = null;
if (strId.startsWith("bitstream_"))
{
strId = strId.substring(10);
result = getBitstreamBySequenceID(item, Integer.parseInt(strId));
}
else if (strId.startsWith("retrieve_"))
{
strId = strId.substring(9);
result = Bitstream.find(context, Integer.parseInt(strId));
}
else
{
throw new DAVStatusException(
HttpServletResponse.SC_BAD_REQUEST,
"Unrecognized bitstream URI format.");
}
if (result == null)
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
"No bitstream at this sequence ID: " + pathElt);
}
return result;
}
catch (NumberFormatException nfe)
{
throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
"Invalid Bitstream Sequence ID in URI: " + pathElt);
}
}
/**
* Find bitstream with matching sequence id.
*
* @param item the item
* @param sid the sid
*
* @return bitstream, or null if none found.
*
* @throws SQLException the SQL exception
*/
protected static Bitstream getBitstreamBySequenceID(Item item, int sid)
throws SQLException
{
Bundle[] bundles = item.getBundles();
for (Bundle element : bundles)
{
Bitstream[] bitstreams = element.getBitstreams();
for (Bitstream element0 : bitstreams)
{
if (sid == element0.getSequenceID())
{
return element0;
}
}
}
return null;
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#getAllProperties()
*/
@Override
protected List getAllProperties()
{
return allProps;
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#children()
*/
@Override
protected DAVResource[] children() throws SQLException
{
return new DAVResource[0];
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVDSpaceObject#propfindInternal(org.jdom.Element)
*/
@Override
protected Element propfindInternal(Element property) throws SQLException,
AuthorizeException, IOException, DAVStatusException
{
String value = null;
/*
* FIXME: This implements permission check that really belongs in
* business logic. Although communities and collections don't check for
* read auth, Bitstream may contain sensitive data and should always
* check for READ permission.
*/
AuthorizeManager.authorizeAction(this.context, this.bitstream, Constants.READ);
// displayname - title or handle.
if (elementsEqualIsh(property, displaynameProperty))
{
value = this.bitstream.getName();
if (value == null)
{
value = makeDisplayname();
}
}
else if (elementsEqualIsh(property, getcontentlengthProperty))
{
value = String.valueOf(this.bitstream.getSize());
}
else if (elementsEqualIsh(property, getcontenttypeProperty))
{
value = this.bitstream.getFormat().getMIMEType();
}
else if (elementsEqualIsh(property, sourceProperty))
{
value = this.bitstream.getSource();
}
else if (elementsEqualIsh(property, descriptionProperty))
{
value = this.bitstream.getDescription();
}
else if (elementsEqualIsh(property, formatProperty))
{
BitstreamFormat bsf = this.bitstream.getFormat();
value = bsf == null ? null : bsf.getShortDescription();
}
else if (elementsEqualIsh(property, format_descriptionProperty))
{
value = this.bitstream.getFormatDescription();
}
else if (elementsEqualIsh(property, checksumProperty))
{
value = this.bitstream.getChecksum();
}
else if (elementsEqualIsh(property, checksum_algorithmProperty))
{
value = this.bitstream.getChecksumAlgorithm();
}
else if (elementsEqualIsh(property, sequence_idProperty))
{
int sid = this.bitstream.getSequenceID();
if (sid >= 0)
{
value = String.valueOf(sid);
}
}
else if (elementsEqualIsh(property, bundleProperty))
{
Bundle bn[] = this.bitstream.getBundles();
if (bn != null && bn.length > 0)
{
value = bn[0].getName();
}
}
else
{
return super.propfindInternal(property);
}
// value was set up by "if" clause:
if (value == null)
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
"Not found.");
}
Element p = new Element(property.getName(), property.getNamespace());
p.setText(filterForXML(value));
return p;
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#proppatchInternal(int, org.jdom.Element)
*/
@Override
protected int proppatchInternal(int action, Element prop)
throws SQLException, AuthorizeException, IOException,
DAVStatusException
{
Namespace ns = prop.getNamespace();
String propName = prop.getName();
boolean nsDspace = ns != null && ns.equals(DAV.NS_DSPACE);
String newValue = (action == DAV.PROPPATCH_REMOVE) ? null : prop
.getText();
// displayname - arbitrary string
if (elementsEqualIsh(prop, displaynameProperty))
{
this.bitstream.setName(newValue);
}
else if (nsDspace && propName.equals("description"))
{
this.bitstream.setDescription(newValue);
}
else if (nsDspace && propName.equals("source"))
{
this.bitstream.setSource(newValue);
}
else if (nsDspace && propName.equals("format_description"))
{
this.bitstream.setUserFormatDescription(newValue);
}
else if (nsDspace && propName.equals("format"))
{
if (action == DAV.PROPPATCH_REMOVE)
{
throw new DAVStatusException(DAV.SC_CONFLICT,
"The format property cannot be removed.");
}
BitstreamFormat bsf = BitstreamFormat.findByShortDescription(
this.context, newValue);
if (bsf == null)
{
throw new DAVStatusException(DAV.SC_CONFLICT,
"Cannot set format, no such Bitstream Format: "
+ newValue);
}
this.bitstream.setFormat(bsf);
}
else
{
throw new DAVStatusException(DAV.SC_CONFLICT, "The "
+ prop.getName() + " property cannot be changed.");
}
// this assumes we got through an IF clause and changed something:
this.bitstream.update();
return HttpServletResponse.SC_OK;
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#get()
*/
@Override
protected void get() throws SQLException, AuthorizeException,
IOException, DAVStatusException
{
if (this.bitstream == null)
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
"Bitstream not found, URI=\"" + hrefURL() + "\"");
}
else
{
if (this.item != null)
{
log.info(LogManager.getHeader(this.context, "DAV GET Bitstream",
"item handle=" + this.item.getHandle() + ", bitstream_id="
+ this.bitstream.getID()));
}
// Set the response MIME type
this.response.setContentType(this.bitstream.getFormat().getMIMEType());
// Response length
this.response.setHeader("Content-Length", String.valueOf(this.bitstream
.getSize()));
// Pipe the bits
InputStream is = this.bitstream.retrieve();
Utils.bufferedCopy(is, this.response.getOutputStream());
is.close();
this.response.getOutputStream().flush();
}
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#put()
*/
@Override
protected void put() throws SQLException, AuthorizeException,
IOException, DAVStatusException
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_IMPLEMENTED,
"PUT is not implemented for Bitstream (yet?).");
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#copyInternal(org.dspace.app.dav.DAVResource, int, boolean, boolean)
*/
@Override
protected int copyInternal(DAVResource destination, int depth,
boolean overwrite, boolean keepProperties)
throws DAVStatusException, SQLException, AuthorizeException,
IOException
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_IMPLEMENTED,
"COPY method not implemented.");
}
/**
* Make a default name to go in properties' displayname Should be last path
* element of canonical resource URI.
*
* @return name string
*/
private String makeDisplayname()
{
String ext[] = this.bitstream.getFormat().getExtensions();
String prefix = (this.item == null) ? "retrieve_"
+ String.valueOf(this.bitstream.getID()) : "bitstream_"
+ String.valueOf(this.bitstream.getSequenceID());
return prefix + (ext.length > 0 ? ext[0] : "");
}
/**
* Match the URIs this subclass understands and return the corresponding
* resource.
*
* @param context the context
* @param request the request
* @param response the response
* @param pathElt the path elt
*
* @return the DAV resource
*
* @throws DAVStatusException the DAV status exception
* @throws SQLException the SQL exception
*/
protected static DAVResource matchResourceURI(Context context,
HttpServletRequest request, HttpServletResponse response,
String pathElt[]) throws DAVStatusException, SQLException
{
/**
* Match URI /retrieve_<DbId> NOTE: This is an evil kludge to get raw
* bitstreams by DB ID, required to implement link form of
* <dspace:bitstream> element in properties. The "logo" of Community or
* Collection is a loose bitstream not connected to any Item, so it can
* only be identified by a direct database-ID reference. Ugh.
*/
if (pathElt[0].startsWith("retrieve_"))
{
Bitstream bs = findBitstream(context, null, pathElt[0]);
return new DAVBitstream(context, request, response, pathElt, bs);
}
return null;
}
/**
* Returns an XML representation of a bitstream -- either inline content or
* a link reference. The XML looks like:
*
* <pre>
* <dspace:bitstream>
* <dspace:link href="url-to-bitstream">
* </dspace:bitstream>
* ...or...
* <dspace:bitstream>
* <dspace:content contenttype="image/gif" contentlength="299" contentencoding="base64">
* ...text of base64..
* </dspace:content>
* </dspace:bitstream>
* NOTE: contentlength is the DECODED length of the content.
* </pre>
*
* Used by the "logo" property on collections and communities.
*
* @param bitstream the bitstream
* @param resource the resource
*
* @return the element
*
* @throws AuthorizeException the authorize exception
* @throws SQLException the SQL exception
* @throws IOException Signals that an I/O exception has occurred.
*/
protected static Element makeXmlBitstream(Bitstream bitstream,
DAVResource resource) throws AuthorizeException, SQLException,
IOException
{
Element b = new Element("bitstream", DAV.NS_DSPACE);
long length = bitstream.getSize();
BitstreamFormat bf = bitstream.getFormat();
if (length > BITSTREAM_INLINE_THRESHOLD)
{
Element e = new Element("link", DAV.NS_DSPACE);
e.setAttribute("href", resource.hrefPrefix() + "retrieve_"
+ String.valueOf(bitstream.getID()));
b.addContent(e);
}
else
{
Element e = new Element("content", DAV.NS_DSPACE);
if (bf != null)
{
e.setAttribute("contenttype", bf.getMIMEType());
}
e.setAttribute("contentlength", String.valueOf(length));
e.setAttribute("contentencoding", "base64");
b.addContent(e);
// write encoding of bitstream contents
ByteArrayOutputStream baos = new ByteArrayOutputStream((int) length);
Utils.copy(bitstream.retrieve(), baos);
e.setText(new String(Base64.encodeBase64(baos.toByteArray())));
}
return b;
}
/**
* Extract bitstream from the XML representation, i.e.
*
* <pre>
* <dspace:bitstream>
* <dspace:content contenttype="image/gif"
* contentlength="299"
* contentencoding="base64">
* ...text of base64..
* </dspace:content>
* </dspace:bitstream>
* </pre>
*
* In the above format, contenttype and contentencoding attributes of
* content are REQUIRED.
*
* @param context the context
* @param xb the xb
*
* @return inputstream of the contents of the data, or null on error.
*/
protected static InputStream getXmlBitstreamContent(Context context,
Element xb)
{
Element c = xb.getChild("content", DAV.NS_DSPACE);
if (c != null)
{
String enc = c.getAttributeValue("contentencoding");
if (enc != null && enc.equals("base64"))
{
byte value[] = Base64.decodeBase64(c.getText().getBytes());
return new ByteArrayInputStream(value);
}
}
return null;
}
/**
* Get the content-type from an XML-encoded bitstream.
*
* @param context required for reading the DB.
* @param xb XML bitstream representation in JDOM.
*
* @return First BitstreamFormat matching content-type string, or null if
* none.
*
* @throws SQLException the SQL exception
*/
protected static BitstreamFormat getXmlBitstreamFormat(Context context,
Element xb) throws SQLException
{
Element c = xb.getChild("content", DAV.NS_DSPACE);
if (c != null)
{
String ctype = c.getAttributeValue("contenttype");
if (ctype != null)
{
BitstreamFormat af[] = BitstreamFormat.findAll(context);
for (BitstreamFormat element : af)
{
if (ctype.equals(element.getMIMEType()))
{
return element;
}
}
}
}
return null;
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#deleteInternal()
*/
@Override
protected int deleteInternal() throws DAVStatusException, SQLException,
AuthorizeException, IOException
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_IMPLEMENTED,
"DELETE method not implemented for BitStream.");
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#mkcolInternal(java.lang.String)
*/
@Override
protected int mkcolInternal(String waste) throws DAVStatusException,
SQLException, AuthorizeException, IOException
{
throw new DAVStatusException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"MKCOL method not allowed for BitStream.");
}
}