0001: /*
0002: *
0003: *
0004: * Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights
0005: * Reserved. Use is subject to license terms.
0006: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0007: *
0008: * This program is free software; you can redistribute it and/or
0009: * modify it under the terms of the GNU General Public License version
0010: * 2 only, as published by the Free Software Foundation.
0011: *
0012: * This program is distributed in the hope that it will be useful, but
0013: * WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * General Public License version 2 for more details (a copy is
0016: * included at /legal/license.txt).
0017: *
0018: * You should have received a copy of the GNU General Public License
0019: * version 2 along with this work; if not, write to the Free Software
0020: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0021: * 02110-1301 USA
0022: *
0023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0024: * Clara, CA 95054 or visit www.sun.com if you need additional
0025: * information or have any questions.
0026: *
0027: * Copyright 2000 Motorola, Inc. All Rights Reserved.
0028: * This notice does not imply publication.
0029: */
0030:
0031: package com.sun.midp.rms;
0032:
0033: import java.io.IOException;
0034: import javax.microedition.rms.*;
0035:
0036: import com.sun.midp.security.Permissions;
0037: import com.sun.midp.security.SecurityToken;
0038: import com.sun.midp.log.Logging;
0039: import com.sun.midp.log.LogChannels;
0040:
0041: /**
0042: * A class implementing a MIDP a record store.
0043: */
0044:
0045: public class RecordStoreImpl implements AbstractRecordStoreImpl {
0046: /** used to compact the records of the record store */
0047: private byte[] compactBuffer = new byte[COMPACT_BUFFER_SIZE];
0048:
0049: /**
0050: * Internal indicator for AUTHMODE_ANY with read only access
0051: * AUTHMODE_ANY_RO has a value of 2.
0052: */
0053: final static int AUTHMODE_ANY_RO = 2;
0054:
0055: /** unique id for suite that owns this record store */
0056: int suiteId;
0057:
0058: /** lock used to synchronize this record store */
0059: Object rsLock;
0060:
0061: /** data block header stored here */
0062: byte[] dbHeader;
0063:
0064: /** record store index */
0065: private RecordStoreIndex dbIndex;
0066:
0067: /** record store data */
0068: private RecordStoreFile dbFile;
0069:
0070: /**
0071: * Deletes the named record store. MIDlet suites are only allowed
0072: * to delete their own record stores. If the named record store is
0073: * open (by a MIDlet in this suite or a MIDlet in a different
0074: * MIDlet suite) when this method is called, a
0075: * RecordStoreException will be thrown. If the named record store
0076: * does not exist a RecordStoreNotFoundException will be
0077: * thrown. Calling this method does NOT result in recordDeleted
0078: * calls to any registered listeners of this RecordStore.
0079: *
0080: * @param token security token for authorization
0081: * @param suiteId ID of the MIDlet suite that owns the record store
0082: * @param recordStoreName the MIDlet suite unique record store to
0083: * delete
0084: *
0085: * @exception RecordStoreException if a record store-related
0086: * exception occurred
0087: * @exception RecordStoreNotFoundException if the record store
0088: * could not be found
0089: */
0090: public static void deleteRecordStore(SecurityToken token,
0091: int suiteId, String recordStoreName)
0092: throws RecordStoreException, RecordStoreNotFoundException {
0093:
0094: token.checkIfPermissionAllowed(Permissions.MIDP);
0095:
0096: // check if file exists and delete it
0097: if (RecordStoreUtil.exists(suiteId, recordStoreName,
0098: RecordStoreFile.DB_EXTENSION)) {
0099: boolean success = RecordStoreIndex.deleteIndex(suiteId,
0100: recordStoreName);
0101: RecordStoreUtil.deleteFile(suiteId, recordStoreName,
0102: RecordStoreFile.DB_EXTENSION);
0103:
0104: if (!success) {
0105: throw new RecordStoreException("deleteRecordStore "
0106: + "failed");
0107: }
0108: } else {
0109: throw new RecordStoreNotFoundException("deleteRecordStore "
0110: + "error: file " + "not found");
0111: }
0112: }
0113:
0114: /**
0115: * Open (and possibly create) a record store associated with the
0116: * given MIDlet suite. If this method is called by a MIDlet when
0117: * the record store is already open by a MIDlet in the MIDlet suite,
0118: * this method returns a reference to the same RecordStoreImpl object.
0119: *
0120: * @param token security token for authorization
0121: * @param suiteId ID of the MIDlet suite that owns the record store
0122: * @param recordStoreName the MIDlet suite unique name for the
0123: * record store, consisting of between one and 32 Unicode
0124: * characters inclusive.
0125: * @param createIfNecessary if true, the record store will be
0126: * created if necessary
0127: *
0128: * @return <code>RecordStore</code> object for the record store
0129: *
0130: * @exception RecordStoreException if a record store-related
0131: * exception occurred
0132: * @exception RecordStoreNotFoundException if the record store
0133: * could not be found
0134: * @exception RecordStoreFullException if the operation cannot be
0135: * completed because the record store is full
0136: * @exception IllegalArgumentException if
0137: * recordStoreName is invalid
0138: */
0139: public static RecordStoreImpl openRecordStore(SecurityToken token,
0140: int suiteId, String recordStoreName,
0141: boolean createIfNecessary) throws RecordStoreException,
0142: RecordStoreFullException, RecordStoreNotFoundException {
0143:
0144: token.checkIfPermissionAllowed(Permissions.MIDP);
0145:
0146: return new RecordStoreImpl(suiteId, recordStoreName,
0147: createIfNecessary);
0148: }
0149:
0150: /**
0151: * Changes the access mode for this RecordStore. The authorization
0152: * mode choices are:
0153: *
0154: * <ul>
0155: * <li><code>AUTHMODE_PRIVATE</code> - Only allows the MIDlet
0156: * suite that created the RecordStore to access it. This
0157: * case behaves identically to
0158: * <code>openRecordStore(recordStoreName,
0159: * createIfNecessary)</code>.</li>
0160: * <li><code>AUTHMODE_ANY</code> - Allows any MIDlet to access the
0161: * RecordStore. Note that this makes your recordStore
0162: * accessible by any other MIDlet on the device. This
0163: * could have privacy and security issues depending on
0164: * the data being shared. Please use carefully.</li>
0165: * </ul>
0166: *
0167: * <p>The owning MIDlet suite may always access the RecordStore and
0168: * always has access to write and update the store. Only the
0169: * owning MIDlet suite can change the mode of a RecordStore.</p>
0170: *
0171: * @param authmode the mode under which to check or create access.
0172: * Must be one of AUTHMODE_PRIVATE or AUTHMODE_ANY.
0173: * @param writable true if the RecordStore is to be writable by
0174: * other MIDlet suites that are granted access
0175: *
0176: * @exception RecordStoreException if a record store-related
0177: * exception occurred
0178: * @exception SecurityException if this MIDlet Suite is not
0179: * allowed to change the mode of the RecordStore
0180: * @exception IllegalArgumentException if authmode is invalid
0181: */
0182: public void setMode(int authmode, boolean writable)
0183: throws RecordStoreException {
0184:
0185: synchronized (rsLock) {
0186: int newAuthMode = authmode;
0187: if ((authmode == RecordStore.AUTHMODE_ANY)
0188: && (writable == false)) {
0189: newAuthMode = AUTHMODE_ANY_RO;
0190: }
0191:
0192: RecordStoreUtil.putInt(newAuthMode, dbHeader, RS1_AUTHMODE);
0193:
0194: try {
0195: // write out the changes to the db header
0196: dbFile.seek(RS1_AUTHMODE);
0197: dbFile.write(dbHeader, RS1_AUTHMODE, 4);
0198: // dbFile.commitWrite();
0199: } catch (java.io.IOException ioe) {
0200: throw new RecordStoreException("error writing record "
0201: + "store attributes");
0202: }
0203: }
0204: }
0205:
0206: /**
0207: * This method is called when the MIDlet requests to have the
0208: * record store closed. Note that the record store will not
0209: * actually be closed until closeRecordStore() is called as many
0210: * times as openRecordStore() was called. In other words, the
0211: * MIDlet needs to make a balanced number of close calls as open
0212: * calls before the record store is closed.
0213: *
0214: * <p>When the record store is closed, all listeners are removed
0215: * and all RecordEnumerations associated with it become invalid.
0216: * If the MIDlet attempts to perform
0217: * operations on the RecordStore object after it has been closed,
0218: * the methods will throw a RecordStoreNotOpenException.
0219: *
0220: * @exception RecordStoreNotOpenException if the record store is
0221: * not open
0222: * @exception RecordStoreException if a different record
0223: * store-related exception occurred
0224: */
0225: public void closeRecordStore() throws RecordStoreNotOpenException,
0226: RecordStoreException {
0227:
0228: synchronized (rsLock) {
0229: try {
0230: // close native fd
0231: compactRecords(); // compact before close
0232:
0233: dbFile.close();
0234:
0235: dbIndex.close();
0236: } catch (java.io.IOException ioe) {
0237: throw new RecordStoreException(
0238: "error closing .db file. " + ioe);
0239: } finally {
0240: dbFile = null;
0241: }
0242: }
0243: }
0244:
0245: /**
0246: * Returns an array of the names of record stores owned by the
0247: * MIDlet suite. Note that if the MIDlet suite does not
0248: * have any record stores, this function will return null.
0249: *
0250: * The order of RecordStore names returned is implementation
0251: * dependent.
0252: *
0253: * @param token security token for authorization
0254: * @param suiteId ID of the MIDlet suite that owns the record store
0255: *
0256: * @return array of the names of record stores owned by the
0257: * MIDlet suite. Note that if the MIDlet suite does not
0258: * have any record stores, this function will return null.
0259: */
0260: public static String[] listRecordStores(SecurityToken token,
0261: int suiteId) {
0262: token.checkIfPermissionAllowed(Permissions.MIDP);
0263:
0264: return RecordStoreFile.listRecordStores(suiteId);
0265: }
0266:
0267: /**
0268: * Get the authorization mode for this record store.
0269: *
0270: * @return authorization mode
0271: */
0272: public int getAuthMode() {
0273: return RecordStoreUtil.getInt(dbHeader, RS1_AUTHMODE);
0274: }
0275:
0276: /**
0277: * Each time a record store is modified (by
0278: * <code>addRecord</code>, <code>setRecord</code>, or
0279: * <code>deleteRecord</code> methods) its <em>version</em> is
0280: * incremented. This can be used by MIDlets to quickly tell if
0281: * anything has been modified.
0282: *
0283: * The initial version number is implementation dependent.
0284: * The increment is a positive integer greater than 0.
0285: * The version number increases only when the RecordStore is updated.
0286: *
0287: * The increment value need not be constant and may vary with each
0288: * update.
0289: *
0290: * @return the current record store version
0291: */
0292: public int getVersion() throws RecordStoreNotOpenException {
0293: return RecordStoreUtil.getInt(dbHeader, RS4_VERSION);
0294: }
0295:
0296: /**
0297: * Returns the number of records currently in the record store.
0298: *
0299: * @return the number of records currently in the record store
0300: */
0301: public int getNumRecords() {
0302: return RecordStoreUtil.getInt(dbHeader, RS3_NUM_LIVE);
0303: }
0304:
0305: /**
0306: * Returns the amount of space, in bytes, that the record store
0307: * occupies. The size returned includes any overhead associated
0308: * with the implementation, such as the data structures
0309: * used to hold the state of the record store, etc.
0310: *
0311: * @return the size of the record store in bytes
0312: */
0313: public int getSize() {
0314: return DB_HEADER_SIZE
0315: + RecordStoreUtil.getInt(dbHeader, RS6_DATA_SIZE);
0316: }
0317:
0318: /**
0319: * Returns the amount of additional room (in bytes) available for
0320: * this record store to grow. Note that this is not necessarily
0321: * the amount of extra MIDlet-level data which can be stored,
0322: * as implementations may store additional data structures with
0323: * each record to support integration with native applications,
0324: * synchronization, etc.
0325: *
0326: * @return the amount of additional room (in bytes) available for
0327: * this record store to grow
0328: */
0329: public int getSizeAvailable() {
0330: int fileSpace = dbFile.spaceAvailable(suiteId)
0331: - BLOCK_HEADER_SIZE - DB_HEADER_SIZE;
0332: int limitSpace = RMSConfig.STORAGE_SUITE_LIMIT
0333: - RecordStoreUtil.getInt(dbHeader, RS6_DATA_SIZE)
0334: - BLOCK_HEADER_SIZE - DB_HEADER_SIZE;
0335:
0336: int rv = (fileSpace < limitSpace) ? fileSpace : limitSpace;
0337: return (rv < 0) ? 0 : rv;
0338: }
0339:
0340: /**
0341: * Returns the last time the record store was modified, in the
0342: * format used by System.currentTimeMillis().
0343: *
0344: * @return the last time the record store was modified, in the
0345: * format used by System.currentTimeMillis()
0346: */
0347: public long getLastModified() {
0348: return RecordStoreUtil.getLong(dbHeader, RS5_LAST_MODIFIED);
0349: }
0350:
0351: /**
0352: * Returns the recordId of the next record to be added to the
0353: * record store. This can be useful for setting up pseudo-relational
0354: * relationships. That is, if you have two or more
0355: * record stores whose records need to refer to one another, you can
0356: * predetermine the recordIds of the records that will be created
0357: * in one record store, before populating the fields and allocating
0358: * the record in another record store. Note that the recordId returned
0359: * is only valid while the record store remains open and until a call
0360: * to <code>addRecord()</code>.
0361: *
0362: * @return the recordId of the next record to be added to the
0363: * record store
0364: */
0365: public int getNextRecordID() {
0366: return RecordStoreUtil.getInt(dbHeader, RS2_NEXT_ID);
0367: }
0368:
0369: /**
0370: * Adds a new record to the record store. The recordId for this
0371: * new record is returned. This is a blocking atomic operation.
0372: * The record is written to persistent storage before the
0373: * method returns.
0374: *
0375: * @param data the data to be stored in this record. If the record
0376: * is to have zero-length data (no data), this parameter may be
0377: * null.
0378: * @param offset the index into the data buffer of the first
0379: * relevant byte for this record
0380: * @param numBytes the number of bytes of the data buffer to use
0381: * for this record (may be zero)
0382: *
0383: * @return the recordId for the new record
0384: *
0385: * @exception RecordStoreNotOpenException if the record store is
0386: * not open
0387: * @exception RecordStoreException if a different record
0388: * store-related exception occurred
0389: * @exception RecordStoreFullException if the operation cannot be
0390: * completed because the record store has no more room
0391: * @exception SecurityException if the MIDlet has read-only access
0392: * to the RecordStore
0393: */
0394: public int addRecord(byte[] data, int offset, int numBytes)
0395: throws RecordStoreNotOpenException, RecordStoreException,
0396: RecordStoreFullException {
0397:
0398: synchronized (rsLock) {
0399: int recordId = getNextRecordID();
0400:
0401: try {
0402: // add a block for this record
0403: addBlock(recordId, data, offset, numBytes);
0404:
0405: // update the db header
0406: RecordStoreUtil.putInt(recordId + 1, dbHeader,
0407: RS2_NEXT_ID);
0408: RecordStoreUtil.putInt(getNumRecords() + 1, dbHeader,
0409: RS3_NUM_LIVE);
0410: RecordStoreUtil.putInt(getVersion() + 1, dbHeader,
0411: RS4_VERSION);
0412: RecordStoreUtil.putLong(System.currentTimeMillis(),
0413: dbHeader, RS5_LAST_MODIFIED);
0414:
0415: // write out the changes to the db header
0416: dbFile.seek(RS2_NEXT_ID);
0417: dbFile.write(dbHeader, RS2_NEXT_ID, 3 * 4 + 8);
0418: // dbFile.commitWrite();
0419: } catch (java.io.IOException ioe) {
0420: throw new RecordStoreException(
0421: "error writing new record " + "data");
0422: }
0423:
0424: // Return the new record id
0425: return recordId;
0426: }
0427: }
0428:
0429: /**
0430: * The record is deleted from the record store. The recordId for
0431: * this record is NOT reused.
0432: *
0433: * @param recordId the ID of the record to delete
0434: *
0435: * @exception RecordStoreNotOpenException if the record store is
0436: * not open
0437: * @exception InvalidRecordIDException if the recordId is invalid
0438: * @exception RecordStoreException if a general record store
0439: * exception occurs
0440: * @exception SecurityException if the MIDlet has read-only access
0441: * to the RecordStore
0442: */
0443: public void deleteRecord(int recordId)
0444: throws RecordStoreNotOpenException,
0445: InvalidRecordIDException, RecordStoreException {
0446: synchronized (rsLock) {
0447: try {
0448: byte[] header = new byte[BLOCK_HEADER_SIZE];
0449: int blockOffset = dbIndex.getRecordHeader(recordId,
0450: header);
0451:
0452: // free the block
0453: freeBlock(blockOffset, header);
0454:
0455: // update the db index
0456: dbIndex.deleteRecordIndex(recordId);
0457:
0458: // update the db header
0459: RecordStoreUtil.putInt(getNumRecords() - 1, dbHeader,
0460: RS3_NUM_LIVE);
0461: RecordStoreUtil.putInt(getVersion() + 1, dbHeader,
0462: RS4_VERSION);
0463: RecordStoreUtil.putLong(System.currentTimeMillis(),
0464: dbHeader, RS5_LAST_MODIFIED);
0465:
0466: // save the updated db header
0467: dbFile.seek(RS3_NUM_LIVE);
0468: dbFile.write(dbHeader, RS3_NUM_LIVE, 2 * 4 + 8);
0469: // dbFile.commitWrite();
0470:
0471: } catch (java.io.IOException ioe) {
0472: throw new RecordStoreException(
0473: "error updating file after"
0474: + " record deletion");
0475: }
0476: }
0477: }
0478:
0479: /**
0480: * Returns the size (in bytes) of the MIDlet data available
0481: * in the given record.
0482: *
0483: * @param recordId the ID of the record to use in this operation
0484: *
0485: * @return the size (in bytes) of the MIDlet data available
0486: * in the given record
0487: *
0488: * @exception RecordStoreNotOpenException if the record store is
0489: * not open
0490: * @exception InvalidRecordIDException if the recordId is invalid
0491: * @exception RecordStoreException if a general record store
0492: * exception occurs
0493: */
0494: public int getRecordSize(int recordId)
0495: throws RecordStoreNotOpenException,
0496: InvalidRecordIDException, RecordStoreException {
0497: synchronized (rsLock) {
0498: byte[] header = new byte[BLOCK_HEADER_SIZE];
0499:
0500: try {
0501: dbIndex.getRecordHeader(recordId, header);
0502: } catch (java.io.IOException ioe) {
0503: throw new RecordStoreException(
0504: "error reading record data");
0505: }
0506:
0507: return RecordStoreUtil.getInt(header, 4);
0508: }
0509: }
0510:
0511: /**
0512: * Returns the data stored in the given record.
0513: *
0514: * @param recordId the ID of the record to use in this operation
0515: * @param buffer the byte array in which to copy the data
0516: * @param offset the index into the buffer in which to start copying
0517: *
0518: * @exception RecordStoreNotOpenException if the record store is
0519: * not open
0520: * @exception InvalidRecordIDException if the recordId is invalid
0521: * @exception RecordStoreException if a general record store
0522: * exception occurs
0523: * @exception ArrayIndexOutOfBoundsException if the record is
0524: * larger than the buffer supplied
0525: *
0526: * @return the number of bytes copied into the buffer, starting at
0527: * index <code>offset</code>
0528: * @see #setRecord
0529: */
0530: public int getRecord(int recordId, byte[] buffer, int offset)
0531: throws RecordStoreNotOpenException,
0532: InvalidRecordIDException, RecordStoreException {
0533: synchronized (rsLock) {
0534: try {
0535: byte[] header = new byte[BLOCK_HEADER_SIZE];
0536: int blockOffset = dbIndex.getRecordHeader(recordId,
0537: header);
0538:
0539: int dataSize = RecordStoreUtil.getInt(header, 4);
0540:
0541: dbFile.seek(blockOffset + BLOCK_HEADER_SIZE);
0542: return dbFile.read(buffer, offset, dataSize);
0543: } catch (java.io.IOException ioe) {
0544: throw new RecordStoreException(
0545: "error reading record data");
0546: }
0547: }
0548: }
0549:
0550: /**
0551: * Returns a copy of the data stored in the given record.
0552: *
0553: * @param recordId the ID of the record to use in this operation
0554: *
0555: * @exception RecordStoreNotOpenException if the record store is
0556: * not open
0557: * @exception InvalidRecordIDException if the recordId is invalid
0558: * @exception RecordStoreException if a general record store
0559: * exception occurs
0560: *
0561: * @return the data stored in the given record. Note that if the
0562: * record has no data, this method will return null.
0563: * @see #setRecord
0564: */
0565: public byte[] getRecord(int recordId)
0566: throws RecordStoreNotOpenException,
0567: InvalidRecordIDException, RecordStoreException {
0568: synchronized (rsLock) {
0569: try {
0570: byte[] header = new byte[BLOCK_HEADER_SIZE];
0571: int blockOffset = dbIndex.getRecordHeader(recordId,
0572: header);
0573:
0574: int dataSize = RecordStoreUtil.getInt(header, 4);
0575: if (dataSize == 0) {
0576: return null;
0577: }
0578:
0579: byte[] buffer = new byte[dataSize];
0580:
0581: dbFile.seek(blockOffset + BLOCK_HEADER_SIZE);
0582: dbFile.read(buffer);
0583:
0584: return buffer;
0585: } catch (java.io.IOException ioe) {
0586: throw new RecordStoreException(
0587: "error reading record data");
0588: }
0589: }
0590: }
0591:
0592: /**
0593: * Sets the data in the given record to that passed in. After
0594: * this method returns, a call to <code>getRecord(int recordId)</code>
0595: * will return an array of numBytes size containing the data
0596: * supplied here.
0597: *
0598: * @param recordId the ID of the record to use in this operation
0599: * @param newData the new data to store in the record
0600: * @param offset the index into the data buffer of the first
0601: * relevant byte for this record
0602: * @param numBytes the number of bytes of the data buffer to use
0603: * for this record
0604: *
0605: * @exception RecordStoreNotOpenException if the record store is
0606: * not open
0607: * @exception InvalidRecordIDException if the recordId is invalid
0608: * @exception RecordStoreException if a general record store
0609: * exception occurs
0610: * @exception RecordStoreFullException if the operation cannot be
0611: * completed because the record store has no more room
0612: * @exception SecurityException if the MIDlet has read-only access
0613: * to the RecordStore
0614: * @see #getRecord
0615: */
0616: public void setRecord(int recordId, byte[] newData, int offset,
0617: int numBytes) throws RecordStoreNotOpenException,
0618: InvalidRecordIDException, RecordStoreException,
0619: RecordStoreFullException {
0620:
0621: synchronized (rsLock) {
0622: try {
0623: byte[] header = new byte[BLOCK_HEADER_SIZE];
0624: int blockOffset = dbIndex.getRecordHeader(recordId,
0625: header);
0626:
0627: int oldBlockSize = RecordStoreUtil
0628: .calculateBlockSize(RecordStoreUtil.getInt(
0629: header, 4));
0630: int newBlockSize = RecordStoreUtil
0631: .calculateBlockSize(numBytes);
0632: if (newBlockSize <= oldBlockSize) {
0633: // reuse the old block
0634: splitBlock(blockOffset, header, newData, offset,
0635: numBytes);
0636: } else {
0637: // free the old record data
0638: freeBlock(blockOffset, header);
0639:
0640: // add a block that contains the new record data
0641: addBlock(recordId, newData, offset, numBytes);
0642: }
0643:
0644: // update the db header
0645: RecordStoreUtil.putInt(getVersion() + 1, dbHeader,
0646: RS4_VERSION);
0647: RecordStoreUtil.putLong(System.currentTimeMillis(),
0648: dbHeader, RS5_LAST_MODIFIED);
0649:
0650: // write out the changes to the db header
0651: dbFile.seek(RS4_VERSION);
0652: dbFile.write(dbHeader, RS4_VERSION, 4 + 8);
0653: // dbFile.commitWrite();
0654: } catch (java.io.IOException ioe) {
0655: throw new RecordStoreException(
0656: "error setting record data");
0657: }
0658: }
0659: }
0660:
0661: /**
0662: * Returns all of the recordId's currently in the record store.
0663: *
0664: * @return an array of the recordId's currently in the record store
0665: * or null if the record store is closed.
0666: */
0667: public int[] getRecordIDs() {
0668: synchronized (rsLock) {
0669: return dbIndex.getRecordIDs();
0670: }
0671: }
0672:
0673: /**
0674: * Returns data base file associated with this record store
0675: *
0676: * @return data base file
0677: */
0678: public AbstractRecordStoreFile getDbFile() {
0679: return dbFile;
0680: }
0681:
0682: /**
0683: * Creates data base index file associated with this record store
0684: *
0685: * @param suiteId unique ID of the suite that owns the store
0686: * @param name a string to name the index file
0687: * @return data base index file
0688: * @exception IOException if failed to create a file
0689: */
0690: public AbstractRecordStoreFile createIndexFile(int suiteId,
0691: String name) throws IOException {
0692: return new RecordStoreFile(suiteId, name,
0693: AbstractRecordStoreFile.IDX_EXTENSION);
0694: }
0695:
0696: /**
0697: * Remove free blocks from the record store and compact records
0698: * with data into as small a space in <code>dbFile</code> as
0699: * possible. Operates from smallest to greatest offset in
0700: * <code>dbFile</code>, copying data in chunks towards the
0701: * beginning of the file, and updating record store meta-data
0702: * as it progresses.
0703: *
0704: * Warning: This is a slow operation that scales linearly
0705: * with dbFile size.
0706: *
0707: * @exception RecordStoreNotOpenException if this record store
0708: * is closed
0709: * @exception RecordStoreException if an error occurs during record
0710: * store compaction
0711: */
0712: private void compactRecords() throws IOException {
0713:
0714: // check if the db can be compacted
0715: if (RecordStoreUtil.getInt(dbHeader, RS7_FREE_SIZE) == 0) {
0716: // no free space to compact
0717: return;
0718: }
0719:
0720: byte[] header = new byte[BLOCK_HEADER_SIZE];
0721: int currentId = 0;
0722: int currentOffset = RecordStoreImpl.DB_HEADER_SIZE;
0723: int currentSize = 0;
0724: int moveUpNumBytes = 0;
0725:
0726: // search through the data blocks for a free block that is large enough
0727: while (currentOffset < getSize()) {
0728: // seek to the next offset
0729: dbFile.seek(currentOffset);
0730:
0731: // read the block header
0732: if (dbFile.read(header) != RecordStoreImpl.BLOCK_HEADER_SIZE) {
0733: // could not read the block
0734: throw new IOException();
0735: }
0736:
0737: currentId = RecordStoreUtil.getInt(header, 0);
0738: currentSize = RecordStoreUtil
0739: .calculateBlockSize(RecordStoreUtil.getInt(header,
0740: 4));
0741:
0742: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0743: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0744: "currentId = " + currentId + " currentSize = "
0745: + currentSize);
0746: }
0747:
0748: // check if this is a free block or a record
0749: if (currentId < 0) {
0750: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0751: Logging.report(Logging.INFORMATION,
0752: LogChannels.LC_RMS,
0753: "found free block at offset "
0754: + currentOffset);
0755: }
0756:
0757: // a free block, add to the moveUpNumBytes
0758: moveUpNumBytes += currentSize;
0759:
0760: // remove from the index
0761: dbIndex.removeBlock(currentOffset, header);
0762: } else {
0763: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0764: Logging.report(Logging.INFORMATION,
0765: LogChannels.LC_RMS,
0766: "found record block at offset "
0767: + currentOffset);
0768: }
0769:
0770: // a record data block, check if it needs to be moved up
0771: if (moveUpNumBytes > 0) {
0772:
0773: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0774: Logging.report(Logging.INFORMATION,
0775: LogChannels.LC_RMS, "moveUpNumBytes = "
0776: + currentOffset);
0777: }
0778:
0779: int numMoved = 0;
0780: while (numMoved < currentSize) {
0781: int curRead = currentSize - numMoved;
0782: if (curRead > COMPACT_BUFFER_SIZE) {
0783: curRead = COMPACT_BUFFER_SIZE;
0784: }
0785:
0786: dbFile.seek(currentOffset + numMoved);
0787: curRead = dbFile
0788: .read(compactBuffer, 0, curRead);
0789: if (curRead == -1) {
0790: throw new IOException();
0791: }
0792:
0793: dbFile.seek(currentOffset + numMoved
0794: - moveUpNumBytes);
0795: dbFile.write(compactBuffer, 0, curRead);
0796: // dbFile.commitWrite();
0797: numMoved += curRead;
0798: }
0799:
0800: dbIndex.updateBlock(currentOffset - moveUpNumBytes,
0801: header);
0802: }
0803: }
0804:
0805: // added the block size to the currentOffset
0806: currentOffset += currentSize;
0807: }
0808:
0809: // check if the db file can be truncated
0810: if (moveUpNumBytes > 0) {
0811: RecordStoreUtil.putInt(RecordStoreUtil.getInt(dbHeader,
0812: RS6_DATA_SIZE)
0813: - moveUpNumBytes, dbHeader, RS6_DATA_SIZE);
0814: RecordStoreUtil.putInt(0, dbHeader, RS7_FREE_SIZE);
0815: dbFile.seek(RS6_DATA_SIZE);
0816: dbFile.write(dbHeader, RS6_DATA_SIZE, 4 + 4);
0817: // dbFile.commitWrite();
0818:
0819: dbFile.truncate(getSize());
0820:
0821: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0822: Logging
0823: .report(Logging.INFORMATION,
0824: LogChannels.LC_RMS,
0825: "compactRecords, truncate to size "
0826: + getSize());
0827: }
0828: }
0829: }
0830:
0831: /**
0832: * Set the record in the block to the data passed in and adds any remaining
0833: * space to the free list.
0834: *
0835: * @param blockOffset the offset in db file to the block to split
0836: * @param header the header of the block to split
0837: * @param newData the new data to store in the record
0838: * @param offset the index into the data buffer of the first
0839: * relevant byte for this record
0840: * @param numBytes the number of bytes of the data buffer to use
0841: * for this record
0842: *
0843: * @exception IOException if there is an error accessing the db file
0844: */
0845: private void splitBlock(int blockOffset, byte[] header,
0846: byte[] newData, int offset, int numBytes)
0847: throws IOException {
0848:
0849: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0850: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0851: "splitBlock(" + RecordStoreUtil.getInt(header, 0)
0852: + ") old numBytes = "
0853: + RecordStoreUtil.getInt(header, 4)
0854: + " new numBytes=" + numBytes);
0855: }
0856:
0857: // calculate the size of the block
0858: int oldBlockSize = RecordStoreUtil
0859: .calculateBlockSize(RecordStoreUtil.getInt(header, 4));
0860:
0861: // calculate the size of the block
0862: int newBlockSize = RecordStoreUtil.calculateBlockSize(numBytes);
0863:
0864: // update the block header
0865: RecordStoreUtil.putInt(numBytes, header, 4);
0866:
0867: // seek to the location and write the block and header
0868: writeBlock(blockOffset, header, newData, offset, numBytes);
0869:
0870: // check if there is any left over free space
0871: int freeSize = oldBlockSize - newBlockSize - BLOCK_HEADER_SIZE;
0872: if (freeSize >= 0) {
0873: // update the block header and free block of extra space
0874: RecordStoreUtil.putInt(freeSize, header, 4);
0875: freeBlock(blockOffset + newBlockSize, header);
0876: }
0877: }
0878:
0879: /**
0880: * Adds a block for the record and data or sets an existing block to
0881: * the data. Splits an exiting block if needed.
0882: *
0883: * @param recordId the ID of the record to use in this operation
0884: * @param data the new data to store in the record
0885: * @param offset the index into the data buffer of the first
0886: * relevant byte for this record
0887: * @param numBytes the number of bytes of the data buffer to use
0888: * for this record
0889: *
0890: * @exception RecordStoreFullException if the operation cannot be
0891: * completed because the record store has no more room
0892: * @exception IOException if there is an error accessing the db file
0893: * @exception RecordStoreException if the new consumption is gong to
0894: * exceed the resource limit
0895: *
0896: * @return the offset in the db file of the block added
0897: */
0898: private int addBlock(int recordId, byte[] data, int offset,
0899: int numBytes) throws IOException, RecordStoreFullException,
0900: RecordStoreException {
0901:
0902: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0903: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0904: "addBlock(" + recordId + ") numBytes=" + numBytes);
0905: }
0906:
0907: // calculate the size of the block needed
0908: int blockSize = RecordStoreUtil.calculateBlockSize(numBytes);
0909: int blockOffset = 0;
0910: int freeBlocksSize = RecordStoreUtil.getInt(dbHeader,
0911: RS7_FREE_SIZE);
0912:
0913: // initialize the block header
0914: byte[] header = new byte[BLOCK_HEADER_SIZE];
0915:
0916: // check if there is the potential for a large enough free block
0917: if (freeBlocksSize >= blockSize) {
0918:
0919: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0920: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0921: "blockSize = " + blockSize + "free size = "
0922: + freeBlocksSize);
0923: }
0924:
0925: // initialize the number of bytes and search for an
0926: // available free block
0927: RecordStoreUtil.putInt(numBytes, header, 4);
0928: blockOffset = dbIndex.getFreeBlock(header);
0929: }
0930:
0931: // set the recordId in to the block header
0932: RecordStoreUtil.putInt(recordId, header, 0);
0933:
0934: if (blockOffset > 0) {
0935: // search found a block, use it
0936: splitBlock(blockOffset, header, data, offset, numBytes);
0937: } else {
0938: // search failed, add a new block to the end of the db file
0939: int spaceAvailable = getSizeAvailable();
0940: /**
0941: * spaceAvailable returns the smaller number of total space
0942: * available and the storage limit per suite. If it is less than
0943: * the block size to be added, RecordStoreFullException is thrown
0944: */
0945: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0946: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0947: "spaceAvailable = " + spaceAvailable);
0948: }
0949:
0950: // Is there room to grow the file?
0951: if (spaceAvailable < blockSize) {
0952: // Is there enough room totally: in storage and free blocks?
0953: if (spaceAvailable + freeBlocksSize < blockSize) {
0954: throw new RecordStoreFullException();
0955: }
0956: compactRecords();
0957: }
0958:
0959: blockOffset = getSize();
0960:
0961: // seek to the location and write the block and header
0962: RecordStoreUtil.putInt(numBytes, header, 4);
0963: writeBlock(blockOffset, header, data, offset, numBytes);
0964:
0965: // update the db data size
0966: RecordStoreUtil.putInt(RecordStoreUtil.getInt(dbHeader,
0967: RS6_DATA_SIZE)
0968: + blockSize, dbHeader, RS6_DATA_SIZE);
0969: dbFile.seek(RS6_DATA_SIZE);
0970: dbFile.write(dbHeader, RS6_DATA_SIZE, 4);
0971: // dbFile.commitWrite();
0972:
0973: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0974: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0975: "blockOffset = " + blockOffset);
0976: }
0977: }
0978:
0979: return blockOffset;
0980: }
0981:
0982: /**
0983: * Mark the block at the given offset in db file as free.
0984: *
0985: * @param blockOffset the offset in db file to the block to free
0986: * @param header the header of the block to free
0987: *
0988: * @exception IOException if there is an error accessing the db file
0989: */
0990: private void freeBlock(int blockOffset, byte[] header)
0991: throws IOException {
0992: int dataSize = RecordStoreUtil.getInt(header, 4);
0993:
0994: // calculate the size of the block
0995: int blockSize = RecordStoreUtil.calculateBlockSize(dataSize);
0996:
0997: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
0998: Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
0999: "freeBlock(" + blockOffset + ") size = "
1000: + blockSize);
1001: }
1002:
1003: // mark the block as free
1004: RecordStoreUtil.putInt(-1, header, 0);
1005: RecordStoreUtil
1006: .putInt(blockSize - BLOCK_HEADER_SIZE, header, 4);
1007:
1008: // save the updated block header
1009: writeBlock(blockOffset, header, null, 0, 0);
1010:
1011: // add to the db free size
1012: RecordStoreUtil.putInt(RecordStoreUtil.getInt(dbHeader,
1013: RS7_FREE_SIZE)
1014: + blockSize, dbHeader, RS7_FREE_SIZE);
1015: dbFile.seek(RS7_FREE_SIZE);
1016: dbFile.write(dbHeader, RS7_FREE_SIZE, 4);
1017: // dbFile.commitWrite();
1018: }
1019:
1020: /**
1021: * Writes a block to the db file at the given offset.
1022: *
1023: * @param blockOffset the offset in db file to write the block
1024: * @param header the header of the block to write
1025: * @param data the new data to store in the record
1026: * @param offset the index into the data buffer of the first
1027: * relevant byte for this record
1028: * @param numBytes the number of bytes of the data buffer to use
1029: * for this record
1030: *
1031: * @exception IOException if there is an error accessing the db file
1032: */
1033: private void writeBlock(int blockOffset, byte[] header,
1034: byte[] data, int offset, int numBytes) throws IOException {
1035:
1036: int remainder;
1037: dbFile.seek(blockOffset);
1038: dbFile.write(header);
1039: if (data != null && numBytes > 0) {
1040: dbFile.write(data, offset, numBytes);
1041: remainder = numBytes % BLOCK_HEADER_SIZE;
1042: if (remainder != 0) {
1043: // DB_SIGNATURE used here as meaningless pad bytes
1044: dbFile.write(DB_SIGNATURE, 0, BLOCK_HEADER_SIZE
1045: - remainder);
1046: }
1047: }
1048: // flush the writes
1049: // dbFile.commitWrite();
1050: // update the index
1051: dbIndex.updateBlock(blockOffset, header);
1052: }
1053:
1054: /**
1055: * Creates a RecordStoreImpl instance; for internal use only.
1056: * Callers from outside must use <code>openRecordStore()</code>.
1057: *
1058: * @param suiteId unique ID of the suite that owns the store
1059: * @param recordStoreName a string to name the record store
1060: * @param create if true, create the record store if it doesn't exist
1061: *
1062: * @exception RecordStoreException if something goes wrong setting up
1063: * the new RecordStore.
1064: * @exception RecordStoreNotFoundException if can't find the record store
1065: * and create is set to false.
1066: * @exception RecordStoreFullException if there is no room in storage
1067: * to create a new record store
1068: */
1069: private RecordStoreImpl(int suiteId, String recordStoreName,
1070: boolean create) throws RecordStoreException,
1071: RecordStoreNotFoundException {
1072:
1073: this .suiteId = suiteId;
1074: rsLock = new Object();
1075:
1076: boolean exists = RecordStoreUtil.exists(suiteId,
1077: recordStoreName, RecordStoreFile.DB_EXTENSION);
1078:
1079: // Check for errors between app and record store existance.
1080: if (!create && !exists) {
1081: throw new RecordStoreNotFoundException(
1082: "cannot find record " + "store file");
1083: }
1084:
1085: /*
1086: * If a new RecordStoreImpl will be created in storage,
1087: * check to see if the space required is available.
1088: */
1089: if (create && !exists) {
1090: int space = RecordStoreFile
1091: .spaceAvailableNewRecordStore(suiteId);
1092: if (space - DB_HEADER_SIZE < 0) {
1093: throw new RecordStoreFullException();
1094: }
1095: }
1096:
1097: // Create a RecordStoreFile for storing the record store.
1098: try {
1099: dbFile = new RecordStoreFile(suiteId, recordStoreName,
1100: RecordStoreFile.DB_EXTENSION);
1101:
1102: // allocate a new header
1103: dbHeader = new byte[DB_HEADER_SIZE];
1104:
1105: if (exists) {
1106: // load header
1107: dbFile.read(dbHeader);
1108:
1109: /*
1110: * Verify that the file is actually a record store
1111: * by verifying the record store "signature."
1112: */
1113: for (int i = 0; i < DB_SIGNATURE.length; i++) {
1114: if (dbHeader[i] != DB_SIGNATURE[i]) {
1115: throw new RecordStoreException(
1116: "invalid record " + "store contents");
1117: }
1118: }
1119: } else {
1120: // initialize the header
1121: for (int i = 0; i < DB_SIGNATURE.length; i++) {
1122: dbHeader[i] = DB_SIGNATURE[i];
1123: }
1124:
1125: RecordStoreUtil.putInt(1, dbHeader, RS2_NEXT_ID);
1126: RecordStoreUtil.putLong(System.currentTimeMillis(),
1127: dbHeader, RS5_LAST_MODIFIED);
1128:
1129: // write the header to the file
1130: dbFile.write(dbHeader);
1131: dbFile.commitWrite();
1132: }
1133:
1134: // create the index object
1135: dbIndex = new RecordStoreIndex(this , suiteId,
1136: recordStoreName);
1137:
1138: } catch (java.io.IOException ioe) {
1139: try {
1140: if (dbFile != null) {
1141: dbFile.close();
1142: }
1143: } catch (java.io.IOException ioe2) {
1144: // ignore exception within exception block
1145: }
1146:
1147: if (!exists) {
1148: // avoid preserving just created damaged files
1149: RecordStoreUtil.quietDeleteFile(suiteId,
1150: recordStoreName, RecordStoreFile.DB_EXTENSION);
1151: RecordStoreIndex.deleteIndex(suiteId, recordStoreName);
1152: }
1153:
1154: dbFile = null;
1155: throw new RecordStoreException(
1156: "error opening record store " + "file");
1157: }
1158: }
1159: }
|