001: /*
002: * FlowItemUtils.java
003: *
004: * Version: $Revision: 1.3 $
005: *
006: * Date: $Date: 2006/07/13 23:20:54 $
007: *
008: * Copyright (c) 2002, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */package org.dspace.app.xmlui.aspect.administrative;
040:
041: import java.io.IOException;
042: import java.io.InputStream;
043: import java.sql.SQLException;
044: import java.util.ArrayList;
045: import java.util.Enumeration;
046:
047: import org.apache.cocoon.environment.Request;
048: import org.apache.cocoon.servlet.multipart.Part;
049: import org.dspace.app.xmlui.utils.UIException;
050: import org.dspace.app.xmlui.wing.Message;
051: import org.dspace.authorize.AuthorizeException;
052: import org.dspace.content.Bitstream;
053: import org.dspace.content.BitstreamFormat;
054: import org.dspace.content.Bundle;
055: import org.dspace.content.Collection;
056: import org.dspace.content.DSpaceObject;
057: import org.dspace.content.FormatIdentifier;
058: import org.dspace.content.Item;
059: import org.dspace.content.MetadataField;
060: import org.dspace.content.MetadataSchema;
061: import org.dspace.core.Constants;
062: import org.dspace.core.Context;
063: import org.dspace.handle.HandleManager;
064:
065: /**
066: * Utility methods to processes actions on Groups. These methods are used
067: * exclusivly from the administrative flow scripts.
068: *
069: * @author Jay Paz
070: * @author Scott Phillips
071: */
072: public class FlowItemUtils {
073:
074: /** Language Strings */
075: private static final Message T_metadata_updated = new Message(
076: "default", "The Item's metadata was successfully updated.");
077: private static final Message T_metadata_added = new Message(
078: "default", "New metadata was added.");
079: private static final Message T_item_withdrawn = new Message(
080: "default", "The item has been withdrawn.");
081: private static final Message T_item_reinstated = new Message(
082: "default", "The item has been reinstated.");
083: private static final Message T_bitstream_added = new Message(
084: "default", "The new bitstream was successfully uploaded.");
085: private static final Message T_bitstream_failed = new Message(
086: "default", "Error while uploading file.");
087: private static final Message T_bitstream_updated = new Message(
088: "default", "The bitstream has been updated.");
089: private static final Message T_bitstream_delete = new Message(
090: "default", "The selected bitstreams have been deleted.");
091:
092: /**
093: * Resolve the given identifier to an item. The identifier may be either an
094: * internal ID or a handle. If an item is found then the result the internal
095: * ID of the item will be placed in the result "itemID" parameter.
096: *
097: * If the identifier was unable to be resolved to an item then the "identifier"
098: * field is placed in error.
099: *
100: * @param context The current DSpace context.
101: * @param identifier An Internal ID or a handle
102: * @return A flow result
103: */
104: public static FlowResult resolveItemIdentifier(Context context,
105: String identifier) throws SQLException {
106: FlowResult result = new FlowResult();
107: result.setContinue(false);
108:
109: // Check whether it's a handle or internal id (by check ing if it has a slash inthe string)
110: if (identifier.contains("/")) {
111: DSpaceObject dso = HandleManager.resolveToObject(context,
112: identifier);
113:
114: if (dso != null && dso.getType() == Constants.ITEM) {
115: result.setParameter("itemID", dso.getID());
116: result.setParameter("type", Constants.ITEM);
117: result.setContinue(true);
118: return result;
119: }
120: } else {
121:
122: Item item = null;
123: try {
124: item = Item.find(context, Integer.valueOf(identifier));
125: } catch (NumberFormatException e) {
126: // ignoring the exception
127: }
128:
129: if (item != null) {
130: result.setParameter("itemID", item.getID());
131: result.setParameter("type", Constants.ITEM);
132: result.setContinue(true);
133: return result;
134: }
135: }
136:
137: result.addError("identifier");
138: return result;
139: }
140:
141: /**
142: * Process the request parameters to update the item's metadata and remove any selected bitstreams.
143: *
144: * Each metadata entry will have three fields "name_X", "value_X", and "language_X" where X is an
145: * integer that relates all three of the fields together. The name parameter stores the metadata name
146: * that is used by the entry (i.e schema_element_qualifier). The value and language paramaters are user
147: * inputed fields. If the optional parameter "remove_X" is given then the metadata value is removed.
148: *
149: * To support AJAX operations on this page an aditional parameter is considered, the "scope". The scope
150: * is the set of metadata entries that are being updated during this request. It the metadata name,
151: * schema_element_qualifier, only fields that have this name are considered! If all fields are to be
152: * considered then scope should be set to "*".
153: *
154: * When creating an AJAX query include all the name_X, value_X, language_X, and remove_X for the fields
155: * in the set, and then set the scope parameter to be the metadata field.
156: *
157: * @param context The current DSpace context
158: * @param itemID internal item id
159: * @param request the Cocoon request
160: * @return A flow result
161: */
162: public static FlowResult processEditItem(Context context,
163: int itemID, Request request) throws SQLException,
164: AuthorizeException, UIException, IOException {
165: FlowResult result = new FlowResult();
166: result.setContinue(false);
167:
168: Item item = Item.find(context, itemID);
169:
170: // STEP 1:
171: // Clear all metadata within the scope
172: // Only metadata values within this scope will be considered. This
173: // is so ajax request can operate on only a subset of the values.
174: String scope = request.getParameter("scope");
175: if ("*".equals(scope)) {
176: item.clearMetadata(Item.ANY, Item.ANY, Item.ANY, Item.ANY);
177: } else {
178: String[] parts = parseName(scope);
179: item.clearMetadata(parts[0], parts[1], parts[2], Item.ANY);
180: }
181:
182: // STEP 2:
183: // First determine all the metadata fields that are within
184: // the scope parameter
185: ArrayList<Integer> indexes = new ArrayList<Integer>();
186: Enumeration parameters = request.getParameterNames();
187: while (parameters.hasMoreElements()) {
188:
189: // Only consider the name_ fields
190: String parameterName = (String) parameters.nextElement();
191: if (parameterName.startsWith("name_")) {
192: // Check if the name is within the scope
193: String parameterValue = request
194: .getParameter(parameterName);
195: if ("*".equals(scope) || scope.equals(parameterValue)) {
196: // Extract the index from the name.
197: String indexString = parameterName
198: .substring("name_".length());
199: Integer index = Integer.valueOf(indexString);
200: indexes.add(index);
201: }
202: }
203: }
204:
205: // STEP 3:
206: // Iterate over all the indexes within the scope and add them back in.
207: for (Integer index : indexes) {
208: String name = request.getParameter("name_" + index);
209: String value = request.getParameter("value_" + index);
210: String lang = request.getParameter("language_" + index);
211: String remove = request.getParameter("remove_" + index);
212:
213: // the user selected the remove checkbox.
214: if (remove != null)
215: continue;
216:
217: // get the field's name broken up
218: String[] parts = parseName(name);
219:
220: // Add the metadata back in.
221: item.addMetadata(parts[0], parts[1], parts[2], lang, value);
222: }
223:
224: item.update();
225: context.commit();
226:
227: result.setContinue(true);
228:
229: result.setOutcome(true);
230: result.setMessage(T_metadata_updated);
231:
232: return result;
233: }
234:
235: /**
236: * Process the request paramaters to add a new metadata entry for the item.
237: *
238: * @param context The current DSpace context
239: * @param itemID internal item id
240: * @param request the Cocoon request
241: * @return A flow result
242: */
243: public static FlowResult processAddMetadata(Context context,
244: int itemID, Request request) throws SQLException,
245: AuthorizeException, UIException, IOException {
246: FlowResult result = new FlowResult();
247: result.setContinue(false);
248:
249: Item item = Item.find(context, itemID);
250:
251: String fieldID = request.getParameter("field");
252: String value = request.getParameter("value");
253: String language = request.getParameter("language");
254:
255: MetadataField field = MetadataField.find(context, Integer
256: .valueOf(fieldID));
257: MetadataSchema schema = MetadataSchema.find(context, field
258: .getSchemaID());
259:
260: item.addMetadata(schema.getName(), field.getElement(), field
261: .getQualifier(), language, value);
262:
263: item.update();
264: context.commit();
265:
266: result.setContinue(true);
267:
268: result.setOutcome(true);
269: result.setMessage(T_metadata_added);
270:
271: return result;
272: }
273:
274: /**
275: * Withdraw the specified item, this method assumes that the action has been confirmed.
276: *
277: * @param context The DSpace context
278: * @param itemID The id of the to-be-withdrawn item.
279: * @return A result object
280: */
281: public static FlowResult processWithdrawItem(Context context,
282: int itemID) throws SQLException, AuthorizeException,
283: IOException {
284: FlowResult result = new FlowResult();
285: result.setContinue(false);
286:
287: Item item = Item.find(context, itemID);
288: item.withdraw();
289: context.commit();
290:
291: result.setContinue(true);
292: result.setOutcome(true);
293: result.setMessage(T_item_withdrawn);
294:
295: return result;
296: }
297:
298: /**
299: * Reinstate the specified item, this method assumes that the action has been confirmed.
300: *
301: * @param context The DSpace context
302: * @param itemID The id of the to-be-reinstated item.
303: * @return A result object
304: */
305: public static FlowResult processReinstateItem(Context context,
306: int itemID) throws SQLException, AuthorizeException,
307: IOException {
308: FlowResult result = new FlowResult();
309: result.setContinue(false);
310:
311: Item item = Item.find(context, itemID);
312: item.reinstate();
313: context.commit();
314:
315: result.setContinue(true);
316: result.setOutcome(true);
317: result.setMessage(T_item_reinstated);
318:
319: return result;
320: }
321:
322: /**
323: * Permanently delete the specified item, this method assumes that
324: * the action has been confirmed.
325: *
326: * @param context The DSpace context
327: * @param itemID The id of the to-be-deleted item.
328: * @return A result object
329: */
330: public static FlowResult processDeleteItem(Context context,
331: int itemID) throws SQLException, AuthorizeException,
332: IOException {
333: FlowResult result = new FlowResult();
334: result.setContinue(false);
335:
336: Item item = Item.find(context, itemID);
337:
338: Collection[] collections = item.getCollections();
339:
340: // Remove item from all the collections it's in
341: for (Collection collection : collections) {
342: collection.removeItem(item);
343: }
344:
345: // Note: when removing an item from the last collection it will
346: // be removed from the system. So there is no need to also call
347: // an item.delete() method.
348:
349: context.commit();
350:
351: result.setContinue(true);
352:
353: return result;
354: }
355:
356: /**
357: * Add a new bitstream to the item. The bundle, bitstream (aka file), and description
358: * will be used to create a new bitstream. If the format needs to be adjusted then they
359: * will need to access the edit bitstream form after it has been uploaded.
360: *
361: * @param context The DSpace content
362: * @param itemID The item to add a new bitstream too
363: * @param request The request.
364: * @return A flow result
365: */
366: public static FlowResult processAddBitstream(Context context,
367: int itemID, Request request) throws SQLException,
368: AuthorizeException, IOException {
369: FlowResult result = new FlowResult();
370: result.setContinue(false);
371:
372: // Upload a new file
373: Item item = Item.find(context, itemID);
374:
375: Object object = request.get("file");
376: Part filePart = null;
377: if (object instanceof Part)
378: filePart = (Part) object;
379:
380: if (filePart != null && filePart.getSize() > 0) {
381: InputStream is = filePart.getInputStream();
382:
383: String bundleName = request.getParameter("bundle");
384:
385: Bitstream bitstream;
386: Bundle[] bundles = item.getBundles(bundleName);
387: if (bundles.length < 1) {
388: // set bundle's name to ORIGINAL
389: bitstream = item.createSingleBitstream(is, bundleName);
390: } else {
391: // we have a bundle already, just add bitstream
392: bitstream = bundles[0].createBitstream(is);
393: }
394:
395: // Strip all but the last filename. It would be nice
396: // to know which OS the file came from.
397: String name = filePart.getUploadName();
398:
399: while (name.indexOf('/') > -1) {
400: name = name.substring(name.indexOf('/') + 1);
401: }
402:
403: while (name.indexOf('\\') > -1) {
404: name = name.substring(name.indexOf('\\') + 1);
405: }
406:
407: bitstream.setName(name);
408: bitstream.setSource(filePart.getUploadName());
409: bitstream.setDescription(request
410: .getParameter("description"));
411:
412: // Identify the format
413: BitstreamFormat format = FormatIdentifier.guessFormat(
414: context, bitstream);
415: bitstream.setFormat(format);
416:
417: // Update to DB
418: bitstream.update();
419: item.update();
420:
421: result.setContinue(true);
422: result.setOutcome(true);
423: result.setMessage(T_bitstream_added);
424: } else {
425: result.setContinue(false);
426: result.setOutcome(false);
427: result.setMessage(T_bitstream_failed);
428: }
429: return result;
430: }
431:
432: /**
433: * Update a bitstream's metadata.
434: *
435: * @param context The DSpace content
436: * @param itemID The item to which the bitstream belongs
437: * @param bitstreamID The bitstream being updated.
438: * @param description The new description of the bitstream
439: * @param formatID The new format ID of the bitstream
440: * @param userFormat Any user supplied formats.
441: * @return A flow result object.
442: */
443: public static FlowResult processEditBitstream(Context context,
444: int itemID, int bitstreamID, String primary,
445: String description, int formatID, String userFormat)
446: throws SQLException, AuthorizeException {
447: FlowResult result = new FlowResult();
448: result.setContinue(false);
449:
450: Bitstream bitstream = Bitstream.find(context, bitstreamID);
451: BitstreamFormat currentFormat = bitstream.getFormat();
452:
453: //Step 1:
454: // Update the bitstream's description
455: if (description != null && description.length() > 0) {
456: bitstream.setDescription(description);
457: }
458:
459: //Step 2:
460: // Check if the primary bitstream status has changed
461: Bundle[] bundles = bitstream.getBundles();
462: if (bundles != null && bundles.length > 0) {
463: if (bitstreamID == bundles[0].getPrimaryBitstreamID()) {
464: // currently the bitstream is primary
465: if ("no".equals(primary)) {
466: // However the user has removed this bitstream as a primary bitstream.
467: bundles[0].unsetPrimaryBitstreamID();
468: bundles[0].update();
469: }
470: } else {
471: // currently the bitstream is non-primary
472: if ("yes".equals(primary)) {
473: // However the user has set this bitstream as primary.
474: bundles[0].setPrimaryBitstreamID(bitstreamID);
475: bundles[0].update();
476: }
477: }
478: }
479:
480: //Step 2:
481: // Update the bitstream's format
482: if (formatID > 0) {
483: if (currentFormat == null
484: || currentFormat.getID() != formatID) {
485: BitstreamFormat newFormat = BitstreamFormat.find(
486: context, formatID);
487: if (newFormat != null) {
488: bitstream.setFormat(newFormat);
489: }
490: }
491: } else {
492: if (userFormat != null && userFormat.length() > 0) {
493: bitstream.setUserFormatDescription(userFormat);
494: }
495: }
496:
497: //Step 3:
498: // Save our changes
499: bitstream.update();
500: context.commit();
501:
502: result.setContinue(true);
503: result.setOutcome(true);
504: result.setMessage(T_bitstream_updated);
505:
506: return result;
507: }
508:
509: /**
510: * Delete the given bitstreams from the bundle and item. If there are no more bitstreams
511: * left in a bundle then also remove it.
512: *
513: * @param context Current dspace content
514: * @param itemID The item id from which to remove bitstreams
515: * @param bitstreamIDs A bundle slash bitstream id pair of bitstreams to be removed.
516: * @return A flow result
517: */
518: public static FlowResult processDeleteBitstreams(Context context,
519: int itemID, String[] bitstreamIDs) throws SQLException,
520: AuthorizeException, IOException, UIException {
521: FlowResult result = new FlowResult();
522: result.setContinue(false);
523:
524: Item item = Item.find(context, itemID);
525:
526: for (String id : bitstreamIDs) {
527: String[] parts = id.split("/");
528:
529: if (parts.length != 2)
530: throw new UIException(
531: "Unable to parse id into bundle and bitstream id: "
532: + id);
533:
534: int bundleID = Integer.valueOf(parts[0]);
535: int bitstreamID = Integer.valueOf(parts[1]);
536:
537: Bundle bundle = Bundle.find(context, bundleID);
538: Bitstream bitstream = Bitstream.find(context, bitstreamID);
539:
540: bundle.removeBitstream(bitstream);
541:
542: if (bundle.getBitstreams().length == 0) {
543: item.removeBundle(bundle);
544: }
545: }
546:
547: item.update();
548:
549: result.setContinue(true);
550: result.setOutcome(true);
551: result.setMessage(T_bitstream_delete);
552:
553: return result;
554: }
555:
556: /**
557: * Parse the given name into three parts, divided by an _. Each part should represent the
558: * schema, element, and qualifier. You are guaranteed that if no qualifier was supplied the
559: * third entry is null.
560: *
561: * @param name The name to be parsed.
562: * @return An array of name parts.
563: */
564: private static String[] parseName(String name) throws UIException {
565: String[] parts = new String[3];
566:
567: String[] split = name.split("_");
568: if (split.length == 2) {
569: parts[0] = split[0];
570: parts[1] = split[1];
571: parts[2] = null;
572: } else if (split.length == 3) {
573: parts[0] = split[0];
574: parts[1] = split[1];
575: parts[2] = split[2];
576: } else {
577: throw new UIException(
578: "Unable to parse metedata field name: " + name);
579: }
580: return parts;
581: }
582: }
|