001: /**
002: * JDBM LICENSE v1.00
003: *
004: * Redistribution and use of this software and associated documentation
005: * ("Software"), with or without modification, are permitted provided
006: * that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain copyright
009: * statements and notices. Redistributions must also contain a
010: * copy of this document.
011: *
012: * 2. Redistributions in binary form must reproduce the
013: * above copyright notice, this list of conditions and the
014: * following disclaimer in the documentation and/or other
015: * materials provided with the distribution.
016: *
017: * 3. The name "JDBM" must not be used to endorse or promote
018: * products derived from this Software without prior written
019: * permission of Cees de Groot. For written permission,
020: * please contact cg@cdegroot.com.
021: *
022: * 4. Products derived from this Software may not be called "JDBM"
023: * nor may "JDBM" appear in their names without prior written
024: * permission of Cees de Groot.
025: *
026: * 5. Due credit should be given to the JDBM Project
027: * (http://jdbm.sourceforge.net/).
028: *
029: * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030: * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031: * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
033: * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040: * OF THE POSSIBILITY OF SUCH DAMAGE.
041: *
042: * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043: * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
044: * Contributions are Copyright (C) 2000 by their associated contributors.
045: *
046: * $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
047: */package jdbm.recman;
048:
049: import java.io.IOException;
050:
051: import java.util.HashMap;
052: import java.util.Map;
053:
054: import jdbm.RecordManager;
055: import jdbm.helper.Serializer;
056: import jdbm.helper.DefaultSerializer;
057:
058: /**
059: * This class manages records, which are uninterpreted blobs of data. The
060: * set of operations is simple and straightforward: you communicate with
061: * the class using long "rowids" and byte[] data blocks. Rowids are returned
062: * on inserts and you can stash them away someplace safe to be able to get
063: * back to them. Data blocks can be as long as you wish, and may have
064: * lengths different from the original when updating.
065: * <p>
066: * Operations are synchronized, so that only one of them will happen
067: * concurrently even if you hammer away from multiple threads. Operations
068: * are made atomic by keeping a transaction log which is recovered after
069: * a crash, so the operations specified by this interface all have ACID
070: * properties.
071: * <p>
072: * You identify a file by just the name. The package attaches <tt>.db</tt>
073: * for the database file, and <tt>.lg</tt> for the transaction log. The
074: * transaction log is synchronized regularly and then restarted, so don't
075: * worry if you see the size going up and down.
076: *
077: * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
078: * @author <a href="cg@cdegroot.com">Cees de Groot</a>
079: * @version $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
080: */
081: public final class BaseRecordManager implements RecordManager {
082:
083: /**
084: * Underlying record file.
085: */
086: private RecordFile _file;
087:
088: /**
089: * Physical row identifier manager.
090: */
091: private PhysicalRowIdManager _physMgr;
092:
093: /**
094: * Logigal to Physical row identifier manager.
095: */
096: private LogicalRowIdManager _logMgr;
097:
098: /**
099: * Page manager.
100: */
101: private PageManager _pageman;
102:
103: /**
104: * Reserved slot for name directory.
105: */
106: public static final int NAME_DIRECTORY_ROOT = 0;
107:
108: /**
109: * Static debugging flag
110: */
111: public static final boolean DEBUG = false;
112:
113: /**
114: * Directory of named JDBMHashtables. This directory is a persistent
115: * directory, stored as a Hashtable. It can be retrived by using
116: * the NAME_DIRECTORY_ROOT.
117: */
118: private Map _nameDirectory;
119:
120: /**
121: * Creates a record manager for the indicated file
122: *
123: * @throws IOException when the file cannot be opened or is not
124: * a valid file content-wise.
125: */
126: public BaseRecordManager(String filename) throws IOException {
127: _file = new RecordFile(filename);
128: _pageman = new PageManager(_file);
129: _physMgr = new PhysicalRowIdManager(_file, _pageman);
130: _logMgr = new LogicalRowIdManager(_file, _pageman);
131: }
132:
133: /**
134: * Get the underlying Transaction Manager
135: */
136: public synchronized TransactionManager getTransactionManager() {
137: checkIfClosed();
138:
139: return _file.txnMgr;
140: }
141:
142: /**
143: * Switches off transactioning for the record manager. This means
144: * that a) a transaction log is not kept, and b) writes aren't
145: * synch'ed after every update. This is useful when batch inserting
146: * into a new database.
147: * <p>
148: * Only call this method directly after opening the file, otherwise
149: * the results will be undefined.
150: */
151: public synchronized void disableTransactions() {
152: checkIfClosed();
153:
154: _file.disableTransactions();
155: }
156:
157: /**
158: * Closes the record manager.
159: *
160: * @throws IOException when one of the underlying I/O operations fails.
161: */
162: public synchronized void close() throws IOException {
163: checkIfClosed();
164:
165: _pageman.close();
166: _pageman = null;
167:
168: _file.close();
169: _file = null;
170: }
171:
172: /**
173: * Inserts a new record using standard java object serialization.
174: *
175: * @param obj the object for the new record.
176: * @return the rowid for the new record.
177: * @throws IOException when one of the underlying I/O operations fails.
178: */
179: public long insert(Object obj) throws IOException {
180: return insert(obj, DefaultSerializer.INSTANCE);
181: }
182:
183: /**
184: * Inserts a new record using a custom serializer.
185: *
186: * @param obj the object for the new record.
187: * @param serializer a custom serializer
188: * @return the rowid for the new record.
189: * @throws IOException when one of the underlying I/O operations fails.
190: */
191: public synchronized long insert(Object obj, Serializer serializer)
192: throws IOException {
193: byte[] data;
194: long recid;
195: Location physRowId;
196:
197: checkIfClosed();
198:
199: data = serializer.serialize(obj);
200: physRowId = _physMgr.insert(data, 0, data.length);
201: recid = _logMgr.insert(physRowId).toLong();
202: if (DEBUG) {
203: System.out.println("BaseRecordManager.insert() recid "
204: + recid + " length " + data.length);
205: }
206: return recid;
207: }
208:
209: /**
210: * Deletes a record.
211: *
212: * @param recid the rowid for the record that should be deleted.
213: * @throws IOException when one of the underlying I/O operations fails.
214: */
215: public synchronized void delete(long recid) throws IOException {
216: checkIfClosed();
217: if (recid <= 0) {
218: throw new IllegalArgumentException(
219: "Argument 'recid' is invalid: " + recid);
220: }
221:
222: if (DEBUG) {
223: System.out.println("BaseRecordManager.delete() recid "
224: + recid);
225: }
226:
227: Location logRowId = new Location(recid);
228: Location physRowId = _logMgr.fetch(logRowId);
229: _physMgr.delete(physRowId);
230: _logMgr.delete(logRowId);
231: }
232:
233: /**
234: * Updates a record using standard java object serialization.
235: *
236: * @param recid the recid for the record that is to be updated.
237: * @param obj the new object for the record.
238: * @throws IOException when one of the underlying I/O operations fails.
239: */
240: public void update(long recid, Object obj) throws IOException {
241: update(recid, obj, DefaultSerializer.INSTANCE);
242: }
243:
244: /**
245: * Updates a record using a custom serializer.
246: *
247: * @param recid the recid for the record that is to be updated.
248: * @param obj the new object for the record.
249: * @param serializer a custom serializer
250: * @throws IOException when one of the underlying I/O operations fails.
251: */
252: public synchronized void update(long recid, Object obj,
253: Serializer serializer) throws IOException {
254: checkIfClosed();
255: if (recid <= 0) {
256: throw new IllegalArgumentException(
257: "Argument 'recid' is invalid: " + recid);
258: }
259:
260: Location logRecid = new Location(recid);
261: Location physRecid = _logMgr.fetch(logRecid);
262:
263: byte[] data = serializer.serialize(obj);
264: if (DEBUG) {
265: System.out.println("BaseRecordManager.update() recid "
266: + recid + " length " + data.length);
267: }
268:
269: Location newRecid = _physMgr.update(physRecid, data, 0,
270: data.length);
271: if (!newRecid.equals(physRecid)) {
272: _logMgr.update(logRecid, newRecid);
273: }
274: }
275:
276: /**
277: * Fetches a record using standard java object serialization.
278: *
279: * @param recid the recid for the record that must be fetched.
280: * @return the object contained in the record.
281: * @throws IOException when one of the underlying I/O operations fails.
282: */
283: public Object fetch(long recid) throws IOException {
284: return fetch(recid, DefaultSerializer.INSTANCE);
285: }
286:
287: /**
288: * Fetches a record using a custom serializer.
289: *
290: * @param recid the recid for the record that must be fetched.
291: * @param serializer a custom serializer
292: * @return the object contained in the record.
293: * @throws IOException when one of the underlying I/O operations fails.
294: */
295: public synchronized Object fetch(long recid, Serializer serializer)
296: throws IOException {
297: byte[] data;
298:
299: checkIfClosed();
300: if (recid <= 0) {
301: throw new IllegalArgumentException(
302: "Argument 'recid' is invalid: " + recid);
303: }
304: data = _physMgr.fetch(_logMgr.fetch(new Location(recid)));
305: if (DEBUG) {
306: System.out.println("BaseRecordManager.fetch() recid "
307: + recid + " length " + data.length);
308: }
309: return serializer.deserialize(data);
310: }
311:
312: /**
313: * Returns the number of slots available for "root" rowids. These slots
314: * can be used to store special rowids, like rowids that point to
315: * other rowids. Root rowids are useful for bootstrapping access to
316: * a set of data.
317: */
318: public int getRootCount() {
319: return FileHeader.NROOTS;
320: }
321:
322: /**
323: * Returns the indicated root rowid.
324: *
325: * @see #getRootCount
326: */
327: public synchronized long getRoot(int id) throws IOException {
328: checkIfClosed();
329:
330: return _pageman.getFileHeader().getRoot(id);
331: }
332:
333: /**
334: * Sets the indicated root rowid.
335: *
336: * @see #getRootCount
337: */
338: public synchronized void setRoot(int id, long rowid)
339: throws IOException {
340: checkIfClosed();
341:
342: _pageman.getFileHeader().setRoot(id, rowid);
343: }
344:
345: /**
346: * Obtain the record id of a named object. Returns 0 if named object
347: * doesn't exist.
348: */
349: public long getNamedObject(String name) throws IOException {
350: checkIfClosed();
351:
352: Map nameDirectory = getNameDirectory();
353: Long recid = (Long) nameDirectory.get(name);
354: if (recid == null) {
355: return 0;
356: }
357: return recid.longValue();
358: }
359:
360: /**
361: * Set the record id of a named object.
362: */
363: public void setNamedObject(String name, long recid)
364: throws IOException {
365: checkIfClosed();
366:
367: Map nameDirectory = getNameDirectory();
368: if (recid == 0) {
369: // remove from hashtable
370: nameDirectory.remove(name);
371: } else {
372: nameDirectory.put(name, new Long(recid));
373: }
374: saveNameDirectory(nameDirectory);
375: }
376:
377: /**
378: * Commit (make persistent) all changes since beginning of transaction.
379: */
380: public synchronized void commit() throws IOException {
381: checkIfClosed();
382:
383: _pageman.commit();
384: }
385:
386: /**
387: * Rollback (cancel) all changes since beginning of transaction.
388: */
389: public synchronized void rollback() throws IOException {
390: checkIfClosed();
391:
392: _pageman.rollback();
393: }
394:
395: /**
396: * Load name directory
397: */
398: private Map getNameDirectory() throws IOException {
399: // retrieve directory of named hashtable
400: long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
401: if (nameDirectory_recid == 0) {
402: _nameDirectory = new HashMap();
403: nameDirectory_recid = insert(_nameDirectory);
404: setRoot(NAME_DIRECTORY_ROOT, nameDirectory_recid);
405: } else {
406: _nameDirectory = (Map) fetch(nameDirectory_recid);
407: }
408: return _nameDirectory;
409: }
410:
411: private void saveNameDirectory(Map directory) throws IOException {
412: long recid = getRoot(NAME_DIRECTORY_ROOT);
413: if (recid == 0) {
414: throw new IOException("Name directory must exist");
415: }
416: update(recid, _nameDirectory);
417: }
418:
419: /**
420: * Check if RecordManager has been closed. If so, throw an
421: * IllegalStateException.
422: */
423: private void checkIfClosed() throws IllegalStateException {
424: if (_file == null) {
425: throw new IllegalStateException(
426: "RecordManager has been closed");
427: }
428: }
429: }
|