KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > dspace > storage > bitstore > BitstreamStorageManager


1 /*
2  * BitstreamStorageManager.java
3  *
4  * Version: $Revision: 1.22 $
5  *
6  * Date: $Date: 2006/09/04 12:48:51 $
7  *
8  * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts
9  * Institute of Technology. All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions are
13  * met:
14  *
15  * - Redistributions of source code must retain the above copyright
16  * notice, this list of conditions and the following disclaimer.
17  *
18  * - Redistributions in binary form must reproduce the above copyright
19  * notice, this list of conditions and the following disclaimer in the
20  * documentation and/or other materials provided with the distribution.
21  *
22  * - Neither the name of the Hewlett-Packard Company nor the name of the
23  * Massachusetts Institute of Technology nor the names of their
24  * contributors may be used to endorse or promote products derived from
25  * this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
33  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
34  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
35  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
36  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
37  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
38  * DAMAGE.
39  */

40 package org.dspace.storage.bitstore;
41
42 import java.io.File JavaDoc;
43 import java.io.IOException JavaDoc;
44 import java.io.InputStream JavaDoc;
45 import java.security.DigestInputStream JavaDoc;
46 import java.security.MessageDigest JavaDoc;
47 import java.security.NoSuchAlgorithmException JavaDoc;
48 import java.sql.SQLException JavaDoc;
49 import java.util.ArrayList JavaDoc;
50 import java.util.Iterator JavaDoc;
51 import java.util.List JavaDoc;
52
53 import org.apache.log4j.Logger;
54 import org.dspace.checker.BitstreamInfoDAO;
55 import org.dspace.core.ConfigurationManager;
56 import org.dspace.core.Context;
57 import org.dspace.core.Utils;
58 import org.dspace.storage.rdbms.DatabaseManager;
59 import org.dspace.storage.rdbms.TableRow;
60
61 import edu.sdsc.grid.io.FileFactory;
62 import edu.sdsc.grid.io.GeneralFile;
63 import edu.sdsc.grid.io.GeneralFileOutputStream;
64 import edu.sdsc.grid.io.local.LocalFile;
65 import edu.sdsc.grid.io.srb.SRBAccount;
66 import edu.sdsc.grid.io.srb.SRBFile;
67 import edu.sdsc.grid.io.srb.SRBFileSystem;
68
69 /**
70  * <P>
71  * Stores, retrieves and deletes bitstreams.
72  * </P>
73  *
74  * <P>
75  * Presently, asset stores are specified in <code>dspace.cfg</code>. Since
76  * Java does not offer a way of detecting free disk space, the asset store to
77  * use for new bitstreams is also specified in a configuration property. The
78  * drawbacks to this are that the administrators are responsible for monitoring
79  * available space in the asset stores, and DSpace (Tomcat) has to be restarted
80  * when the asset store for new ('incoming') bitstreams is changed.
81  * </P>
82  *
83  * <P>
84  * Mods by David Little, UCSD Libraries 12/21/04 to allow the registration of
85  * files (bitstreams) into DSpace.
86  * </P>
87  *
88  * <p>Cleanup integration with checker package by Nate Sarr 2006-01. N.B. The
89  * dependency on the checker package isn't ideal - a Listener pattern would be
90  * better but was considered overkill for the purposes of integrating the checker.
91  * It would be worth re-considering a Listener pattern if another package needs to
92  * be notified of BitstreamStorageManager actions.</p>
93  *
94  * @author Peter Breton, Robert Tansley, David Little, Nathan Sarr
95  * @version $Revision: 1.22 $
96  */

97 public class BitstreamStorageManager
98 {
99     /** log4j log */
100     private static Logger log = Logger.getLogger(BitstreamStorageManager.class);
101
102     /**
103      * The asset store locations. The information for each GeneralFile in the
104      * array comes from dspace.cfg, so see the comments in that file.
105      *
106      * If an array element refers to a conventional (non_SRB) asset store, the
107      * element will be a LocalFile object (similar to a java.io.File object)
108      * referencing a local directory under which the bitstreams are stored.
109      *
110      * If an array element refers to an SRB asset store, the element will be an
111      * SRBFile object referencing an SRB 'collection' (directory) under which
112      * the bitstreams are stored.
113      *
114      * An SRBFile object is obtained by (1) using dspace.cfg properties to
115      * create an SRBAccount object (2) using the account to create an
116      * SRBFileSystem object (similar to a connection) (3) using the
117      * SRBFileSystem object to create an SRBFile object
118      */

119     private static GeneralFile[] assetStores;
120
121     /** The asset store to use for new bitstreams */
122     private static int incoming;
123
124     // These settings control the way an identifier is hashed into
125
// directory and file names
126
//
127
// With digitsPerLevel 2 and directoryLevels 3, an identifier
128
// like 12345678901234567890 turns into the relative name
129
// /12/34/56/12345678901234567890.
130
//
131
// You should not change these settings if you have data in the
132
// asset store, as the BitstreamStorageManager will be unable
133
// to find your existing data.
134
private static final int digitsPerLevel = 2;
135
136     private static final int directoryLevels = 3;
137
138     /**
139      * This prefix string marks registered bitstreams in internal_id
140      */

141     private static final String JavaDoc REGISTERED_FLAG = "-R";
142
143     /* Read in the asset stores from the config. */
144     static
145     {
146         ArrayList JavaDoc stores = new ArrayList JavaDoc();
147
148         // 'assetstore.dir' is always store number 0
149
String JavaDoc sAssetstoreDir = ConfigurationManager
150                 .getProperty("assetstore.dir");
151  
152         // see if conventional assetstore or srb
153
if (sAssetstoreDir != null) {
154             stores.add(sAssetstoreDir); // conventional (non-srb)
155
} else if (ConfigurationManager.getProperty("srb.host") != null) {
156             stores.add(new SRBAccount( // srb
157
ConfigurationManager.getProperty("srb.host"),
158                     ConfigurationManager.getIntProperty("srb.port"),
159                     ConfigurationManager.getProperty("srb.username"),
160                     ConfigurationManager.getProperty("srb.password"),
161                     ConfigurationManager.getProperty("srb.homedirectory"),
162                     ConfigurationManager.getProperty("srb.mdasdomainname"),
163                     ConfigurationManager
164                             .getProperty("srb.defaultstorageresource"),
165                     ConfigurationManager.getProperty("srb.mcatzone")));
166         } else {
167             log.error("No default assetstore");
168         }
169
170         // read in assetstores .1, .2, ....
171
for (int i = 1;; i++) { // i == 0 is default above
172
sAssetstoreDir = ConfigurationManager.getProperty("assetstore.dir."
173                     + i);
174
175             // see if 'i' conventional assetstore or srb
176
if (sAssetstoreDir != null) { // conventional (non-srb)
177
stores.add(sAssetstoreDir);
178             } else if (ConfigurationManager.getProperty("srb.host." + i)
179                     != null) { // srb
180
stores.add(new SRBAccount(
181                         ConfigurationManager.getProperty("srb.host." + i),
182                         ConfigurationManager.getIntProperty("srb.port." + i),
183                         ConfigurationManager.getProperty("srb.username." + i),
184                         ConfigurationManager.getProperty("srb.password." + i),
185                         ConfigurationManager
186                                 .getProperty("srb.homedirectory." + i),
187                         ConfigurationManager
188                                 .getProperty("srb.mdasdomainname." + i),
189                         ConfigurationManager
190                                 .getProperty("srb.defaultstorageresource." + i),
191                         ConfigurationManager.getProperty("srb.mcatzone." + i)));
192             } else {
193                 break; // must be at the end of the assetstores
194
}
195         }
196
197         // convert list to array
198
// the elements (objects) in the list are class
199
// (1) String - conventional non-srb assetstore
200
// (2) SRBAccount - srb assetstore
201
assetStores = new GeneralFile[stores.size()];
202         for (int i = 0; i < stores.size(); i++) {
203             Object JavaDoc o = stores.get(i);
204             if (o == null) { // I don't know if this can occur
205
log.error("Problem with assetstore " + i);
206             }
207             if (o instanceof String JavaDoc) {
208                 assetStores[i] = new LocalFile((String JavaDoc) o);
209             } else if (o instanceof SRBAccount) {
210                 SRBFileSystem srbFileSystem = null;
211                 try {
212                     srbFileSystem = new SRBFileSystem((SRBAccount) o);
213                 } catch (NullPointerException JavaDoc e) {
214                     log.error("No SRBAccount for assetstore " + i);
215                 } catch (IOException JavaDoc e) {
216                     log.error("Problem getting SRBFileSystem for assetstore"
217                             + i);
218                 }
219                 if (srbFileSystem == null) {
220                     log.error("SRB FileSystem is null for assetstore " + i);
221                 }
222                 String JavaDoc sSRBAssetstore = null;
223                 if (i == 0) { // the zero (default) assetstore has no suffix
224
sSRBAssetstore = ConfigurationManager
225                             .getProperty("srb.parentdir");
226                 } else {
227                     sSRBAssetstore = ConfigurationManager
228                             .getProperty("srb.parentdir." + i);
229                 }
230                 if (sSRBAssetstore == null) {
231                     log.error("srb.parentdir is undefined for assetstore " + i);
232                 }
233                 assetStores[i] = new SRBFile(srbFileSystem, sSRBAssetstore);
234             } else {
235                 log.error("Unexpected " + o.getClass().toString()
236                         + " with assetstore " + i);
237             }
238         }
239
240         // Read asset store to put new files in. Default is 0.
241
incoming = ConfigurationManager.getIntProperty("assetstore.incoming");
242     }
243
244     /**
245      * Store a stream of bits.
246      *
247      * <p>
248      * If this method returns successfully, the bits have been stored, and RDBMS
249      * metadata entries are in place (the context still needs to be completed to
250      * finalize the transaction).
251      * </p>
252      *
253      * <p>
254      * If this method returns successfully and the context is aborted, then the
255      * bits will be stored in the asset store and the RDBMS metadata entries
256      * will exist, but with the deleted flag set.
257      * </p>
258      *
259      * If this method throws an exception, then any of the following may be
260      * true:
261      *
262      * <ul>
263      * <li>Neither bits nor RDBMS metadata entries have been stored.
264      * <li>RDBMS metadata entries with the deleted flag set have been stored,
265      * but no bits.
266      * <li>RDBMS metadata entries with the deleted flag set have been stored,
267      * and some or all of the bits have also been stored.
268      * </ul>
269      *
270      * @param context
271      * The current context
272      * @param is
273      * The stream of bits to store
274      * @exception IOException
275      * If a problem occurs while storing the bits
276      * @exception SQLException
277      * If a problem occurs accessing the RDBMS
278      *
279      * @return The ID of the stored bitstream
280      */

281     public static int store(Context context, InputStream JavaDoc is)
282             throws SQLException JavaDoc, IOException JavaDoc
283     {
284         // Create internal ID
285
String JavaDoc id = Utils.generateKey();
286
287         // Create a deleted bitstream row, using a separate DB connection
288
TableRow bitstream;
289         Context tempContext = null;
290
291         try
292         {
293             tempContext = new Context();
294
295             bitstream = DatabaseManager.create(tempContext, "Bitstream");
296             bitstream.setColumn("deleted", true);
297             bitstream.setColumn("internal_id", id);
298
299             /*
300              * Set the store number of the new bitstream If you want to use some
301              * other method of working out where to put a new bitstream, here's
302              * where it should go
303              */

304             bitstream.setColumn("store_number", incoming);
305
306             DatabaseManager.update(tempContext, bitstream);
307
308             tempContext.complete();
309         }
310         catch (SQLException JavaDoc sqle)
311         {
312             if (tempContext != null)
313             {
314                 tempContext.abort();
315             }
316
317             throw sqle;
318         }
319
320         // Where on the file system will this new bitstream go?
321
GeneralFile file = getFile(bitstream);
322
323         // Make the parent dirs if necessary
324
GeneralFile parent = file.getParentFile();
325
326         if (!parent.exists())
327         {
328             parent.mkdirs();
329         }
330
331         //Create the corresponding file and open it
332
file.createNewFile();
333
334         GeneralFileOutputStream fos = FileFactory.newFileOutputStream(file);
335
336         // Read through a digest input stream that will work out the MD5
337
DigestInputStream JavaDoc dis = null;
338
339         try
340         {
341             dis = new DigestInputStream JavaDoc(is, MessageDigest.getInstance("MD5"));
342         }
343         // Should never happen
344
catch (NoSuchAlgorithmException JavaDoc nsae)
345         {
346             log.warn("Caught NoSuchAlgorithmException", nsae);
347         }
348
349         Utils.bufferedCopy(dis, fos);
350         fos.close();
351         is.close();
352
353         bitstream.setColumn("size_bytes", file.length());
354
355         bitstream.setColumn("checksum", Utils.toHex(dis.getMessageDigest()
356                 .digest()));
357         bitstream.setColumn("checksum_algorithm", "MD5");
358         bitstream.setColumn("deleted", false);
359         DatabaseManager.update(context, bitstream);
360
361         int bitstream_id = bitstream.getIntColumn("bitstream_id");
362
363         if (log.isDebugEnabled())
364         {
365             log.debug("Stored bitstream " + bitstream_id + " in file "
366                     + file.getAbsolutePath());
367         }
368
369         return bitstream_id;
370     }
371
372     /**
373      * Register a bitstream already in storage.
374      *
375      * @param context
376      * The current context
377      * @param assetstore The assetstore number for the bitstream to be
378      * registered
379      * @param bitstreamPath The relative path of the bitstream to be registered.
380      * The path is relative to the path of ths assetstore.
381      * @return The ID of the registered bitstream
382      * @exception SQLException
383      * If a problem occurs accessing the RDBMS
384      * @throws IOExeption
385      */

386     public static int register(Context context, int assetstore,
387                 String JavaDoc bitstreamPath) throws SQLException JavaDoc, IOException JavaDoc {
388
389         // mark this bitstream as a registered bitstream
390
String JavaDoc sInternalId = REGISTERED_FLAG + bitstreamPath;
391
392         // Create a deleted bitstream row, using a separate DB connection
393
TableRow bitstream;
394         Context tempContext = null;
395
396         try {
397             tempContext = new Context();
398
399             bitstream = DatabaseManager.create(tempContext, "Bitstream");
400             bitstream.setColumn("deleted", true);
401             bitstream.setColumn("internal_id", sInternalId);
402             bitstream.setColumn("store_number", assetstore);
403             DatabaseManager.update(tempContext, bitstream);
404
405             tempContext.complete();
406         } catch (SQLException JavaDoc sqle) {
407             if (tempContext != null) {
408                 tempContext.abort();
409             }
410             throw sqle;
411         }
412
413         // get a reference to the file
414
GeneralFile file = getFile(bitstream);
415
416         // read through a DigestInputStream that will work out the MD5
417
//
418
// DSpace refers to checksum, writes it in METS, and uses it as an
419
// AIP filename (!), but never seems to validate with it. Furthermore,
420
// DSpace appears to hardcode the algorithm to MD5 in some places--see
421
// METSExport.java.
422
//
423
// To remain compatible with DSpace we calculate an MD5 checksum on
424
// LOCAL registered files. But for REMOTE (e.g. SRB) files we
425
// calculate an MD5 on just the fileNAME. The reasoning is that in the
426
// case of a remote file, calculating an MD5 on the file itself will
427
// generate network traffic to read the file's bytes. In this case it
428
// would be better have a proxy process calculate MD5 and store it as
429
// an SRB metadata attribute so it can be retrieved simply from SRB.
430
//
431
// TODO set this up as a proxy server process so no net activity
432

433         // FIXME this is a first class HACK! for the reasons described above
434
if (file instanceof LocalFile)
435         {
436
437             // get MD5 on the file for local file
438
DigestInputStream JavaDoc dis = null;
439             try
440             {
441                 dis = new DigestInputStream JavaDoc(FileFactory.newFileInputStream(file),
442                         MessageDigest.getInstance("MD5"));
443             }
444             catch (NoSuchAlgorithmException JavaDoc e)
445             {
446                 log.warn("Caught NoSuchAlgorithmException", e);
447                 throw new IOException JavaDoc("Invalid checksum algorithm");
448             }
449             catch (IOException JavaDoc e)
450             {
451                 log.error("File: " + file.getAbsolutePath()
452                         + " to be registered cannot be opened - is it "
453                         + "really there?");
454                 throw e;
455             }
456             final int BUFFER_SIZE = 1024 * 4;
457             final byte[] buffer = new byte[BUFFER_SIZE];
458             while (true)
459             {
460                 final int count = dis.read(buffer, 0, BUFFER_SIZE);
461                 if (count == -1)
462                 {
463                     break;
464                 }
465             }
466             bitstream.setColumn("checksum", Utils.toHex(dis.getMessageDigest()
467                     .digest()));
468             dis.close();
469         }
470         else if (file instanceof SRBFile)
471         {
472             if (!file.exists())
473             {
474                 log.error("File: " + file.getAbsolutePath()
475                         + " is not in SRB MCAT");
476                 throw new IOException JavaDoc("File is not in SRB MCAT");
477             }
478
479             // get MD5 on just the filename (!) for SRB file
480
int iLastSlash = bitstreamPath.lastIndexOf('/');
481             String JavaDoc sFilename = bitstreamPath.substring(iLastSlash + 1);
482             MessageDigest JavaDoc md = null;
483             try
484             {
485                 md = MessageDigest.getInstance("MD5");
486             }
487             catch (NoSuchAlgorithmException JavaDoc e)
488             {
489                 log.error("Caught NoSuchAlgorithmException", e);
490                 throw new IOException JavaDoc("Invalid checksum algorithm");
491             }
492             bitstream.setColumn("checksum",
493                     Utils.toHex(md.digest(sFilename.getBytes())));
494         }
495         else
496         {
497             throw new IOException JavaDoc("Unrecognized file type - "
498                     + "not local, not SRB");
499         }
500
501         bitstream.setColumn("checksum_algorithm", "MD5");
502         bitstream.setColumn("size_bytes", file.length());
503         bitstream.setColumn("deleted", false);
504         DatabaseManager.update(context, bitstream);
505
506         int bitstream_id = bitstream.getIntColumn("bitstream_id");
507         if (log.isDebugEnabled())
508         {
509             log.debug("Stored bitstream " + bitstream_id + " in file "
510                     + file.getAbsolutePath());
511         }
512         return bitstream_id;
513     }
514
515     /**
516      * Does the internal_id column in the bitstream row indicate the bitstream
517      * is a registered file
518      *
519      * @param internalId the value of the internal_id column
520      * @return true if the bitstream is a registered file
521      */

522     public static boolean isRegisteredBitstream(String JavaDoc internalId) {
523         if (internalId.substring(0, REGISTERED_FLAG.length())
524                 .equals(REGISTERED_FLAG))
525         {
526             return true;
527         }
528         return false;
529     }
530
531     /**
532      * Retrieve the bits for the bitstream with ID. If the bitstream does not
533      * exist, or is marked deleted, returns null.
534      *
535      * @param context
536      * The current context
537      * @param id
538      * The ID of the bitstream to retrieve
539      * @exception IOException
540      * If a problem occurs while retrieving the bits
541      * @exception SQLException
542      * If a problem occurs accessing the RDBMS
543      *
544      * @return The stream of bits, or null
545      */

546     public static InputStream JavaDoc retrieve(Context context, int id)
547             throws SQLException JavaDoc, IOException JavaDoc
548     {
549         TableRow bitstream = DatabaseManager.find(context, "bitstream", id);
550
551         GeneralFile file = getFile(bitstream);
552
553         return (file != null) ? FileFactory.newFileInputStream(file) : null;
554     }
555
556     /**
557      * <p>
558      * Remove a bitstream from the asset store. This method does not delete any
559      * bits, but simply marks the bitstreams as deleted (the context still needs
560      * to be completed to finalize the transaction).
561      * </p>
562      *
563      * <p>
564      * If the context is aborted, the bitstreams deletion status remains
565      * unchanged.
566      * </p>
567      *
568      * @param context
569      * The current context
570      * @param id
571      * The ID of the bitstream to delete
572      * @exception SQLException
573      * If a problem occurs accessing the RDBMS
574      */

575     public static void delete(Context context, int id) throws SQLException JavaDoc
576     {
577         DatabaseManager.updateQuery(context,
578                         "update Bitstream set deleted = '1' where bitstream_id = ? ",
579                         id);
580     }
581
582     /**
583      * Clean up the bitstream storage area. This method deletes any bitstreams
584      * which are more than 1 hour old and marked deleted. The deletions cannot
585      * be undone.
586      *
587      * @param deleteDbRecords if true deletes the database records otherwise it
588      * only deletes the files and directories in the assetstore
589      * @exception IOException
590      * If a problem occurs while cleaning up
591      * @exception SQLException
592      * If a problem occurs accessing the RDBMS
593      */

594     public static void cleanup(boolean deleteDbRecords) throws SQLException JavaDoc, IOException JavaDoc
595     {
596         Context context = null;
597         BitstreamInfoDAO bitstreamInfoDAO = new BitstreamInfoDAO();
598         int commit_counter = 0;
599
600         try
601         {
602             context = new Context();
603
604             String JavaDoc myQuery = "select * from Bitstream where deleted = '1'";
605
606             List JavaDoc storage = DatabaseManager.queryTable(context, "Bitstream", myQuery)
607                     .toList();
608
609             for (Iterator JavaDoc iterator = storage.iterator(); iterator.hasNext();)
610             {
611                 TableRow row = (TableRow) iterator.next();
612                 int bid = row.getIntColumn("bitstream_id");
613
614                 GeneralFile file = getFile(row);
615
616                 // Make sure entries which do not exist are removed
617
if (file == null || !file.exists())
618                 {
619                     log.debug("file is null");
620                     if (deleteDbRecords)
621                     {
622                         log.debug("deleting record");
623                         bitstreamInfoDAO.deleteBitstreamInfoWithHistory(bid);
624                         DatabaseManager.delete(context, "Bitstream", bid);
625                     }
626                     continue;
627                 }
628
629                 // This is a small chance that this is a file which is
630
// being stored -- get it next time.
631
if (isRecent(file))
632                 {
633                     log.debug("file is recent");
634                     continue;
635                 }
636
637                 if (deleteDbRecords)
638                 {
639                     log.debug("deleting db record");
640                     bitstreamInfoDAO.deleteBitstreamInfoWithHistory(bid);
641                     DatabaseManager.delete(context, "Bitstream", bid);
642                 }
643
644                 if (isRegisteredBitstream(row.getStringColumn("internal_id"))) {
645                     continue; // do not delete registered bitstreams
646
}
647
648                 boolean success = file.delete();
649
650                 if (log.isDebugEnabled())
651                 {
652                     log.debug("Deleted bitstream " + bid + " (file "
653                             + file.getAbsolutePath() + ") with result "
654                             + success);
655                 }
656
657                 // if the file was deleted then
658
// try deleting the parents
659
// Otherwise the cleanup script is set to
660
// leave the db records then the file
661
// and directories have already been deleted
662
// if this is turned off then it still looks like the
663
// file exists
664
if( success )
665                 {
666                     deleteParents(file);
667                 }
668                 
669                 // Make sure to commit our outstanding work every 100
670
// iterations. Otherwise you risk losing the entire transaction
671
// if we hit an exception, which isn't useful at all for large
672
// amounts of bitstreams.
673
commit_counter++;
674                 if (commit_counter % 100 == 0)
675                 {
676                     context.commit();
677                 }
678             }
679
680             context.complete();
681         }
682         // Aborting will leave the DB objects around, even if the
683
// bitstreams are deleted. This is OK; deleting them next
684
// time around will be a no-op.
685
catch (SQLException JavaDoc sqle)
686         {
687             context.abort();
688             throw sqle;
689         }
690         catch (IOException JavaDoc ioe)
691         {
692             context.abort();
693             throw ioe;
694         }
695     }
696
697     ////////////////////////////////////////
698
// Internal methods
699
////////////////////////////////////////
700

701     /**
702      * Return true if this file is too recent to be deleted, false otherwise.
703      *
704      * @param file
705      * The file to check
706      * @return True if this file is too recent to be deleted
707      */

708     private static boolean isRecent(GeneralFile file)
709     {
710         long lastmod = file.lastModified();
711         long now = new java.util.Date JavaDoc().getTime();
712
713         if (lastmod >= now)
714         {
715             return true;
716         }
717
718         // Less than one hour old
719
return (now - lastmod) < (1 * 60 * 1000);
720     }
721
722     /**
723      * Delete empty parent directories.
724      *
725      * @param file
726      * The file with parent directories to delete
727      */

728     private synchronized static void deleteParents(GeneralFile file)
729     {
730         if (file == null )
731         {
732             return;
733         }
734  
735         GeneralFile tmp = file;
736
737         for (int i = 0; i < directoryLevels; i++)
738         {
739
740             GeneralFile directory = tmp.getParentFile();
741             GeneralFile[] files = directory.listFiles();
742
743             // Only delete empty directories
744
if (files.length != 0)
745             {
746                 break;
747             }
748
749             directory.delete();
750             tmp = directory;
751         }
752     }
753
754     /**
755      * Return the file corresponding to a bitstream. It's safe to pass in
756      * <code>null</code>.
757      *
758      * @param bitstream
759      * the database table row for the bitstream. Can be
760      * <code>null</code>
761      *
762      * @return The corresponding file in the file system, or <code>null</code>
763      *
764      * @exception IOException
765      * If a problem occurs while determining the file
766      */

767     private static GeneralFile getFile(TableRow bitstream) throws IOException JavaDoc
768     {
769         // Check that bitstream is not null
770
if (bitstream == null)
771         {
772             return null;
773         }
774
775         // Get the store to use
776
int storeNumber = bitstream.getIntColumn("store_number");
777
778         // Default to zero ('assetstore.dir') for backwards compatibility
779
if (storeNumber == -1)
780         {
781             storeNumber = 0;
782         }
783
784         GeneralFile assetstore = assetStores[storeNumber];
785
786         // turn the internal_id into a file path relative to the assetstore
787
// directory
788
String JavaDoc sInternalId = bitstream.getStringColumn("internal_id");
789
790         // there are 4 cases:
791
// -conventional bitstream, conventional storage
792
// -conventional bitstream, srb storage
793
// -registered bitstream, conventional storage
794
// -registered bitstream, srb storage
795
// conventional bitstream - dspace ingested, dspace random name/path
796
// registered bitstream - registered to dspace, any name/path
797
String JavaDoc sIntermediatePath = null;
798         if (isRegisteredBitstream(sInternalId)) {
799             sInternalId = sInternalId.substring(REGISTERED_FLAG.length());
800             sIntermediatePath = "";
801         } else {
802             
803             // Sanity Check: If the internal ID contains a
804
// pathname separator, it's probably an attempt to
805
// make a path traversal attack, so ignore the path
806
// prefix. The internal-ID is supposed to be just a
807
// filename, so this will not affect normal operation.
808
if (sInternalId.indexOf(File.separator) != -1)
809                 sInternalId = sInternalId.substring(sInternalId.lastIndexOf(File.separator)+1);
810             
811             sIntermediatePath = getIntermediatePath(sInternalId);
812         }
813
814         StringBuffer JavaDoc bufFilename = new StringBuffer JavaDoc();
815         if (assetstore instanceof LocalFile) {
816             bufFilename.append(assetstore.getCanonicalPath());
817             bufFilename.append(File.separator);
818             bufFilename.append(sIntermediatePath);
819             bufFilename.append(sInternalId);
820             if (log.isDebugEnabled()) {
821                 log.debug("Local filename for " + sInternalId + " is "
822                         + bufFilename.toString());
823             }
824             return new LocalFile(bufFilename.toString());
825         }
826         if (assetstore instanceof SRBFile) {
827             bufFilename.append(sIntermediatePath);
828             bufFilename.append(sInternalId);
829             if (log.isDebugEnabled()) {
830                 log.debug("SRB filename for " + sInternalId + " is "
831                         + ((SRBFile) assetstore).toString()
832                         + bufFilename.toString());
833             }
834             return new SRBFile((SRBFile) assetstore, bufFilename.toString());
835         }
836         return null;
837     }
838
839     /**
840      * Return the intermediate path derived from the internal_id. This method
841      * splits the id into groups which become subdirectories.
842      *
843      * @param iInternalId
844      * The internal_id
845      * @return The path based on the id without leading or trailing separators
846      */

847     private static String JavaDoc getIntermediatePath(String JavaDoc iInternalId) {
848         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
849         for (int i = 0; i < directoryLevels; i++) {
850             int digits = i * digitsPerLevel;
851             if (i > 0) {
852                 buf.append(File.separator);
853             }
854             buf.append(iInternalId.substring(digits, digits
855                             + digitsPerLevel));
856         }
857         buf.append(File.separator);
858         return buf.toString();
859     }
860
861 }
862
Popular Tags