org.dspace.app.dav.DAVBitstream.java Source code

Java tutorial

Introduction

Here is the source code for org.dspace.app.dav.DAVBitstream.java

Source

/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
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.ArrayList;
import java.util.List;

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 static final 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<Element> allProps = new ArrayList<Element>(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) : 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, nfe);
        }
    }

    /**
     * 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<Element> 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>
     * &lt;dspace:bitstream&gt;
     * &lt;dspace:link href=&quot;url-to-bitstream&quot;&gt;
     * &lt;/dspace:bitstream&gt;
     * ...or...
     * &lt;dspace:bitstream&gt;
     * &lt;dspace:content contenttype=&quot;image/gif&quot; contentlength=&quot;299&quot; contentencoding=&quot;base64&quot;&gt;
     * ...text of base64..
     * &lt;/dspace:content&gt;
     * &lt;/dspace:bitstream&gt;
     * 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>
     * &lt;dspace:bitstream&gt;
     * &lt;dspace:content contenttype=&quot;image/gif&quot;
     * contentlength=&quot;299&quot;
     * contentencoding=&quot;base64&quot;&gt;
     * ...text of base64..
     * &lt;/dspace:content&gt;
     * &lt;/dspace:bitstream&gt;
     * </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.");
    }
}