001: /*
002: * Bundle.java
003: *
004: * Version: $Revision: 2074 $
005: *
006: * Date: $Date: 2007-07-19 14:40:11 -0500 (Thu, 19 Jul 2007) $
007: *
008: * Copyright (c) 2002-2005, 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: */
040: package org.dspace.content;
041:
042: import java.io.IOException;
043: import java.io.InputStream;
044: import java.sql.SQLException;
045: import java.util.ArrayList;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.ListIterator;
049:
050: import org.apache.log4j.Logger;
051: import org.dspace.authorize.AuthorizeException;
052: import org.dspace.authorize.AuthorizeManager;
053: import org.dspace.core.Constants;
054: import org.dspace.core.Context;
055: import org.dspace.core.LogManager;
056: import org.dspace.event.Event;
057: import org.dspace.storage.rdbms.DatabaseManager;
058: import org.dspace.storage.rdbms.TableRow;
059: import org.dspace.storage.rdbms.TableRowIterator;
060:
061: /**
062: * Class representing bundles of bitstreams stored in the DSpace system
063: * <P>
064: * The corresponding Bitstream objects are loaded into memory. At present, there
065: * is no metadata associated with bundles - they are simple containers. Thus,
066: * the <code>update</code> method doesn't do much yet. Creating, adding or
067: * removing bitstreams has instant effect in the database.
068: *
069: * @author Robert Tansley
070: * @version $Revision: 2074 $
071: */
072: public class Bundle extends DSpaceObject {
073: /** log4j logger */
074: private static Logger log = Logger.getLogger(Bundle.class);
075:
076: /** Our context */
077: private Context ourContext;
078:
079: /** The table row corresponding to this bundle */
080: private TableRow bundleRow;
081:
082: /** The bitstreams in this bundle */
083: private List<Bitstream> bitstreams;
084:
085: /** Flag set when data is modified, for events */
086: private boolean modified;
087:
088: /** Flag set when metadata is modified, for events */
089: private boolean modifiedMetadata;
090:
091: /**
092: * Construct a bundle object with the given table row
093: *
094: * @param context
095: * the context this object exists in
096: * @param row
097: * the corresponding row in the table
098: */
099: Bundle(Context context, TableRow row) throws SQLException {
100: ourContext = context;
101: bundleRow = row;
102: bitstreams = new ArrayList<Bitstream>();
103:
104: // Get bitstreams
105: TableRowIterator tri = DatabaseManager
106: .queryTable(
107: ourContext,
108: "bitstream",
109: "SELECT bitstream.* FROM bitstream, bundle2bitstream WHERE "
110: + "bundle2bitstream.bitstream_id=bitstream.bitstream_id AND "
111: + "bundle2bitstream.bundle_id= ? ",
112: bundleRow.getIntColumn("bundle_id"));
113:
114: while (tri.hasNext()) {
115: TableRow r = (TableRow) tri.next();
116:
117: // First check the cache
118: Bitstream fromCache = (Bitstream) context.fromCache(
119: Bitstream.class, r.getIntColumn("bitstream_id"));
120:
121: if (fromCache != null) {
122: bitstreams.add(fromCache);
123: } else {
124: bitstreams.add(new Bitstream(ourContext, r));
125: }
126: }
127: // close the TableRowIterator to free up resources
128: tri.close();
129:
130: // Cache ourselves
131: context.cache(this , row.getIntColumn("bundle_id"));
132:
133: modified = modifiedMetadata = false;
134: }
135:
136: /**
137: * Get a bundle from the database. The bundle and bitstream metadata are all
138: * loaded into memory.
139: *
140: * @param context
141: * DSpace context object
142: * @param id
143: * ID of the bundle
144: *
145: * @return the bundle, or null if the ID is invalid.
146: */
147: public static Bundle find(Context context, int id)
148: throws SQLException {
149: // First check the cache
150: Bundle fromCache = (Bundle) context.fromCache(Bundle.class, id);
151:
152: if (fromCache != null) {
153: return fromCache;
154: }
155:
156: TableRow row = DatabaseManager.find(context, "bundle", id);
157:
158: if (row == null) {
159: if (log.isDebugEnabled()) {
160: log.debug(LogManager.getHeader(context, "find_bundle",
161: "not_found,bundle_id=" + id));
162: }
163:
164: return null;
165: } else {
166: if (log.isDebugEnabled()) {
167: log.debug(LogManager.getHeader(context, "find_bundle",
168: "bundle_id=" + id));
169: }
170:
171: return new Bundle(context, row);
172: }
173: }
174:
175: /**
176: * Create a new bundle, with a new ID. This method is not public, since
177: * bundles need to be created within the context of an item. For this
178: * reason, authorisation is also not checked; that is the responsibility of
179: * the caller.
180: *
181: * @param context
182: * DSpace context object
183: *
184: * @return the newly created bundle
185: */
186: static Bundle create(Context context) throws SQLException {
187: // Create a table row
188: TableRow row = DatabaseManager.create(context, "bundle");
189:
190: log.info(LogManager.getHeader(context, "create_bundle",
191: "bundle_id=" + row.getIntColumn("bundle_id")));
192:
193: context.addEvent(new Event(Event.CREATE, Constants.BUNDLE, row
194: .getIntColumn("bundle_id"), null));
195:
196: return new Bundle(context, row);
197: }
198:
199: /**
200: * Get the internal identifier of this bundle
201: *
202: * @return the internal identifier
203: */
204: public int getID() {
205: return bundleRow.getIntColumn("bundle_id");
206: }
207:
208: /**
209: * Get the name of the bundle
210: *
211: * @return name of the bundle (ORIGINAL, TEXT, THUMBNAIL) or NULL if not set
212: */
213: public String getName() {
214: return bundleRow.getStringColumn("name");
215: }
216:
217: /**
218: * Set the name of the bundle
219: *
220: * @param name
221: * string name of the bundle (ORIGINAL, TEXT, THUMBNAIL) are the
222: * values currently used
223: */
224: public void setName(String name) {
225: bundleRow.setColumn("name", name);
226: modifiedMetadata = true;
227: }
228:
229: /**
230: * Get the primary bitstream ID of the bundle
231: *
232: * @return primary bitstream ID or -1 if not set
233: */
234: public int getPrimaryBitstreamID() {
235: return bundleRow.getIntColumn("primary_bitstream_id");
236: }
237:
238: /**
239: * Set the primary bitstream ID of the bundle
240: *
241: * @param bitstreamID
242: * int ID of primary bitstream (e.g. index html file)
243: */
244: public void setPrimaryBitstreamID(int bitstreamID) {
245: bundleRow.setColumn("primary_bitstream_id", bitstreamID);
246: modified = true;
247: }
248:
249: /**
250: * Unset the primary bitstream ID of the bundle
251: */
252: public void unsetPrimaryBitstreamID() {
253: bundleRow.setColumnNull("primary_bitstream_id");
254: }
255:
256: public String getHandle() {
257: // No Handles for bundles
258: return null;
259: }
260:
261: /**
262: * @param name
263: * name of the bitstream you're looking for
264: *
265: * @return the bitstream or null if not found
266: */
267: public Bitstream getBitstreamByName(String name) {
268: Bitstream target = null;
269:
270: Iterator i = bitstreams.iterator();
271:
272: while (i.hasNext()) {
273: Bitstream b = (Bitstream) i.next();
274:
275: if (name.equals(b.getName())) {
276: target = b;
277:
278: break;
279: }
280: }
281:
282: return target;
283: }
284:
285: /**
286: * Get the bitstreams in this bundle
287: *
288: * @return the bitstreams
289: */
290: public Bitstream[] getBitstreams() {
291: Bitstream[] bitstreamArray = new Bitstream[bitstreams.size()];
292: bitstreamArray = (Bitstream[]) bitstreams
293: .toArray(bitstreamArray);
294:
295: return bitstreamArray;
296: }
297:
298: /**
299: * Get the items this bundle appears in
300: *
301: * @return array of <code>Item</code> s this bundle appears in
302: */
303: public Item[] getItems() throws SQLException {
304: List<Item> items = new ArrayList<Item>();
305:
306: // Get items
307: TableRowIterator tri = DatabaseManager.queryTable(ourContext,
308: "item", "SELECT item.* FROM item, item2bundle WHERE "
309: + "item2bundle.item_id=item.item_id AND "
310: + "item2bundle.bundle_id= ? ", bundleRow
311: .getIntColumn("bundle_id"));
312:
313: while (tri.hasNext()) {
314: TableRow r = (TableRow) tri.next();
315:
316: // Used cached copy if there is one
317: Item fromCache = (Item) ourContext.fromCache(Item.class, r
318: .getIntColumn("item_id"));
319:
320: if (fromCache != null) {
321: items.add(fromCache);
322: } else {
323: items.add(new Item(ourContext, r));
324: }
325: }
326: // close the TableRowIterator to free up resources
327: tri.close();
328:
329: Item[] itemArray = new Item[items.size()];
330: itemArray = (Item[]) items.toArray(itemArray);
331:
332: return itemArray;
333: }
334:
335: /**
336: * Create a new bitstream in this bundle.
337: *
338: * @param is
339: * the stream to read the new bitstream from
340: *
341: * @return the newly created bitstream
342: */
343: public Bitstream createBitstream(InputStream is)
344: throws AuthorizeException, IOException, SQLException {
345: // Check authorisation
346: AuthorizeManager.authorizeAction(ourContext, this ,
347: Constants.ADD);
348:
349: Bitstream b = Bitstream.create(ourContext, is);
350:
351: // FIXME: Set permissions for bitstream
352: addBitstream(b);
353:
354: return b;
355: }
356:
357: /**
358: * Create a new bitstream in this bundle. This method is for registering
359: * bitstreams.
360: *
361: * @param assetstore corresponds to an assetstore in dspace.cfg
362: * @param bitstreamPath the path and filename relative to the assetstore
363: * @return the newly created bitstream
364: * @throws IOException
365: * @throws SQLException
366: */
367: public Bitstream registerBitstream(int assetstore,
368: String bitstreamPath) throws AuthorizeException,
369: IOException, SQLException {
370: // check authorisation
371: AuthorizeManager.authorizeAction(ourContext, this ,
372: Constants.ADD);
373:
374: Bitstream b = Bitstream.register(ourContext, assetstore,
375: bitstreamPath);
376:
377: // FIXME: Set permissions for bitstream
378:
379: addBitstream(b);
380: return b;
381: }
382:
383: /**
384: * Add an existing bitstream to this bundle
385: *
386: * @param b
387: * the bitstream to add
388: */
389: public void addBitstream(Bitstream b) throws SQLException,
390: AuthorizeException {
391: // Check authorisation
392: AuthorizeManager.authorizeAction(ourContext, this ,
393: Constants.ADD);
394:
395: log.info(LogManager.getHeader(ourContext, "add_bitstream",
396: "bundle_id=" + getID() + ",bitstream_id=" + b.getID()));
397:
398: // First check that the bitstream isn't already in the list
399: for (int i = 0; i < bitstreams.size(); i++) {
400: Bitstream existing = (Bitstream) bitstreams.get(i);
401:
402: if (b.getID() == existing.getID()) {
403: // Bitstream is already there; no change
404: return;
405: }
406: }
407:
408: // Add the bitstream object
409: bitstreams.add(b);
410:
411: ourContext.addEvent(new Event(Event.ADD, Constants.BUNDLE,
412: getID(), Constants.BITSTREAM, b.getID(), String
413: .valueOf(b.getSequenceID())));
414:
415: // copy authorization policies from bundle to bitstream
416: // FIXME: multiple inclusion is affected by this...
417: AuthorizeManager.inheritPolicies(ourContext, this , b);
418:
419: // Add the mapping row to the database
420: TableRow mappingRow = DatabaseManager.create(ourContext,
421: "bundle2bitstream");
422: mappingRow.setColumn("bundle_id", getID());
423: mappingRow.setColumn("bitstream_id", b.getID());
424: DatabaseManager.update(ourContext, mappingRow);
425: }
426:
427: /**
428: * Remove a bitstream from this bundle - the bitstream is only deleted if
429: * this was the last reference to it
430: * <p>
431: * If the bitstream in question is the primary bitstream recorded for the
432: * bundle the primary bitstream field is unset in order to free the
433: * bitstream from the foreign key constraint so that the
434: * <code>cleanup</code> process can run normally.
435: *
436: * @param b
437: * the bitstream to remove
438: */
439: public void removeBitstream(Bitstream b) throws AuthorizeException,
440: SQLException, IOException {
441: // Check authorisation
442: AuthorizeManager.authorizeAction(ourContext, this ,
443: Constants.REMOVE);
444:
445: log.info(LogManager.getHeader(ourContext, "remove_bitstream",
446: "bundle_id=" + getID() + ",bitstream_id=" + b.getID()));
447:
448: // Remove from internal list of bitstreams
449: ListIterator li = bitstreams.listIterator();
450:
451: while (li.hasNext()) {
452: Bitstream existing = (Bitstream) li.next();
453:
454: if (b.getID() == existing.getID()) {
455: // We've found the bitstream to remove
456: li.remove();
457:
458: // In the event that the bitstream to remove is actually
459: // the primary bitstream, be sure to unset the primary
460: // bitstream.
461: if (b.getID() == getPrimaryBitstreamID()) {
462: unsetPrimaryBitstreamID();
463: }
464: }
465: }
466:
467: ourContext.addEvent(new Event(Event.REMOVE, Constants.BUNDLE,
468: getID(), Constants.BITSTREAM, b.getID(), String
469: .valueOf(b.getSequenceID())));
470:
471: // Delete the mapping row
472: DatabaseManager.updateQuery(ourContext,
473: "DELETE FROM bundle2bitstream WHERE bundle_id= ? "
474: + "AND bitstream_id= ? ", getID(), b.getID());
475:
476: // If the bitstream is orphaned, it's removed
477: TableRowIterator tri = DatabaseManager
478: .query(
479: ourContext,
480: "SELECT * FROM bundle2bitstream WHERE bitstream_id= ? ",
481: b.getID());
482:
483: if (!tri.hasNext()) {
484: // The bitstream is an orphan, delete it
485: b.delete();
486: }
487: // close the TableRowIterator to free up resources
488: tri.close();
489: }
490:
491: /**
492: * Update the bundle metadata
493: */
494: public void update() throws SQLException, AuthorizeException {
495: // Check authorisation
496: //AuthorizeManager.authorizeAction(ourContext, this, Constants.WRITE);
497: log.info(LogManager.getHeader(ourContext, "update_bundle",
498: "bundle_id=" + getID()));
499:
500: if (modified) {
501: ourContext.addEvent(new Event(Event.MODIFY,
502: Constants.BUNDLE, getID(), null));
503: modified = false;
504: }
505: if (modifiedMetadata) {
506: ourContext.addEvent(new Event(Event.MODIFY_METADATA,
507: Constants.BUNDLE, getID(), null));
508: modifiedMetadata = false;
509: }
510:
511: DatabaseManager.update(ourContext, bundleRow);
512: }
513:
514: /**
515: * Delete the bundle. Bitstreams contained by the bundle are removed first;
516: * this may result in their deletion, if deleting this bundle leaves them as
517: * orphans.
518: */
519: void delete() throws SQLException, AuthorizeException, IOException {
520: log.info(LogManager.getHeader(ourContext, "delete_bundle",
521: "bundle_id=" + getID()));
522:
523: ourContext.addEvent(new Event(Event.DELETE, Constants.BUNDLE,
524: getID(), getName()));
525:
526: // Remove from cache
527: ourContext.removeCached(this , getID());
528:
529: // Remove bitstreams
530: Bitstream[] bs = getBitstreams();
531:
532: for (int i = 0; i < bs.length; i++) {
533: removeBitstream(bs[i]);
534: }
535:
536: // remove our authorization policies
537: AuthorizeManager.removeAllPolicies(ourContext, this );
538:
539: // Remove ourself
540: DatabaseManager.delete(ourContext, bundleRow);
541: }
542:
543: /**
544: * return type found in Constants
545: */
546: public int getType() {
547: return Constants.BUNDLE;
548: }
549: }
|