/* * BitstreamStorageManager.java * * Version: $Revision: 1.17 $ * * Date: $Date: 2006/01/20 16:13:19 $ * * Copyright (c) 2002-2007, 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.storage.bitstore; import java.io.IOException; import java.io.InputStream; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.dspace.checker.BitstreamInfoDAO; import org.dspace.core.ConfigurationManager; import org.dspace.core.Context; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; /** *
* Stores, retrieves and deletes bitstreams. *
* *
* Presently, asset stores are specified in dspace.cfg
. Since
* Java does not offer a way of detecting free disk space, the asset store to
* use for new bitstreams is also specified in a configuration property. The
* drawbacks to this are that the administrators are responsible for monitoring
* available space in the asset stores, and DSpace (Tomcat) has to be restarted
* when the asset store for new ('incoming') bitstreams is changed.
*
* Mods by David Little, UCSD Libraries 12/21/04 to allow the registration of * files (bitstreams) into DSpace. *
* *Cleanup integration with checker package by Nate Sarr 2006-01. N.B. The * dependency on the checker package isn't ideal - a Listener pattern would be * better but was considered overkill for the purposes of integrating the checker. * It would be worth re-considering a Listener pattern if another package needs to * be notified of BitstreamStorageManager actions.
* * @author Peter Breton, Robert Tansley, David Little, Nathan Sarr, Richard Rodgers * @version $Revision: 1.17 $ */ public class BitstreamStorageManager { /** log4j log */ private static Logger log = Logger.getLogger(BitstreamStorageManager.class); /** asset stores */ private static BitStore[] stores; /** The index of the asset store to use for new bitstreams */ private static int incoming; /** * This prefix string marks registered bitstreams in internal_id */ private static final String REGISTERED_FLAG = "-R"; /** default asset store implementation */ private static final String DEFAULT_STORE_PREFIX = "ds:"; private static final String DEFAULT_STORE_IMPL = "org.dspace.storage.bitstore.impl.DSAssetStore"; /* Read in the asset stores from the config. */ static { ArrayList list = new ArrayList(); // Begin block of code preserving backward compatibility with current // configuration syntax. Remove when superceded. // 'assetstore.dir' is always store number 0 String storeDir = ConfigurationManager.getProperty("assetstore.dir"); if (storeDir == null) { log.error("No default assetstore"); } else { initStore(DEFAULT_STORE_PREFIX + storeDir, list); // read any further ones for (int i = 1;; i++) { storeDir = ConfigurationManager.getProperty("assetstore.dir." + i); if (storeDir == null) { break; } initStore(DEFAULT_STORE_PREFIX + storeDir, list); } } // End compatibility block // if not already configured, configure asset stores for (int j = 0; j < 100; j++) { String assetCfg = ConfigurationManager.getProperty("assetstore." + j); if (assetCfg == null) { // no more stores configured - assumes sequential assignment break; } if (list.get(j) == null) { initStore(assetCfg, list); } } stores = (BitStore[])list.toArray(new BitStore[list.size()]); // Read asset store to put new files in. Default is 0. incoming = ConfigurationManager.getIntProperty("assetstore.incoming"); } private static void initStore(String storeConfig, List list) { // create and initialize an asset store int split = storeConfig.indexOf(":"); if (split != -1) { String prefix = storeConfig.substring(0,split); String config = storeConfig.substring(split+1); String className = ConfigurationManager.getProperty("bitstore." + prefix + ".class"); if (className == null && DEFAULT_STORE_PREFIX.equals(prefix)) { // use default implementation class if none explicitly defined className = DEFAULT_STORE_IMPL; } try { BitStore store = (BitStore)Class.forName(className).newInstance(); store.init(config); list.add(store); } catch (Exception e) { log.error("Cannot instantiate store class: " + className ); } } } private static void updateBitstream(TableRow bitstream, Map attrs) throws IOException { Iterator iter = attrs.keySet().iterator(); while (iter.hasNext()) { String column = (String)iter.next(); String value = (String)attrs.get(column); if (value != null) { bitstream.setColumn(column, value); } } } /** * Store a stream of bits. * ** If this method returns successfully, the bits have been stored, and RDBMS * metadata entries are in place (the context still needs to be completed to * finalize the transaction). *
* ** If this method returns successfully and the context is aborted, then the * bits will be stored in the asset store and the RDBMS metadata entries * will exist, but with the deleted flag set. *
* * If this method throws an exception, then any of the following may be * true: * ** Remove a bitstream from the asset store. This method does not delete any * bits, but simply marks the bitstreams as deleted (the context still needs * to be completed to finalize the transaction). *
* ** If the context is aborted, the bitstreams deletion status remains * unchanged. *
* * @param context * The current context * @param id * The ID of the bitstream to delete * @exception SQLException * If a problem occurs accessing the RDBMS */ public static void delete(Context context, int id) throws SQLException { DatabaseManager .updateQuery(context, "update Bitstream set deleted = '1' where bitstream_id = " + id); } /** * Clean up the bitstream storage area. This method deletes any bitstreams * which are more than 1 hour old and marked deleted. The deletions cannot * be undone. * * @param context * the current Context * * @param deleteDbRecords if true deletes the database records otherwise it * only deletes the files and directories in the assetstore * @exception IOException * If a problem occurs while cleaning up * @exception SQLException * If a problem occurs accessing the RDBMS */ public static void cleanup(Context context, boolean deleteDbRecords) throws SQLException, IOException { BitstreamInfoDAO bitstreamInfoDAO = new BitstreamInfoDAO(); String myQuery = "select * from Bitstream where deleted = '1'"; List storage = DatabaseManager.query(context, "Bitstream", myQuery) .toList(); for (Iterator iterator = storage.iterator(); iterator.hasNext();) { TableRow row = (TableRow) iterator.next(); int bid = row.getIntColumn("bitstream_id"); int storeNo = row.getIntColumn("store_number"); String id = row.getStringColumn("internal_id"); // all we care about is last modified time Map want = new HashMap(); want.put("modified", null); Map attrs = stores[storeNo].about(id, want); // Make sure entries which do not exist are removed if (attrs == null) { log.debug("file is null"); if (deleteDbRecords) { log.debug("deleting record"); bitstreamInfoDAO.deleteBitstreamInfoWithHistory(bid); DatabaseManager.delete(context, "Bitstream", bid); } continue; } // This is a small chance that this is a file which is // being stored -- get it next time. long lastmod = Long.valueOf((String)attrs.get("modified")).longValue(); long now = new java.util.Date().getTime(); // Skip if less than one hour old if (lastmod >= now || (now - lastmod) < (1 * 60 * 1000) ) { log.debug("file is recent"); continue; } if (deleteDbRecords) { log.debug("deleting db record"); bitstreamInfoDAO.deleteBitstreamInfoWithHistory(bid); DatabaseManager.delete(context, "Bitstream", bid); } if (isRegisteredBitstream(row.getStringColumn("internal_id"))) { continue; // do not delete registered bitstreams } stores[storeNo].remove(id); if (log.isDebugEnabled()) { log.debug("Deleted bitstream " + bid + " (id " + id + " )"); } } } }