/**
* 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.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
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.authorize.AuthorizeManager;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.Collection;
import org.dspace.content.Metadatum;
import org.dspace.content.Item;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.packager.PackageDisseminator;
import org.dspace.content.packager.PackageException;
import org.dspace.content.packager.PackageParameters;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.PluginManager;
import org.dspace.core.Utils;
import org.dspace.eperson.EPerson;
import org.dspace.license.CreativeCommons;
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.s
*/
class DAVItem extends DAVDSpaceObject
{
/** log4j category. */
private static Logger log = Logger.getLogger(DAVItem.class);
/** The item. */
private Item item = null;
/** The Constant submitterProperty. */
private static final Element submitterProperty = new Element("submitter",
DAV.NS_DSPACE);
/** The Constant getlastmodifiedProperty. */
private static final Element getlastmodifiedProperty = new Element(
"getlastmodified", DAV.NS_DAV);
/** The Constant licenseProperty. */
private static final Element licenseProperty = new Element("license",
DAV.NS_DSPACE);
/** The Constant cc_license_textProperty. */
private static final Element cc_license_textProperty = new Element(
"cc_license_text", DAV.NS_DSPACE);
/** The Constant cc_license_rdfProperty. */
private static final Element cc_license_rdfProperty = new Element(
"cc_license_rdf", DAV.NS_DSPACE);
/** The Constant cc_license_urlProperty. */
private static final Element cc_license_urlProperty = new Element(
"cc_license_url", DAV.NS_DSPACE);
/** The Constant owning_collectionProperty. */
private static final Element owning_collectionProperty = new Element(
"owning_collection", DAV.NS_DSPACE);
/** The Constant withdrawnProperty. */
private static final Element withdrawnProperty = new Element("withdrawn",
DAV.NS_DSPACE);
/** The all props. */
private static List<Element> allProps = new ArrayList<Element>(commonProps);
static
{
allProps.add(submitterProperty);
allProps.add(getlastmodifiedProperty);
allProps.add(licenseProperty);
allProps.add(cc_license_textProperty);
allProps.add(cc_license_rdfProperty);
allProps.add(cc_license_urlProperty);
allProps.add(owning_collectionProperty);
allProps.add(handleProperty);
allProps.add(withdrawnProperty);
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#getAllProperties()
*/
@Override
protected List<Element> getAllProperties()
{
return allProps;
}
/**
* Instantiates a new DAV item.
*
* @param context the context
* @param request the request
* @param response the response
* @param pathElt the path elt
* @param item the item
*/
protected DAVItem(Context context, HttpServletRequest request,
HttpServletResponse response, String pathElt[], Item item)
{
super(context, request, response, pathElt, item);
this.type = TYPE_ITEM;
this.item = item;
}
/**
* Match Item URIs that identify the item by a database ID. Handle URIs are
* matched by DAVDSpaceObject.
*
* @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
{
int id = -1;
String bsElt = null;
try
{
// The "/item_db_" element in last or next-to-last element
if (pathElt[pathElt.length - 1].startsWith("item_db_"))
{
id = Integer.parseInt(pathElt[pathElt.length - 1].substring(8));
}
else if (pathElt[pathElt.length - 1].startsWith("bitstream_")
&& pathElt.length > 1
&& pathElt[pathElt.length - 2].startsWith("item_db_"))
{
id = Integer.parseInt(pathElt[pathElt.length - 2].substring(8));
bsElt = pathElt[pathElt.length - 1];
}
if (id >= 0)
{
Item item = Item.find(context, id);
if (item == null)
{
throw new DAVStatusException(
HttpServletResponse.SC_NOT_FOUND, "Item with ID="
+ String.valueOf(id) + " not found.");
}
if (bsElt != null)
{
Bitstream bs = DAVBitstream.findBitstream(context, item,
bsElt);
if (bs == null)
{
throw new DAVStatusException(
HttpServletResponse.SC_NOT_FOUND,
"Bitstream not found.");
}
return new DAVBitstream(context, request, response,
pathElt, item, bs);
}
else
{
return new DAVItem(context, request, response, pathElt,
item);
}
}
return null;
}
catch (NumberFormatException ne)
{
throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
"Error parsing number in request URI.", ne);
}
}
/**
* Return pathname element in db-id format.
*
* @param dbid the dbid
*
* @return the path elt
*/
protected static String getPathElt(int dbid)
{
return "item_db_" + String.valueOf(dbid);
}
/**
* Return pathname element to this Item. Use db-id format if no handle,
* otherwise the DSpace object format.
*
* @param item the item
*
* @return the path elt
*/
protected static String getPathElt(Item item)
{
String handle = item.getHandle();
if (handle == null)
{
return getPathElt(item.getID());
}
else
{
return DAVDSpaceObject.getPathElt(item);
}
}
/**
* Return this resource's children. Item's children are its bitstreams.
*
* @return the DAV resource[]
*
* @throws SQLException the SQL exception
*/
@Override
protected DAVResource[] children() throws SQLException
{
// Check for overall read permission on Item
if (!AuthorizeManager.authorizeActionBoolean(this.context, this.item,
Constants.READ))
{
return new DAVResource[0];
}
Vector result = new Vector();
Bundle[] bundles = this.item.getBundles();
for (Bundle element : bundles)
{
// check read permission on this Bundle
if (!AuthorizeManager.authorizeActionBoolean(this.context, element,
Constants.READ))
{
continue;
}
Bitstream[] bitstreams = element.getBitstreams();
for (Bitstream element0 : bitstreams)
{
String ext[] = element0.getFormat().getExtensions();
result.add(new DAVBitstream(this.context, this.request, this.response,
makeChildPath(DAVBitstream.getPathElt(element0
.getSequenceID(), ext.length < 1 ? null
: ext[0])), this.item, element0));
}
}
return (DAVResource[]) result.toArray(new DAVResource[result.size()]);
}
/* (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, Item may contain sensitive data and should always check
* for READ permission. Exception: allow "withdrawn" property to be
* checked regardless of authorization since all permissions are removed
* when item is withdrawn.
*/
if (!elementsEqualIsh(property, withdrawnProperty))
{
AuthorizeManager.authorizeAction(this.context, this.item, Constants.READ);
}
if (elementsEqualIsh(property, withdrawnProperty))
{
value = String.valueOf(this.item.isWithdrawn());
}
else if (elementsEqualIsh(property, displaynameProperty))
{
// displayname - title or handle.
Metadatum titleDc[] = this.item.getDC("title", Item.ANY, Item.ANY);
value = titleDc.length > 0 ? titleDc[0].value : this.item.getHandle();
}
else if (elementsEqualIsh(property, handleProperty))
{
value = canonicalizeHandle(this.item.getHandle());
}
else if (elementsEqualIsh(property, submitterProperty))
{
EPerson ep = this.item.getSubmitter();
if (ep != null)
{
value = hrefToEPerson(ep);
}
}
else if (elementsEqualIsh(property, owning_collectionProperty))
{
Collection owner = this.item.getOwningCollection();
if (owner != null)
{
value = canonicalizeHandle(owner.getHandle());
}
}
else if (elementsEqualIsh(property, getlastmodifiedProperty))
{
value = DAV.applyHttpDateFormat(this.item.getLastModified());
}
else if (elementsEqualIsh(property, licenseProperty))
{
value = getLicenseAsString();
}
else if (elementsEqualIsh(property, cc_license_textProperty))
{
value = CreativeCommons.getLicenseText(this.item);
}
else if (elementsEqualIsh(property, cc_license_rdfProperty))
{
value = CreativeCommons.getLicenseRDF(this.item);
}
else if (elementsEqualIsh(property, cc_license_urlProperty))
{
value = CreativeCommons.getLicenseURL(this.item);
}
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;
}
/**
* Get the license for this Item as String. License is the first bitstream
* named "license.txt" in a LICENSE bundle, apparently?
* <p>
* FIXME: is this correct? there's no counterexample..
*
* @return license string, or null if none found.
*
* @throws SQLException the SQL exception
* @throws AuthorizeException the authorize exception
* @throws IOException Signals that an I/O exception has occurred.
*/
private String getLicenseAsString() throws SQLException,
AuthorizeException, IOException
{
Bundle lb[] = this.item.getBundles(Constants.LICENSE_BUNDLE_NAME);
for (Bundle element : lb)
{
Bitstream lbs = element.getBitstreamByName("license.txt");
if (lbs != null)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream(
(int) lbs.getSize());
Utils.copy(lbs.retrieve(), baos);
return baos.toString();
}
}
return null;
}
/* (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
{
// Don't need any authorization checks since the Item layer
// checks authorization for write and delete.
Namespace ns = prop.getNamespace();
String propName = prop.getName();
// "submitter" is the only Item-specific mutable property, rest are
// live and unchangeable.
if (ns != null && ns.equals(DAV.NS_DSPACE)
&& propName.equals("submitter"))
{
if (action == DAV.PROPPATCH_REMOVE)
{
throw new DAVStatusException(DAV.SC_CONFLICT,
"The submitter property cannot be removed.");
}
String newName = prop.getText();
EPerson ep = EPerson.findByEmail(this.context, newName);
if (ep == null)
{
throw new DAVStatusException(DAV.SC_CONFLICT,
"Cannot set submitter, no EPerson found for email address: "
+ newName);
}
this.item.setSubmitter(ep);
this.item.update();
return HttpServletResponse.SC_OK;
}
throw new DAVStatusException(DAV.SC_CONFLICT, "The " + prop.getName()
+ " property cannot be changed.");
}
/**
* GET implementation returns the contents of the Item as a package. The
* query arg "package" must be specified.
*
* @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 get() throws SQLException, AuthorizeException,
IOException, DAVStatusException
{
// Check for overall read permission on Item, because nothing else will
AuthorizeManager.authorizeAction(this.context, this.item, Constants.READ);
String packageType = this.request.getParameter("package");
if (packageType == null)
{
packageType = "default";
}
PackageDisseminator dip = (PackageDisseminator) PluginManager
.getNamedPlugin(PackageDisseminator.class, packageType);
if (dip == null)
{
throw new DAVStatusException(HttpServletResponse.SC_BAD_REQUEST,
"Cannot find a disseminate plugin for package="
+ packageType);
}
else
{
try
{
// Create a temporary file to disseminate into
String tempDirectory = (ConfigurationManager.getProperty("upload.temp.dir") != null)
? ConfigurationManager.getProperty("upload.temp.dir") : System.getProperty("java.io.tmpdir");
File tempFile = File.createTempFile("DAVItemGet" + this.item.hashCode(), null, new File(tempDirectory));
tempFile.deleteOnExit();
// Disseminate item to temporary file
PackageParameters pparams = PackageParameters.create(this.request);
this.response.setContentType(dip.getMIMEType(pparams));
dip.disseminate(this.context, this.item, pparams, tempFile);
// Copy temporary file contents to response stream
FileInputStream fileIn = null;
try
{
fileIn = new FileInputStream(tempFile);
Utils.copy(fileIn, this.response.getOutputStream());
}
finally
{
if (fileIn != null)
{
fileIn.close();
}
}
}
catch (CrosswalkException pe)
{
throw new DAVStatusException(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Failed in crosswalk of metadata: " + pe.toString(), pe);
}
catch (PackageException pe)
{
pe.log(log);
throw new DAVStatusException(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, pe
.toString(), pe);
}
}
}
/* (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 Item.");
}
/**
* COPY is implemented by an "add", which is the closest we can get in
* DSpace semantics.
*
* @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
{
return addItemToCollection(this.context, this.item, destination, overwrite);
}
/**
* Do the work of "copy" method; this code is shared with e.g.
* DAVInProgressSubmission.
*
* @param context the context
* @param item the item
* @param destination the destination
* @param overwrite the overwrite
*
* @return HTTP status code.
*
* @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.
*/
protected static int addItemToCollection(Context context, Item item,
DAVResource destination, boolean overwrite)
throws DAVStatusException, SQLException, AuthorizeException,
IOException
{
// sanity checks
if (!(destination instanceof DAVCollection))
{
throw new DAVStatusException(
HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"COPY of Item is only allowed when destination is a DSpace Collection.");
}
// access check
AuthorizeManager.authorizeAction(context, item, Constants.READ);
// make sure item doesn't belong to this collection
Collection destColl = ((DAVCollection) destination).getCollection();
log.debug("COPY from=" + item.toString() + " (" + item.getHandle()
+ "), to=" + destColl.toString() + " (" + destColl.getHandle()
+ ")");
// check if it's already a member
Collection refs[] = item.getCollections();
for (Collection element : refs)
{
if (destColl.equals(element))
{
log.debug("COPY - item @ " + item.getHandle()
+ " is already a member of collection @ "
+ destColl.getHandle());
if (overwrite)
{
return DAV.SC_NO_CONTENT;
}
else
{
throw new DAVStatusException(DAV.SC_CONFLICT,
"This Item is already a member of collection handle="
+ destColl.getHandle());
}
}
}
destColl.addItem(item);
return DAV.SC_NO_CONTENT;
}
/* (non-Javadoc)
* @see org.dspace.app.dav.DAVResource#deleteInternal()
*/
@Override
protected int deleteInternal() throws DAVStatusException, SQLException,
AuthorizeException, IOException
{
this.item.withdraw();
return HttpServletResponse.SC_OK; // HTTP OK
}
/* (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 Item.");
}
}