/*
* DAVLookup.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.IOException;
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.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.handle.HandleManager;
import org.jdom.Element;
/**
* The Lookup resource translates a DSpace persistent object identifier (i.e. an
* Item or Bitstream Handle) into a DAV resource URI for the LNI. It accepts two
* simple, flexible formats: one for Item handles and one for a bitstream within
* an Item.
* <p>
* Any GET, PUT, PROPFIND, etc response gets a "Temporarily Moved" status and
* the DAV URL in the "Location:" header of the response.
* <p>
* The "lookup" URI format:
*
* <pre>
* {prefix}/lookup/handle/{hdl-prefix}/{hdl-suffix} ... item Handle
* e.g.
* {prefix}/lookup/handle/1234.56/99 ... item Handle
* {prefix}/lookup/handle/1234.56%2f99 ... item Handle
* {prefix}/lookup/bitstream-handle/{seq-id}/{hdl-prefix}/{hdl-suffix}
* e.g.
* {prefix}/lookup/bitstream-handle/13/1234.56/99 ... bitstream Handle
* {prefix}/lookup/bitstream-handle/13/1234.56%2f99 ... bitstream Handle
* </pre>
*/
class DAVLookup extends DAVResource
{
/** log4j category. */
private static Logger log = Logger.getLogger(DAVLookup.class);
/**
* Instantiates a new DAV lookup.
*
* @param context the context
* @param request the request
* @param response the response
* @param pathElt the path elt
*/
protected DAVLookup(Context context, HttpServletRequest request,
HttpServletResponse response, String pathElt[])
{
super(context, request, response, pathElt);
}
// empty property list, this class doesn't implement propfind.
/** The Constant allProps. */
private static final List allProps = new Vector();
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#getAllProperties()
*/
@Override
protected List getAllProperties()
{
return allProps;
}
/**
* 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 a DAVLookup resource if we can parse this URI, or null.
*
* @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
{
// The "/lookup" request:
if (pathElt[0].equals("lookup"))
{
return new DAVLookup(context, request, response, pathElt);
}
return null;
}
/**
* Send a redirect (302) response to client with DAV URL of the resource for
* this handle and/or bitstream. Puts URL in the <code>Location:</code>
* header.
*
* @return URL in string form for desired DAV resource.
*
* @throws IOException Signals that an I/O exception has occurred.
* @throws SQLException the SQL exception
* @throws DAVStatusException the DAV status exception
*
* @throw IOException
* @throw SQLException
*/
private void doRedirect() throws IOException, SQLException,
DAVStatusException
{
DSpaceObject dso = null;
String bsPid = null;
/*
* FIXME: (maybe?) NOTE: This is currently hard-wired to accomodate the
* syntax of Handles, with "prefix/suffix" separated by the slash --
* that means the Handle probably takes up multiple path elements,
* unless the client escaped the '/'. This code *might* need adjusting
* if we allow other kinds of persistent identifiers for DSpace objects.
*/
int hdlStart = -1;
if (this.pathElt.length > 2 && this.pathElt[1].equals("handle"))
{
hdlStart = 2;
}
else if (this.pathElt.length > 3 && this.pathElt[1].equals("bitstream-handle"))
{
bsPid = this.pathElt[2];
hdlStart = 3;
}
else
{
throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
"Unrecognized 'lookup' request format.");
}
String prefix = decodeHandle(this.pathElt[hdlStart]);
String handle = null;
// if "prefix" contains a slash, then it's the whole handle:
if (prefix.indexOf("/") >= 0)
{
handle = prefix;
log.debug("Lookup: resolving escaped handle \"" + handle + "\"");
}
else if (this.pathElt.length >= hdlStart + 2)
{
StringBuffer hdl = new StringBuffer(prefix);
for (int i = hdlStart + 1; i < this.pathElt.length; ++i)
{
hdl.append("/");
hdl.append(this.pathElt[i]);
}
handle = hdl.toString();
log.debug("Lookup: resolving multielement handle \"" + handle
+ "\"");
}
else
{
throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
"Incomplete handle in lookup request.");
}
// did handle lookup fail?
dso = HandleManager.resolveToObject(this.context, handle);
if (dso == null)
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
"Cannot resolve handle \"" + handle + "\"");
}
// bitstream must exist too
String location = makeLocation(dso, bsPid);
if (location == null)
{
throw new DAVStatusException(HttpServletResponse.SC_NOT_FOUND,
"Bitstream \"" + bsPid + "\" does not exist in \"" + handle
+ "\"");
}
// add query string -- unnecessary, but it helps naive clients that
// use GET with "package" query arg to download an Item.
String qs = this.request.getQueryString();
if (qs != null)
{
location += "?" + qs;
}
log.debug("Lookup returning redirect to: " + location);
this.response.setHeader("Location", location);
this.response.sendError(HttpServletResponse.SC_MOVED_TEMPORARILY,
"These are not the droids you are looking for.");
}
/**
* Create URI as string for a given handle and optional bitstream. URI is
* relative to top of DAV hierarchy, but starts with '/'.
*
* @param handle handle of a DSpace object (Item, Collection, etc)
* @param bsPid bitstream persistent identifier.
*
* @return "absolute" URI from top of DAV hierarchy
*
* @throws IOException Signals that an I/O exception has occurred.
* @throws SQLException the SQL exception
*/
protected String makeURI(String handle, String bsPid) throws IOException,
SQLException
{
DSpaceObject dso = HandleManager.resolveToObject(this.context, handle);
if (dso == null)
{
return null;
}
return makeURI(dso, bsPid);
}
/**
* Create URI as string for a given handle and optional bitstream. URI is
* relative to top of DAV hierarchy, but starts with '/'.
*
* @param dso a DSpace object (Item, Collection, etc)
* @param bsPid bitstream persistent identifier.
*
* @return "absolute" URI from top of DAV hierarchy
*
* @throws IOException Signals that an I/O exception has occurred.
* @throws SQLException the SQL exception
*/
private String makeURI(DSpaceObject dso, String bsPid) throws IOException,
SQLException
{
// make sure that bitstream actually exists:
if (bsPid != null)
{
if (dso.getType() != Constants.ITEM)
{
log.warn("Non-Item with Bitstream Sequence ID in DAV Lookup.");
return null;
}
try
{
int pid = Integer.parseInt(bsPid);
if (DAVBitstream.getBitstreamBySequenceID((Item) dso, pid) == null)
{
log
.warn("Bitstream Sequence ID Not Found in DAV Lookup: \""
+ bsPid + "\"");
return null;
}
}
catch (NumberFormatException nfe)
{
log.warn("Invalid Bitstream Sequence ID in DAV Lookup: \""
+ bsPid + "\"");
return null;
}
}
String base = "/" + DAVDSpaceObject.getPathElt(dso);
if (bsPid != null)
{
return base + "/bitstream_" + bsPid;
}
else
{
return base;
}
}
// returns fully-qualified URL or null upon error.
/**
* Make location.
*
* @param dso the dso
* @param bsPid the bs pid
*
* @return the string
*
* @throws IOException Signals that an I/O exception has occurred.
* @throws SQLException the SQL exception
*/
private String makeLocation(DSpaceObject dso, String bsPid)
throws IOException, SQLException
{
String prefix = hrefPrefix();
String rest = makeURI(dso, bsPid);
if (rest == null)
{
return null;
}
// delete leading '/' from URI since prefix has trailing one
return prefix + rest.substring(1);
}
/**
* placeholder that does nothing since propfind() is overridden.
*
* @param property the property
*
* @return the element
*
* @throws SQLException the SQL exception
* @throws AuthorizeException the authorize exception
* @throws IOException Signals that an I/O exception has occurred.
* @throws DAVStatusException the DAV status exception
*/
@Override
protected Element propfindInternal(Element property) throws SQLException,
AuthorizeException, IOException, DAVStatusException
{
return null;
}
/**
* Override propfind() to make sure it always returns a redirect.
*
* @throws SQLException the SQL exception
* @throws AuthorizeException the authorize exception
* @throws IOException Signals that an I/O exception has occurred.
* @throws DAVStatusException the DAV status exception
*/
@Override
protected void propfind() throws SQLException,
AuthorizeException, IOException, DAVStatusException
{
this.doRedirect();
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#proppatchInternal(int, org.jdom.Element)
*/
@Override
protected int proppatchInternal(int mode, Element prop)
throws SQLException, AuthorizeException, IOException,
DAVStatusException
{
return HttpServletResponse.SC_METHOD_NOT_ALLOWED;
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#get()
*/
@Override
protected void get() throws SQLException, AuthorizeException,
IOException, DAVStatusException
{
this.doRedirect();
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#put()
*/
@Override
protected void put() throws SQLException, AuthorizeException,
IOException, DAVStatusException
{
this.doRedirect();
}
/**
* Reject copy. Client should get resource URL first.
*
* @param destination the destination
* @param depth the depth
* @param overwrite the overwrite
* @param keepProperties the keep properties
*
* @return the int
*
* @throws DAVStatusException the DAV status exception
* @throws SQLException the SQL exception
* @throws AuthorizeException the authorize exception
* @throws IOException Signals that an I/O exception has occurred.
*/
@Override
protected int copyInternal(DAVResource destination, int depth,
boolean overwrite, boolean keepProperties)
throws DAVStatusException, SQLException, AuthorizeException,
IOException
{
throw new DAVStatusException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"COPY method not allowed on lookup resource.");
}
/**
* This should never get called.
*
* @return the DAV resource[]
*
* @throws SQLException the SQL exception
*/
@Override
protected DAVResource[] children() throws SQLException
{
return new DAVResource[0];
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#typeValue()
*/
@Override
protected Element typeValue()
{
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 Lookup.");
}
/* (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 Lookup.");
}
}