/*
* EditItemServlet.java
*
* Version: $Revision: 4365 $
*
* Date: $Date: 2009-10-05 23:52:42 +0000 (Mon, 05 Oct 2009) $
*
* Copyright (c) 2002-2005, 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.webui.servlet.admin;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.dspace.app.util.AuthorizeUtil;
import org.dspace.app.webui.servlet.DSpaceServlet;
import org.dspace.app.webui.util.FileUploadRequest;
import org.dspace.app.webui.util.JSPManager;
import org.dspace.app.webui.util.UIUtil;
import org.dspace.authorize.AuthorizeConfiguration;
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.Collection;
import org.dspace.content.DSpaceObject;
import org.dspace.content.FormatIdentifier;
import org.dspace.content.Item;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchema;
import org.dspace.content.authority.Choices;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.handle.HandleManager;
import org.dspace.license.CreativeCommons;
/**
* Servlet for editing and deleting (expunging) items
*
* @author Robert Tansley
* @version $Revision: 4365 $
*/
public class EditItemServlet extends DSpaceServlet
{
/** User wants to delete (expunge) an item */
public static final int START_DELETE = 1;
/** User confirms delete (expunge) of item */
public static final int CONFIRM_DELETE = 2;
/** User updates item */
public static final int UPDATE_ITEM = 3;
/** User starts withdrawal of item */
public static final int START_WITHDRAW = 4;
/** User confirms withdrawal of item */
public static final int CONFIRM_WITHDRAW = 5;
/** User reinstates a withdrawn item */
public static final int REINSTATE = 6;
/** User starts the movement of an item */
public static final int START_MOVE_ITEM = 7;
/** User confirms the movement of the item */
public static final int CONFIRM_MOVE_ITEM = 8;
/** Logger */
private static Logger log = Logger.getLogger(EditCommunitiesServlet.class);
protected void doDSGet(Context context, HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException,
SQLException, AuthorizeException
{
/*
* GET with no parameters displays "find by handle/id" form parameter
* item_id -> find and edit item with internal ID item_id parameter
* handle -> find and edit corresponding item if internal ID or Handle
* are invalid, "find by handle/id" form is displayed again with error
* message
*/
int internalID = UIUtil.getIntParameter(request, "item_id");
String handle = request.getParameter("handle");
boolean showError = false;
// See if an item ID or Handle was passed in
Item itemToEdit = null;
if (internalID > 0)
{
itemToEdit = Item.find(context, internalID);
showError = (itemToEdit == null);
}
else if ((handle != null) && !handle.equals(""))
{
// resolve handle
DSpaceObject dso = HandleManager.resolveToObject(context, handle.trim());
// make sure it's an ITEM
if ((dso != null) && (dso.getType() == Constants.ITEM))
{
itemToEdit = (Item) dso;
showError = false;
}
else
{
showError = true;
}
}
// Show edit form if appropriate
if (itemToEdit != null)
{
// now check to see if person can edit item
checkEditAuthorization(context, itemToEdit);
showEditForm(context, request, response, itemToEdit);
}
else
{
if (showError)
{
request.setAttribute("invalid.id", new Boolean(true));
}
JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
}
}
protected void doDSPost(Context context, HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException,
SQLException, AuthorizeException
{
// First, see if we have a multipart request (uploading a new bitstream)
String contentType = request.getContentType();
if ((contentType != null)
&& (contentType.indexOf("multipart/form-data") != -1))
{
// This is a multipart request, so it's a file upload
processUploadBitstream(context, request, response);
return;
}
/*
* Then we check for a "cancel" button - if it's been pressed, we simply
* return to the "find by handle/id" page
*/
if (request.getParameter("submit_cancel") != null)
{
JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
return;
}
/*
* Respond to submitted forms. Each form includes an "action" parameter
* indicating what needs to be done (from the constants above.)
*/
int action = UIUtil.getIntParameter(request, "action");
Item item = Item.find(context, UIUtil.getIntParameter(request,
"item_id"));
String handle = HandleManager.findHandle(context, item);
// now check to see if person can edit item
checkEditAuthorization(context, item);
request.setAttribute("item", item);
request.setAttribute("handle", handle);
switch (action)
{
case START_DELETE:
// Show "delete item" confirmation page
JSPManager.showJSP(request, response,
"/tools/confirm-delete-item.jsp");
break;
case CONFIRM_DELETE:
// Delete the item - if "cancel" was pressed this would be
// picked up above
// FIXME: Don't know if this does all it should - remove Handle?
Collection[] collections = item.getCollections();
// Remove item from all the collections it's in
for (int i = 0; i < collections.length; i++)
{
collections[i].removeItem(item);
}
JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
context.complete();
break;
case UPDATE_ITEM:
processUpdateItem(context, request, response, item);
break;
case START_WITHDRAW:
// Show "withdraw item" confirmation page
JSPManager.showJSP(request, response,
"/tools/confirm-withdraw-item.jsp");
break;
case CONFIRM_WITHDRAW:
// Withdraw the item
item.withdraw();
JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
context.complete();
break;
case REINSTATE:
item.reinstate();
JSPManager.showJSP(request, response, "/tools/get-item-id.jsp");
context.complete();
break;
case START_MOVE_ITEM:
if (AuthorizeManager.isAdmin(context,item))
{
// Display move collection page with fields of collections and communities
Collection[] allNotLinkedCollections = item.getCollectionsNotLinked();
Collection[] allLinkedCollections = item.getCollections();
// get only the collection where the current user has the right permission
List<Collection> authNotLinkedCollections = new ArrayList<Collection>();
for (Collection c : allNotLinkedCollections)
{
if (AuthorizeManager.authorizeActionBoolean(context, c, Constants.ADD))
{
authNotLinkedCollections.add(c);
}
}
List<Collection> authLinkedCollections = new ArrayList<Collection>();
for (Collection c : allLinkedCollections)
{
if (AuthorizeManager.authorizeActionBoolean(context, c, Constants.REMOVE))
{
authLinkedCollections.add(c);
}
}
Collection[] notLinkedCollections = new Collection[authNotLinkedCollections.size()];
notLinkedCollections = authNotLinkedCollections.toArray(notLinkedCollections);
Collection[] linkedCollections = new Collection[authLinkedCollections.size()];
linkedCollections = authLinkedCollections.toArray(linkedCollections);
request.setAttribute("linkedCollections", linkedCollections);
request.setAttribute("notLinkedCollections", notLinkedCollections);
JSPManager.showJSP(request, response, "/tools/move-item.jsp");
} else
{
throw new ServletException("You must be an administrator to move an item");
}
break;
case CONFIRM_MOVE_ITEM:
if (AuthorizeManager.isAdmin(context,item))
{
Collection fromCollection = Collection.find(context, UIUtil.getIntParameter(request, "collection_from_id"));
Collection toCollection = Collection.find(context, UIUtil.getIntParameter(request, "collection_to_id"));
if (fromCollection == null || toCollection == null)
{
throw new ServletException("Missing or incorrect collection IDs for moving item");
}
item.move(fromCollection, toCollection);
showEditForm(context, request, response, item);
context.complete();
} else
{
throw new ServletException("You must be an administrator to move an item");
}
break;
default:
// Erm... weird action value received.
log.warn(LogManager.getHeader(context, "integrity_error", UIUtil
.getRequestLogInfo(request)));
JSPManager.showIntegrityError(request, response);
}
}
/**
* Throw an exception if user isn't authorized to edit this item
*
* @param context
* @param item
*/
private void checkEditAuthorization(Context c, Item item)
throws AuthorizeException, java.sql.SQLException
{
if (!item.canEdit())
{
int userID = 0;
// first, check if userid is set
if (c.getCurrentUser() != null)
{
userID = c.getCurrentUser().getID();
}
// show an error or throw an authorization exception
throw new AuthorizeException("EditItemServlet: User " + userID
+ " not authorized to edit item " + item.getID());
}
}
/**
* Show the item edit form for a particular item
*
* @param context
* DSpace context
* @param request
* the HTTP request containing posted info
* @param response
* the HTTP response
* @param item
* the item
*/
private void showEditForm(Context context, HttpServletRequest request,
HttpServletResponse response, Item item) throws ServletException,
IOException, SQLException, AuthorizeException
{
if ( request.getParameter("cc_license_url") != null )
{
// check authorization
AuthorizeUtil.authorizeManageCCLicense(context, item);
// turn off auth system to allow replace also to user that can't
// remove/add bitstream to the item
context.turnOffAuthorisationSystem();
// set or replace existing CC license
CreativeCommons.setLicense( context, item,
request.getParameter("cc_license_url") );
context.restoreAuthSystemState();
context.commit();
}
// Get the handle, if any
String handle = HandleManager.findHandle(context, item);
// Collections
Collection[] collections = item.getCollections();
// All DC types in the registry
MetadataField[] types = MetadataField.findAll(context);
// Get a HashMap of metadata field ids and a field name to display
HashMap metadataFields = new HashMap();
// Get all existing Schemas
MetadataSchema[] schemas = MetadataSchema.findAll(context);
for (int i = 0; i < schemas.length; i++)
{
String schemaName = schemas[i].getName();
// Get all fields for the given schema
MetadataField[] fields = MetadataField.findAllInSchema(context, schemas[i].getSchemaID());
for (int j = 0; j < fields.length; j++)
{
Integer fieldID = new Integer(fields[j].getFieldID());
String displayName = "";
displayName = schemaName + "." + fields[j].getElement() + (fields[j].getQualifier() == null ? "" : "." + fields[j].getQualifier());
metadataFields.put(fieldID, displayName);
}
}
request.setAttribute("admin_button", AuthorizeManager.authorizeActionBoolean(context, item, Constants.ADMIN));
try
{
AuthorizeUtil.authorizeManageItemPolicy(context, item);
request.setAttribute("policy_button", new Boolean(true));
}
catch (AuthorizeException authex)
{
request.setAttribute("policy_button", new Boolean(false));
}
if (AuthorizeManager.authorizeActionBoolean(context, item
.getParentObject(), Constants.REMOVE))
{
request.setAttribute("delete_button", new Boolean(true));
}
else
{
request.setAttribute("delete_button", new Boolean(false));
}
try
{
AuthorizeManager.authorizeAction(context, item, Constants.ADD);
request.setAttribute("create_bitstream_button", new Boolean(true));
}
catch (AuthorizeException authex)
{
request.setAttribute("create_bitstream_button", new Boolean(false));
}
try
{
AuthorizeManager.authorizeAction(context, item, Constants.REMOVE);
request.setAttribute("remove_bitstream_button", new Boolean(true));
}
catch (AuthorizeException authex)
{
request.setAttribute("remove_bitstream_button", new Boolean(false));
}
try
{
AuthorizeUtil.authorizeManageCCLicense(context, item);
request.setAttribute("cclicense_button", new Boolean(true));
}
catch (AuthorizeException authex)
{
request.setAttribute("cclicense_button", new Boolean(false));
}
if (!item.isWithdrawn())
{
try
{
AuthorizeUtil.authorizeWithdrawItem(context, item);
request.setAttribute("withdraw_button", new Boolean(true));
}
catch (AuthorizeException authex)
{
request.setAttribute("withdraw_button", new Boolean(false));
}
}
else
{
try
{
AuthorizeUtil.authorizeReinstateItem(context, item);
request.setAttribute("reinstate_button", new Boolean(true));
}
catch (AuthorizeException authex)
{
request.setAttribute("reinstate_button", new Boolean(false));
}
}
request.setAttribute("item", item);
request.setAttribute("handle", handle);
request.setAttribute("collections", collections);
request.setAttribute("dc.types", types);
request.setAttribute("metadataFields", metadataFields);
JSPManager.showJSP(request, response, "/tools/edit-item-form.jsp");
}
/**
* Process input from the edit item form
*
* @param context
* DSpace context
* @param request
* the HTTP request containing posted info
* @param response
* the HTTP response
* @param item
* the item
*/
private void processUpdateItem(Context context, HttpServletRequest request,
HttpServletResponse response, Item item) throws ServletException,
IOException, SQLException, AuthorizeException
{
String button = UIUtil.getSubmitButton(request, "submit");
/*
* "Cancel" handled above, so whatever happens, we need to update the
* item metadata. First, we remove it all, then build it back up again.
*/
item.clearMetadata(Item.ANY, Item.ANY, Item.ANY, Item.ANY);
// We'll sort the parameters by name. This ensures that DC fields
// of the same element/qualifier are added in the correct sequence.
// Get the parameters names
Enumeration unsortedParamNames = request.getParameterNames();
// Put them in a list
List sortedParamNames = new LinkedList();
while (unsortedParamNames.hasMoreElements())
{
sortedParamNames.add(unsortedParamNames.nextElement());
}
// Sort the list
Collections.sort(sortedParamNames);
Iterator iterator = sortedParamNames.iterator();
while (iterator.hasNext())
{
String p = (String) iterator.next();
if (p.startsWith("value"))
{
/*
* It's a metadata value - it will be of the form
* value_element_1 OR value_element_qualifier_2 (the number
* being the sequence number) We use a StringTokenizer to
* extract these values
*/
StringTokenizer st = new StringTokenizer(p, "_");
st.nextToken(); // Skip "value"
String schema = st.nextToken();
String element = st.nextToken();
String qualifier = null;
if (st.countTokens() == 2)
{
qualifier = st.nextToken();
}
String sequenceNumber = st.nextToken();
// Get a string with "element" for unqualified or
// "element_qualifier"
String key = MetadataField.formKey(schema,element,qualifier);
// Get the language
String language = request.getParameter("language_" + key + "_"
+ sequenceNumber);
// Empty string language = null
if ((language != null) && language.equals(""))
{
language = null;
}
// Get the authority key if any
String authority = request.getParameter("choice_" + key + "_authority_"
+ sequenceNumber);
// Empty string authority = null
if ((authority != null) && authority.equals(""))
{
authority = null;
}
// Get the authority confidence value, passed as symbolic name
String sconfidence = request.getParameter("choice_" + key + "_confidence_" + sequenceNumber);
int confidence = (sconfidence == null || sconfidence.equals("")) ?
Choices.CF_NOVALUE : Choices.getConfidenceValue(sconfidence);
// Get the value
String value = request.getParameter(p).trim();
// If remove button pressed for this value, we don't add it
// back to the item. We also don't add empty values
// (if no authority is specified).
if (!((value.equals("") && authority == null) || button.equals("submit_remove_" + key
+ "_" + sequenceNumber)))
{
// Value is empty, or remove button for this wasn't pressed
item.addMetadata(schema, element, qualifier, language, value,
authority, confidence);
}
}
else if (p.startsWith("bitstream_name"))
{
// We have bitstream metadata
// First, get the bundle and bitstream ID
// Parameter name is bitstream_name_(bundleID)_(bitstreamID)
StringTokenizer st = new StringTokenizer(p, "_");
// Ignore "bitstream" and "name"
st.nextToken();
st.nextToken();
// Bundle ID and bitstream ID next
int bundleID = Integer.parseInt(st.nextToken());
int bitstreamID = Integer.parseInt(st.nextToken());
Bundle bundle = Bundle.find(context, bundleID);
Bitstream bitstream = Bitstream.find(context, bitstreamID);
// Get the string "(bundleID)_(bitstreamID)" for finding other
// parameters related to this bitstream
String key = String.valueOf(bundleID) + "_" + bitstreamID;
// Update bitstream metadata, or delete?
if (button.equals("submit_delete_bitstream_" + key))
{
// "delete" button pressed
bundle.removeBitstream(bitstream);
// Delete bundle too, if empty
if (bundle.getBitstreams().length == 0)
{
item.removeBundle(bundle);
}
}
else
{
// Update the bitstream metadata
String name = request.getParameter(p);
String source = request.getParameter("bitstream_source_"
+ key);
String desc = request.getParameter("bitstream_description_"
+ key);
int formatID = UIUtil.getIntParameter(request,
"bitstream_format_id_" + key);
String userFormatDesc = request
.getParameter("bitstream_user_format_description_"
+ key);
int primaryBitstreamID = UIUtil.getIntParameter(request,
bundleID + "_primary_bitstream_id");
// Empty strings become non-null
if (source.equals(""))
{
source = null;
}
if (desc.equals(""))
{
desc = null;
}
if (userFormatDesc.equals(""))
{
userFormatDesc = null;
}
bitstream.setName(name);
bitstream.setSource(source);
bitstream.setDescription(desc);
bitstream
.setFormat(BitstreamFormat.find(context, formatID));
if (primaryBitstreamID > 0)
{
bundle.setPrimaryBitstreamID(primaryBitstreamID);
}
if (userFormatDesc != null)
{
bitstream.setUserFormatDescription(userFormatDesc);
}
bitstream.update();
bundle.update();
}
}
}
/*
* Now respond to button presses, other than "Remove" or "Delete" button
* presses which were dealt with in the above loop.
*/
if (button.equals("submit_addfield"))
{
// Adding a metadata field
int dcTypeID = UIUtil.getIntParameter(request, "addfield_dctype");
String value = request.getParameter("addfield_value").trim();
String lang = request.getParameter("addfield_language");
// Empty language = null
if (lang.equals(""))
{
lang = null;
}
MetadataField field = MetadataField.find(context, dcTypeID);
MetadataSchema schema = MetadataSchema.find(context, field
.getSchemaID());
item.addMetadata(schema.getName(), field.getElement(), field
.getQualifier(), lang, value);
}
item.update();
if (button.equals("submit_addcc"))
{
// Show cc-edit page
request.setAttribute("item", item);
JSPManager
.showJSP(request, response, "/tools/creative-commons-edit.jsp");
}
if (button.equals("submit_addbitstream"))
{
// Show upload bitstream page
request.setAttribute("item", item);
JSPManager
.showJSP(request, response, "/tools/upload-bitstream.jsp");
}
else
{
// Show edit page again
showEditForm(context, request, response, item);
}
// Complete transaction
context.complete();
}
/**
* Process the input from the upload bitstream page
*
* @param context
* current DSpace context
* @param request
* current servlet request object
* @param response
* current servlet response object
*/
private void processUploadBitstream(Context context,
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, SQLException,
AuthorizeException
{
// Wrap multipart request to get the submission info
FileUploadRequest wrapper = new FileUploadRequest(request);
Bitstream b = null;
Item item = Item.find(context, UIUtil.getIntParameter(wrapper,
"item_id"));
File temp = wrapper.getFile("file");
// Read the temp file as logo
InputStream is = new BufferedInputStream(new FileInputStream(temp));
// now check to see if person can edit item
checkEditAuthorization(context, item);
// do we already have an ORIGINAL bundle?
Bundle[] bundles = item.getBundles("ORIGINAL");
if (bundles.length < 1)
{
// set bundle's name to ORIGINAL
b = item.createSingleBitstream(is, "ORIGINAL");
// set the permission as defined in the owning collection
Collection owningCollection = item.getOwningCollection();
if (owningCollection != null)
{
Bundle bnd = b.getBundles()[0];
bnd.inheritCollectionDefaultPolicies(owningCollection);
}
}
else
{
// we have a bundle already, just add bitstream
b = bundles[0].createBitstream(is);
}
// Strip all but the last filename. It would be nice
// to know which OS the file came from.
String noPath = wrapper.getFilesystemName("file");
while (noPath.indexOf('/') > -1)
{
noPath = noPath.substring(noPath.indexOf('/') + 1);
}
while (noPath.indexOf('\\') > -1)
{
noPath = noPath.substring(noPath.indexOf('\\') + 1);
}
b.setName(noPath);
b.setSource(wrapper.getFilesystemName("file"));
// Identify the format
BitstreamFormat bf = FormatIdentifier.guessFormat(context, b);
b.setFormat(bf);
b.update();
item.update();
// Back to edit form
showEditForm(context, request, response, item);
// Remove temp file
temp.delete();
// Update DB
context.complete();
}
}