/* * Bitstream.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 18:02:24 +0100 (Sat, 11 Apr 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.content; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeManager; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.event.Event; import org.dspace.storage.bitstore.BitstreamStorageManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; /** * Class representing bitstreams stored in the DSpace system. * <P> * When modifying the bitstream metadata, changes are not reflected in the * database until <code>update</code> is called. Note that you cannot alter * the contents of a bitstream; you need to create a new bitstream. * * @author Robert Tansley * @version $Revision: 3705 $ */ public class Bitstream extends DSpaceObject { /** log4j logger */ private static Logger log = Logger.getLogger(Bitstream.class); /** Our context */ private Context bContext; /** The row in the table representing this bitstream */ private TableRow bRow; /** The bitstream format corresponding to this bitstream */ private BitstreamFormat bitstreamFormat; /** Flag set when data is modified, for events */ private boolean modified; /** Flag set when metadata is modified, for events */ private boolean modifiedMetadata; /** * Private constructor for creating a Bitstream object based on the contents * of a DB table row. * * @param context * the context this object exists in * @param row * the corresponding row in the table * @throws SQLException */ Bitstream(Context context, TableRow row) throws SQLException { bContext = context; bRow = row; // Get the bitstream format bitstreamFormat = BitstreamFormat.find(context, row .getIntColumn("bitstream_format_id")); if (bitstreamFormat == null) { // No format: use "Unknown" bitstreamFormat = BitstreamFormat.findUnknown(context); // Panic if we can't find it if (bitstreamFormat == null) { throw new IllegalStateException("No Unknown bitsream format"); } } // Cache ourselves context.cache(this, row.getIntColumn("bitstream_id")); modified = modifiedMetadata = false; clearDetails(); } /** * Get a bitstream from the database. The bitstream metadata is loaded into * memory. * * @param context * DSpace context object * @param id * ID of the bitstream * * @return the bitstream, or null if the ID is invalid. * @throws SQLException */ public static Bitstream find(Context context, int id) throws SQLException { // First check the cache Bitstream fromCache = (Bitstream) context .fromCache(Bitstream.class, id); if (fromCache != null) { return fromCache; } TableRow row = DatabaseManager.find(context, "bitstream", id); if (row == null) { if (log.isDebugEnabled()) { log.debug(LogManager.getHeader(context, "find_bitstream", "not_found,bitstream_id=" + id)); } return null; } // not null, return Bitstream if (log.isDebugEnabled()) { log.debug(LogManager.getHeader(context, "find_bitstream", "bitstream_id=" + id)); } return new Bitstream(context, row); } /** * Create a new bitstream, with a new ID. The checksum and file size are * calculated. This method is not public, and does not check authorisation; * other methods such as Bundle.createBitstream() will check authorisation. * The newly created bitstream has the "unknown" format. * * @param context * DSpace context object * @param is * the bits to put in the bitstream * * @return the newly created bitstream * @throws IOException * @throws SQLException */ static Bitstream create(Context context, InputStream is) throws IOException, SQLException { // Store the bits int bitstreamID = BitstreamStorageManager.store(context, is); log.info(LogManager.getHeader(context, "create_bitstream", "bitstream_id=" + bitstreamID)); // Set the format to "unknown" Bitstream bitstream = find(context, bitstreamID); bitstream.setFormat(null); context.addEvent(new Event(Event.CREATE, Constants.BITSTREAM, bitstreamID, null)); return bitstream; } /** * Register a new bitstream, with a new ID. The checksum and file size * are calculated. This method is not public, and does not check * authorisation; other methods such as Bundle.createBitstream() will * check authorisation. The newly created bitstream has the "unknown" * format. * * @param context DSpace context object * @param assetstore corresponds to an assetstore in dspace.cfg * @param bitstreamPath the path and filename relative to the assetstore * @return the newly registered bitstream * @throws IOException * @throws SQLException */ static Bitstream register(Context context, int assetstore, String bitstreamPath) throws IOException, SQLException { // Store the bits int bitstreamID = BitstreamStorageManager.register( context, assetstore, bitstreamPath); log.info(LogManager.getHeader(context, "create_bitstream", "bitstream_id=" + bitstreamID)); // Set the format to "unknown" Bitstream bitstream = find(context, bitstreamID); bitstream.setFormat(null); context.addEvent(new Event(Event.CREATE, Constants.BITSTREAM, bitstreamID, "REGISTER")); return bitstream; } /** * Get the internal identifier of this bitstream * * @return the internal identifier */ public int getID() { return bRow.getIntColumn("bitstream_id"); } public String getHandle() { // No Handles for bitstreams return null; } /** * Get the sequence ID of this bitstream * * @return the sequence ID */ public int getSequenceID() { return bRow.getIntColumn("sequence_id"); } /** * Set the sequence ID of this bitstream * * @param sid * the ID */ public void setSequenceID(int sid) { bRow.setColumn("sequence_id", sid); modifiedMetadata = true; addDetails("SequenceID"); } /** * Get the name of this bitstream - typically the filename, without any path * information * * @return the name of the bitstream */ public String getName() { return bRow.getStringColumn("name"); } /** * Set the name of the bitstream * * @param n * the new name of the bitstream */ public void setName(String n) { bRow.setColumn("name", n); modifiedMetadata = true; addDetails("Name"); } /** * Get the source of this bitstream - typically the filename with path * information (if originally provided) or the name of the tool that * generated this bitstream * * @return the source of the bitstream */ public String getSource() { return bRow.getStringColumn("source"); } /** * Set the source of the bitstream * * @param n * the new source of the bitstream */ public void setSource(String n) { bRow.setColumn("source", n); modifiedMetadata = true; addDetails("Source"); } /** * Get the description of this bitstream - optional free text, typically * provided by a user at submission time * * @return the description of the bitstream */ public String getDescription() { return bRow.getStringColumn("description"); } /** * Set the description of the bitstream * * @param n * the new description of the bitstream */ public void setDescription(String n) { bRow.setColumn("description", n); modifiedMetadata = true; addDetails("Description"); } /** * Get the checksum of the content of the bitstream, for integrity checking * * @return the checksum */ public String getChecksum() { return bRow.getStringColumn("checksum"); } /** * Get the algorithm used to calculate the checksum * * @return the algorithm, e.g. "MD5" */ public String getChecksumAlgorithm() { return bRow.getStringColumn("checksum_algorithm"); } /** * Get the size of the bitstream * * @return the size in bytes */ public long getSize() { return bRow.getLongColumn("size_bytes"); } /** * Set the user's format description. This implies that the format of the * bitstream is uncertain, and the format is set to "unknown." * * @param desc * the user's description of the format * @throws SQLException */ public void setUserFormatDescription(String desc) throws SQLException { // FIXME: Would be better if this didn't throw an SQLException, // but we need to find the unknown format! setFormat(null); bRow.setColumn("user_format_description", desc); modifiedMetadata = true; addDetails("UserFormatDescription"); } /** * Get the user's format description. Returns null if the format is known by * the system. * * @return the user's format description. */ public String getUserFormatDescription() { return bRow.getStringColumn("user_format_description"); } /** * Get the description of the format - either the user's or the description * of the format defined by the system. * * @return a description of the format. */ public String getFormatDescription() { if (bitstreamFormat.getShortDescription().equals("Unknown")) { // Get user description if there is one String desc = bRow.getStringColumn("user_format_description"); if (desc == null) { return "Unknown"; } return desc; } // not null or Unknown return bitstreamFormat.getShortDescription(); } /** * Get the format of the bitstream * * @return the format of this bitstream */ public BitstreamFormat getFormat() { return bitstreamFormat; } /** * Set the format of the bitstream. If the user has supplied a type * description, it is cleared. Passing in <code>null</code> sets the type * of this bitstream to "unknown". * * @param f * the format of this bitstream, or <code>null</code> for * unknown * @throws SQLException */ public void setFormat(BitstreamFormat f) throws SQLException { // FIXME: Would be better if this didn't throw an SQLException, // but we need to find the unknown format! if (f == null) { // Use "Unknown" format bitstreamFormat = BitstreamFormat.findUnknown(bContext); } else { bitstreamFormat = f; } // Remove user type description bRow.setColumnNull("user_format_description"); // Update the ID in the table row bRow.setColumn("bitstream_format_id", bitstreamFormat.getID()); modified = true; } /** * Update the bitstream metadata. Note that the content of the bitstream * cannot be changed - for that you need to create a new bitstream. * * @throws SQLException * @throws AuthorizeException */ public void update() throws SQLException, AuthorizeException { Boolean overideAuthorization = false; Bundle[] bundles = getBundles(); if (bundles.length == 1) // only one parent Bundle for this Bitstream { Item[] items = bundles[0].getItems(); if (items.length == 1) // only one parent Item for this Bundle { if (items[0].canEdit()) // user attempting to update Bitstream is authorized to edit parent Item { overideAuthorization = true; } } } if (overideAuthorization) { bContext.turnOffAuthorisationSystem(); AuthorizeManager.authorizeAction(bContext, this, Constants.WRITE); bContext.restoreAuthSystemState(); } else { // Check authorisation AuthorizeManager.authorizeAction(bContext, this, Constants.WRITE); } log.info(LogManager.getHeader(bContext, "update_bitstream", "bitstream_id=" + getID())); if (modified) { bContext.addEvent(new Event(Event.MODIFY, Constants.BITSTREAM, getID(), null)); modified = false; } if (modifiedMetadata) { bContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.BITSTREAM, getID(), getDetails())); modifiedMetadata = false; clearDetails(); } DatabaseManager.update(bContext, bRow); } /** * Delete the bitstream, including any mappings to bundles * * @throws SQLException */ void delete() throws SQLException { boolean oracle = false; if ("oracle".equals(ConfigurationManager.getProperty("db.name"))) { oracle = true; } // changed to a check on remove // Check authorisation //AuthorizeManager.authorizeAction(bContext, this, Constants.DELETE); log.info(LogManager.getHeader(bContext, "delete_bitstream", "bitstream_id=" + getID())); bContext.addEvent(new Event(Event.DELETE, Constants.BITSTREAM, getID(), String.valueOf(getSequenceID()))); // Remove from cache bContext.removeCached(this, getID()); // Remove policies AuthorizeManager.removeAllPolicies(bContext, this); // Remove references to primary bitstreams in bundle String query = "update bundle set primary_bitstream_id = "; query += (oracle ? "''" : "Null") + " where primary_bitstream_id = ? "; DatabaseManager.updateQuery(bContext, query, bRow.getIntColumn("bitstream_id")); // Remove bitstream itself BitstreamStorageManager.delete(bContext, bRow .getIntColumn("bitstream_id")); } /** * Retrieve the contents of the bitstream * * @return a stream from which the bitstream can be read. * @throws IOException * @throws SQLException * @throws AuthorizeException */ public InputStream retrieve() throws IOException, SQLException, AuthorizeException { // Maybe should return AuthorizeException?? AuthorizeManager.authorizeAction(bContext, this, Constants.READ); return BitstreamStorageManager.retrieve(bContext, bRow .getIntColumn("bitstream_id")); } /** * Get the bundles this bitstream appears in * * @return array of <code>Bundle</code> s this bitstream appears in * @throws SQLException */ public Bundle[] getBundles() throws SQLException { // Get the bundle table rows TableRowIterator tri = DatabaseManager.queryTable(bContext, "bundle", "SELECT bundle.* FROM bundle, bundle2bitstream WHERE " + "bundle.bundle_id=bundle2bitstream.bundle_id AND " + "bundle2bitstream.bitstream_id= ? ", bRow.getIntColumn("bitstream_id")); // Build a list of Bundle objects List<Bundle> bundles = new ArrayList<Bundle>(); try { while (tri.hasNext()) { TableRow r = tri.next(); // First check the cache Bundle fromCache = (Bundle) bContext.fromCache(Bundle.class, r .getIntColumn("bundle_id")); if (fromCache != null) { bundles.add(fromCache); } else { bundles.add(new Bundle(bContext, r)); } } } finally { // close the TableRowIterator to free up resources if (tri != null) tri.close(); } Bundle[] bundleArray = new Bundle[bundles.size()]; bundleArray = (Bundle[]) bundles.toArray(bundleArray); return bundleArray; } /** * return type found in Constants * * @return int Constants.BITSTREAM */ public int getType() { return Constants.BITSTREAM; } /** * Determine if this bitstream is registered * * @return true if the bitstream is registered, false otherwise */ public boolean isRegisteredBitstream() { return BitstreamStorageManager .isRegisteredBitstream(bRow.getStringColumn("internal_id")); } /** * Get the asset store number where this bitstream is stored * * @return the asset store number of the bitstream */ public int getStoreNumber() { return bRow.getIntColumn("store_number"); } /** * @return the bContext */ public Context getbContext() { return bContext; } }