Main Page | Class Hierarchy | Data Structures | Directories | File List | Data Fields | Related Pages

StoredClassCatalog.java

00001 /*-
00002  * See the file LICENSE for redistribution information.
00003  *
00004  * Copyright (c) 2000-2005
00005  *      Sleepycat Software.  All rights reserved.
00006  *
00007  * $Id: StoredClassCatalog.java,v 12.1 2005/01/31 19:27:29 mark Exp $
00008  */
00009 
00010 package com.sleepycat.bind.serial;
00011 
00012 import java.io.ByteArrayInputStream;
00013 import java.io.ByteArrayOutputStream;
00014 import java.io.IOException;
00015 import java.io.ObjectInputStream;
00016 import java.io.ObjectOutputStream;
00017 import java.io.ObjectStreamClass;
00018 import java.io.Serializable;
00019 import java.math.BigInteger;
00020 import java.util.HashMap;
00021 
00022 import com.sleepycat.compat.DbCompat;
00023 import com.sleepycat.db.Cursor;
00024 import com.sleepycat.db.CursorConfig;
00025 import com.sleepycat.db.Database;
00026 import com.sleepycat.db.DatabaseConfig;
00027 import com.sleepycat.db.DatabaseEntry;
00028 import com.sleepycat.db.DatabaseException;
00029 import com.sleepycat.db.EnvironmentConfig;
00030 import com.sleepycat.db.LockMode;
00031 import com.sleepycat.db.OperationStatus;
00032 import com.sleepycat.db.Transaction;
00033 import com.sleepycat.util.RuntimeExceptionWrapper;
00034 import com.sleepycat.util.UtfOps;
00035 
00044 public class StoredClassCatalog implements ClassCatalog {
00045 
00046     /*
00047      * Record types ([key] [data]):
00048      *
00049      * [0] [next class ID]
00050      * [1 / class ID] [ObjectStreamClass (class format)]
00051      * [2 / class name] [ClassInfo (has 8 byte class ID)]
00052      */
00053     private static final byte REC_LAST_CLASS_ID = (byte) 0;
00054     private static final byte REC_CLASS_FORMAT = (byte) 1;
00055     private static final byte REC_CLASS_INFO = (byte) 2;
00056 
00057     private static final byte[] LAST_CLASS_ID_KEY = {REC_LAST_CLASS_ID};
00058 
00059     private Database db;
00060     private HashMap classMap;
00061     private HashMap formatMap;
00062     private LockMode writeLockMode;
00063     private boolean cdbMode;
00064     private boolean txnMode;
00065 
00078     public StoredClassCatalog(Database database)
00079         throws DatabaseException, IllegalArgumentException {
00080 
00081         db = database;
00082         DatabaseConfig dbConfig = db.getConfig();
00083         EnvironmentConfig envConfig = db.getEnvironment().getConfig();
00084 
00085         writeLockMode = (DbCompat.getInitializeLocking(envConfig) ||
00086                          envConfig.getTransactional()) ? LockMode.RMW
00087                                                        : LockMode.DEFAULT;
00088         cdbMode = DbCompat.getInitializeCDB(envConfig);
00089         txnMode = dbConfig.getTransactional();
00090 
00091         if (!DbCompat.isTypeBtree(dbConfig)) {
00092             throw new IllegalArgumentException(
00093                     "The class catalog must be a BTREE database.");
00094         }
00095         if (DbCompat.getSortedDuplicates(dbConfig) ||
00096             DbCompat.getUnsortedDuplicates(dbConfig)) {
00097             throw new IllegalArgumentException(
00098                     "The class catalog database must not allow duplicates.");
00099         }
00100 
00101         /*
00102          * Create the class format and class info maps. Note that these are not
00103          * synchronized, and therefore the methods that use them are
00104          * synchronized.
00105          */
00106         classMap = new HashMap();
00107         formatMap = new HashMap();
00108 
00109         DatabaseEntry key = new DatabaseEntry(LAST_CLASS_ID_KEY);
00110         DatabaseEntry data = new DatabaseEntry();
00111         if (dbConfig.getReadOnly()) {
00112             /* Check that the class ID record exists. */
00113             OperationStatus status = db.get(null, key, data, null);
00114             if (status != OperationStatus.SUCCESS) {
00115                 throw new IllegalStateException
00116                     ("A read-only catalog database may not be empty");
00117             }
00118         } else {
00119             /* Add the initial class ID record if it doesn't exist.  */
00120             data.setData(new byte[1]); // zero ID
00121             /* Use putNoOverwrite to avoid phantoms. */
00122             db.putNoOverwrite(null, key, data);
00123         }
00124     }
00125 
00126     // javadoc is inherited
00127     public synchronized void close()
00128         throws DatabaseException {
00129 
00130         if (db != null) {
00131             db.close();
00132         }
00133         db = null;
00134         formatMap = null;
00135         classMap = null;
00136     }
00137 
00138     // javadoc is inherited
00139     public synchronized byte[] getClassID(ObjectStreamClass classFormat)
00140         throws DatabaseException, ClassNotFoundException {
00141 
00142         ClassInfo classInfo = getClassInfo(classFormat);
00143         return classInfo.getClassID();
00144     }
00145 
00146     // javadoc is inherited
00147     public synchronized ObjectStreamClass getClassFormat(byte[] classID)
00148         throws DatabaseException, ClassNotFoundException {
00149 
00150         return getClassFormat(classID, new DatabaseEntry());
00151     }
00152 
00158     private ObjectStreamClass getClassFormat(byte[] classID,
00159                                              DatabaseEntry data)
00160         throws DatabaseException, ClassNotFoundException {
00161 
00162         /* First check the map and, if found, add class info to the map. */
00163 
00164         BigInteger classIDObj = new BigInteger(classID);
00165         ObjectStreamClass classFormat =
00166             (ObjectStreamClass) formatMap.get(classIDObj);
00167         if (classFormat == null) {
00168 
00169             /* Make the class format key. */
00170 
00171             byte[] keyBytes = new byte[classID.length + 1];
00172             keyBytes[0] = REC_CLASS_FORMAT;
00173             System.arraycopy(classID, 0, keyBytes, 1, classID.length);
00174             DatabaseEntry key = new DatabaseEntry(keyBytes);
00175 
00176             /* Read the class format. */
00177 
00178             OperationStatus status = db.get(null, key, data, LockMode.DEFAULT);
00179             if (status != OperationStatus.SUCCESS) {
00180                 throw new ClassNotFoundException("Catalog class ID not found");
00181             }
00182             try {
00183                 ObjectInputStream ois =
00184                     new ObjectInputStream(
00185                         new ByteArrayInputStream(data.getData(),
00186                                                  data.getOffset(),
00187                                                  data.getSize()));
00188                 classFormat = (ObjectStreamClass) ois.readObject();
00189             } catch (IOException e) {
00190                 throw new RuntimeExceptionWrapper(e);
00191             }
00192 
00193             /* Update the class format map. */
00194 
00195             formatMap.put(classIDObj, classFormat);
00196         }
00197         return classFormat;
00198     }
00199 
00209     private ClassInfo getClassInfo(ObjectStreamClass classFormat)
00210         throws DatabaseException, ClassNotFoundException {
00211 
00212         /*
00213          * First check for a cached copy of the class info, which if
00214          * present always contains the class format object
00215          */
00216         String className = classFormat.getName();
00217         ClassInfo classInfo = (ClassInfo) classMap.get(className);
00218         if (classInfo != null) {
00219             return classInfo;
00220         } else {
00221             /* Make class info key.  */
00222             char[] nameChars = className.toCharArray();
00223             byte[] keyBytes = new byte[1 + UtfOps.getByteLength(nameChars)];
00224             keyBytes[0] = REC_CLASS_INFO;
00225             UtfOps.charsToBytes(nameChars, 0, keyBytes, 1, nameChars.length);
00226             DatabaseEntry key = new DatabaseEntry(keyBytes);
00227 
00228             /* Read class info.  */
00229             DatabaseEntry data = new DatabaseEntry();
00230             OperationStatus status = db.get(null, key, data, LockMode.DEFAULT);
00231             if (status != OperationStatus.SUCCESS) {
00232                 /*
00233                  * Not found in the database, write class info and class
00234                  * format.
00235                  */
00236                 classInfo = putClassInfo(new ClassInfo(), className, key,
00237                                          classFormat);
00238             } else {
00239                 /*
00240                  * Read class info to get the class format key, then read class
00241                  * format.
00242                  */
00243                 classInfo = new ClassInfo(data);
00244                 DatabaseEntry formatData = new DatabaseEntry();
00245                 ObjectStreamClass storedClassFormat =
00246                     getClassFormat(classInfo.getClassID(), formatData);
00247 
00248                 /*
00249                  * Compare the stored class format to the current class format,
00250                  * and if they are different then generate a new class ID.
00251                  */
00252                 if (!areClassFormatsEqual(storedClassFormat,
00253                                           getBytes(formatData),
00254                                           classFormat)) {
00255                     classInfo = putClassInfo(classInfo, className, key,
00256                                              classFormat);
00257                 }
00258 
00259                 /* Update the class info map.  */
00260                 classInfo.setClassFormat(classFormat);
00261                 classMap.put(className, classInfo);
00262             }
00263         }
00264         return classInfo;
00265     }
00266 
00273     private ClassInfo putClassInfo(ClassInfo classInfo,
00274                                    String className,
00275                                    DatabaseEntry classKey,
00276                                    ObjectStreamClass classFormat)
00277         throws DatabaseException, ClassNotFoundException {
00278 
00279         /* An intent-to-write cursor is needed for CDB. */
00280         CursorConfig cursorConfig = null;
00281         if (cdbMode) {
00282             cursorConfig = new CursorConfig();
00283             DbCompat.setWriteCursor(cursorConfig, true);
00284         }
00285         Cursor cursor = null;
00286         Transaction txn = null;
00287         try {
00288             if (txnMode) {
00289                 txn = db.getEnvironment().beginTransaction(null, null);
00290             }
00291             cursor = db.openCursor(txn, cursorConfig);
00292 
00293             /* Get the current class ID. */
00294             DatabaseEntry key = new DatabaseEntry(LAST_CLASS_ID_KEY);
00295             DatabaseEntry data = new DatabaseEntry();
00296             OperationStatus status = cursor.getSearchKey(key, data,
00297                                                          writeLockMode);
00298             if (status != OperationStatus.SUCCESS) {
00299                 throw new IllegalStateException("Class ID not initialized");
00300             }
00301             byte[] idBytes = getBytes(data);
00302 
00303             /* Increment the ID by one and write the updated record.  */
00304             idBytes = incrementID(idBytes);
00305             data.setData(idBytes);
00306             cursor.put(key, data);
00307 
00308             /*
00309              * Write the new class format record whose key is the ID just
00310              * assigned.
00311              */
00312             byte[] keyBytes = new byte[1 + idBytes.length];
00313             keyBytes[0] = REC_CLASS_FORMAT;
00314             System.arraycopy(idBytes, 0, keyBytes, 1, idBytes.length);
00315             key.setData(keyBytes);
00316 
00317             ByteArrayOutputStream baos = new ByteArrayOutputStream();
00318             ObjectOutputStream oos;
00319             try {
00320                 oos = new ObjectOutputStream(baos);
00321                 oos.writeObject(classFormat);
00322             } catch (IOException e) {
00323                 throw new RuntimeExceptionWrapper(e);
00324             }
00325             data.setData(baos.toByteArray());
00326 
00327             cursor.put(key, data);
00328 
00329             /*
00330              * Write the new class info record, using the key passed in; this
00331              * is done last so that a reader who gets the class info record
00332              * first will always find the corresponding class format record.
00333              */
00334             classInfo.setClassID(idBytes);
00335             classInfo.toDbt(data);
00336 
00337             cursor.put(classKey, data);
00338 
00339             /*
00340              * Update the maps before closing the cursor, so that the cursor
00341              * lock prevents other writers from duplicating this entry.
00342              */
00343             classInfo.setClassFormat(classFormat);
00344             classMap.put(className, classInfo);
00345             formatMap.put(new BigInteger(idBytes), classFormat);
00346             return classInfo;
00347         } finally {
00348             if (cursor != null) {
00349                 cursor.close();
00350             }
00351             if (txn != null) {
00352                 txn.commit();
00353             }
00354         }
00355     }
00356 
00357     private static byte[] incrementID(byte[] key) {
00358 
00359         BigInteger id = new BigInteger(key);
00360         id = id.add(BigInteger.valueOf(1));
00361         return id.toByteArray();
00362     }
00363 
00369     private static class ClassInfo implements Serializable {
00370 
00371         private byte[] classID;
00372         private transient ObjectStreamClass classFormat;
00373 
00374         ClassInfo() {
00375         }
00376 
00377         ClassInfo(DatabaseEntry dbt) {
00378 
00379             byte[] data = dbt.getData();
00380             int len = data[0];
00381             classID = new byte[len];
00382             System.arraycopy(data, 1, classID, 0, len);
00383         }
00384 
00385         void toDbt(DatabaseEntry dbt) {
00386 
00387             byte[] data = new byte[1 + classID.length];
00388             data[0] = (byte) classID.length;
00389             System.arraycopy(classID, 0, data, 1, classID.length);
00390             dbt.setData(data);
00391         }
00392 
00393         void setClassID(byte[] classID) {
00394 
00395             this.classID = classID;
00396         }
00397 
00398         byte[] getClassID() {
00399 
00400             return classID;
00401         }
00402 
00403         ObjectStreamClass getClassFormat() {
00404 
00405             return classFormat;
00406         }
00407 
00408         void setClassFormat(ObjectStreamClass classFormat) {
00409 
00410             this.classFormat = classFormat;
00411         }
00412     }
00413 
00419     private static boolean areClassFormatsEqual(ObjectStreamClass format1,
00420                                                 byte[] format1Bytes,
00421                                                 ObjectStreamClass format2) {
00422         try {
00423             if (format1Bytes == null) { // using cached format1 object
00424                 format1Bytes = getObjectBytes(format1);
00425             }
00426             byte[] format2Bytes = getObjectBytes(format2);
00427             return java.util.Arrays.equals(format2Bytes, format1Bytes);
00428         } catch (IOException e) { return false; }
00429     }
00430 
00431     private static byte[] getBytes(DatabaseEntry dbt) {
00432         byte[] b = dbt.getData();
00433         if (b == null) {
00434             return null;
00435         }
00436         if (dbt.getOffset() == 0 && b.length == dbt.getSize()) {
00437             return b;
00438         }
00439         byte[] t = new byte[dbt.getSize()];
00440         System.arraycopy(b, dbt.getOffset(), t, 0, t.length);
00441         return t;
00442     }
00443 
00444     private static byte[] getObjectBytes(Object o)
00445         throws IOException {
00446 
00447         ByteArrayOutputStream baos = new ByteArrayOutputStream();
00448         ObjectOutputStream oos = new ObjectOutputStream(baos);
00449         oos.writeObject(o);
00450         return baos.toByteArray();
00451     }
00452 }

Generated on Sun Dec 25 12:14:30 2005 for Berkeley DB 4.4.16 by  doxygen 1.4.2