Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*-
   * See the file LICENSE for redistribution information.
   *
   * Copyright (c) 2000, 2010 Oracle and/or its affiliates.  All rights reserved.
   *
   */
  
  package com.sleepycat.bind.serial;
  
 
A ClassCatalog that is stored in a Database.

A single StoredClassCatalog object is normally used along with a set of databases that stored serialized objects.

Author(s):
Mark Hayes
See also:
Class Evolution
 
 public class StoredClassCatalog implements ClassCatalog {
 
     /*
      * Record types ([key] [data]):
      *
      * [0] [next class ID]
      * [1 / class ID] [ObjectStreamClass (class format)]
      * [2 / class name] [ClassInfo (has 8 byte class ID)]
      */
     private static final byte REC_LAST_CLASS_ID = (byte) 0;
     private static final byte REC_CLASS_FORMAT = (byte) 1;
     private static final byte REC_CLASS_INFO = (byte) 2;
 
     private static final byte[] LAST_CLASS_ID_KEY = {};
 
     private Database db;
     private HashMap<StringClassInfoclassMap;
     private LockMode writeLockMode;
     private boolean cdbMode;
     private boolean txnMode;

    
Creates a catalog based on a given database. To save resources, only a single catalog object should be used for each unique catalog database.

Parameters:
database an open database to use as the class catalog. It must be a BTREE database and must not allow duplicates.
Throws:
com.sleepycat.je.DatabaseException if an error occurs accessing the database.
java.lang.IllegalArgumentException if the database is not a BTREE database or if it configured to allow duplicates.
 
     public StoredClassCatalog(Database database)
         throws DatabaseExceptionIllegalArgumentException {
 
          = database;
         DatabaseConfig dbConfig = .getConfig();
         EnvironmentConfig envConfig = .getEnvironment().getConfig();
 
          = (DbCompat.getInitializeLocking(envConfig) ||
                          envConfig.getTransactional()) ? .
                                                        : .;
          = DbCompat.getInitializeCDB(envConfig);
          = dbConfig.getTransactional();
 
         if (!DbCompat.isTypeBtree(dbConfig)) {
             throw new IllegalArgumentException
                 ("The class catalog must be a BTREE database.");
         }
         if (DbCompat.getSortedDuplicates(dbConfig) ||
             DbCompat.getUnsortedDuplicates(dbConfig)) {
             throw new IllegalArgumentException
                ("The class catalog database must not allow duplicates.");
        }
        /*
         * Create the class format and class info maps. Note that these are not
         * synchronized, and therefore the methods that use them are
         * synchronized.
         */
         = new HashMap<StringClassInfo>();
         = new HashMap<BigIntegerObjectStreamClass>();
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
        if (dbConfig.getReadOnly()) {
            /* Check that the class ID record exists. */
            OperationStatus status = .get(nullkeydatanull);
            if (status != .) {
                throw DbCompat.unexpectedState
                    ("A read-only catalog database may not be empty");
            }
        } else {
            /* Add the initial class ID record if it doesn't exist.  */
            data.setData(new byte[1]); // zero ID
            /*
             * Query the record before writing it to the database, to avoid
             * ReplicaWriteException while opening a StoredClassCatalog on the
             * replicas.
             */
            OperationStatus status = .get(nullkeydatanull);
            if (status == .) {
                .putNoOverwrite(nullkeydata);
            }
        }
    }
    // javadoc is inherited
    public synchronized void close()
        throws DatabaseException {
        if ( != null) {
            .close();
        }
         = null;
         = null;
         = null;
    }
    // javadoc is inherited
    public synchronized byte[] getClassID(ObjectStreamClass classFormat)
        throws DatabaseExceptionClassNotFoundException {
        ClassInfo classInfo = getClassInfo(classFormat);
        return classInfo.getClassID();
    }
    // javadoc is inherited
    public synchronized ObjectStreamClass getClassFormat(byte[] classID)
        throws DatabaseExceptionClassNotFoundException {
        return getClassFormat(classIDnew DatabaseEntry());
    }

    
Internal function for getting the class format. Allows passing the DatabaseEntry object for the data, so the bytes of the class format can be examined afterwards.
    private ObjectStreamClass getClassFormat(byte[] classID,
                                             DatabaseEntry data)
        throws DatabaseExceptionClassNotFoundException {
        /* First check the map and, if found, add class info to the map. */
        BigInteger classIDObj = new BigInteger(classID);
        ObjectStreamClass classFormat = .get(classIDObj);
        if (classFormat == null) {
            /* Make the class format key. */
            byte[] keyBytes = new byte[classID.length + 1];
            keyBytes[0] = ;
            System.arraycopy(classID, 0, keyBytes, 1, classID.length);
            DatabaseEntry key = new DatabaseEntry(keyBytes);
            /* Read the class format. */
            OperationStatus status = .get(nullkeydata.);
            if (status != .) {
                throw new ClassNotFoundException("Catalog class ID not found");
            }
            try {
                ObjectInputStream ois =
                    new ObjectInputStream(
                        new ByteArrayInputStream(data.getData(),
                                                 data.getOffset(),
                                                 data.getSize()));
                classFormat = (ObjectStreamClassois.readObject();
            } catch (IOException e) {
                throw RuntimeExceptionWrapper.wrapIfNeeded(e);
            }
            /* Update the class format map. */
            .put(classIDObjclassFormat);
        }
        return classFormat;
    }

    
Get the ClassInfo for a given class name, adding it and its ObjectStreamClass to the database if they are not already present, and caching both of them using the class info and class format maps. When a class is first loaded from the database, the stored ObjectStreamClass is compared to the current ObjectStreamClass loaded by the Java class loader; if they are different, a new class ID is assigned for the current format.
    private ClassInfo getClassInfo(ObjectStreamClass classFormat)
        throws DatabaseExceptionClassNotFoundException {
        /*
         * First check for a cached copy of the class info, which if
         * present always contains the class format object.
         */
        String className = classFormat.getName();
        ClassInfo classInfo = .get(className);
        if (classInfo != null) {
            return classInfo;
        } else {
            /* Make class info key.  */
            char[] nameChars = className.toCharArray();
            byte[] keyBytes = new byte[1 + UtfOps.getByteLength(nameChars)];
            keyBytes[0] = ;
            UtfOps.charsToBytes(nameChars, 0, keyBytes, 1, nameChars.length);
            DatabaseEntry key = new DatabaseEntry(keyBytes);
            /* Read class info.  */
            DatabaseEntry data = new DatabaseEntry();
            OperationStatus status = .get(nullkeydata.);
            if (status != .) {
            
                /*
                 * Workaround for a Harmony bug that appears on Android.  The
                 * ObjectStreamClass is not properly initialized, and using it
                 * later will cause NullPointerException.  Serializing it and
                 * then deserializing it causes is to be initialized properly.
                 * [#18163]
                 */
                if (DbCompat.isDalvik()) {
                    try {
                        /* Serialize classFormat first. */
                        FastOutputStream fo = new FastOutputStream();
                        ObjectOutputStream oos = new ObjectOutputStream(fo);
                        oos.writeObject(classFormat);
                        byte[] bytes = fo.toByteArray();
                        /* Then deserialize classFormat. */
                        FastInputStream fi = new FastInputStream(bytes);
                        ObjectInputStream ois = new ObjectInputStream(fi);
                        classFormat = (ObjectStreamClassois.readObject();
                    } catch (Exception e) {
                        throw RuntimeExceptionWrapper.wrapIfNeeded(e);
                    }
                }
                
                /*
                 * Not found in the database, write class info and class
                 * format.
                 */
                classInfo = putClassInfo(new ClassInfo(), classNamekey,
                                         classFormat);
            } else {
                /*
                 * Read class info to get the class format key, then read class
                 * format.
                 */
                classInfo = new ClassInfo(data);
                DatabaseEntry formatData = new DatabaseEntry();
                ObjectStreamClass storedClassFormat =
                    getClassFormat(classInfo.getClassID(), formatData);
                /*
                 * Compare the stored class format to the current class format,
                 * and if they are different then generate a new class ID.
                 */
                if (!areClassFormatsEqual(storedClassFormat,
                                          getBytes(formatData),
                                          classFormat)) {
                    classInfo = putClassInfo(classInfoclassNamekey,
                                             classFormat);
                }
                /* Update the class info map.  */
                classInfo.setClassFormat(classFormat);
                .put(classNameclassInfo);
            }
        }
        return classInfo;
    }

    
Assign a new class ID (increment the current ID record), write the ObjectStreamClass record for this new ID, and update the ClassInfo record with the new ID also. The ClassInfo passed as an argument is the one to be updated.
    private ClassInfo putClassInfo(ClassInfo classInfo,
                                   String className,
                                   DatabaseEntry classKey,
                                   ObjectStreamClass classFormat)
        throws DatabaseException {
        /* An intent-to-write cursor is needed for CDB. */
        CursorConfig cursorConfig = null;
        if () {
            cursorConfig = new CursorConfig();
            DbCompat.setWriteCursor(cursorConfigtrue);
        }
        Cursor cursor = null;
        Transaction txn = null;
        try {
            if () {
                txn = .getEnvironment().beginTransaction(nullnull);
            }
            cursor = .openCursor(txncursorConfig);
            /* Get the current class ID. */
            DatabaseEntry key = new DatabaseEntry();
            DatabaseEntry data = new DatabaseEntry();
            OperationStatus status = cursor.getSearchKey(keydata,
                                                         );
            if (status != .) {
                throw DbCompat.unexpectedState("Class ID not initialized");
            }
            byte[] idBytes = getBytes(data);
            /* Increment the ID by one and write the updated record.  */
            idBytes = incrementID(idBytes);
            data.setData(idBytes);
            cursor.put(keydata);
            /*
             * Write the new class format record whose key is the ID just
             * assigned.
             */
            byte[] keyBytes = new byte[1 + idBytes.length];
            keyBytes[0] = ;
            System.arraycopy(idBytes, 0, keyBytes, 1, idBytes.length);
            key.setData(keyBytes);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos;
            try {
                oos = new ObjectOutputStream(baos);
                oos.writeObject(classFormat);
            } catch (IOException e) {
                throw RuntimeExceptionWrapper.wrapIfNeeded(e);
            }
            data.setData(baos.toByteArray());
            cursor.put(keydata);
            /*
             * Write the new class info record, using the key passed in; this
             * is done last so that a reader who gets the class info record
             * first will always find the corresponding class format record.
             */
            classInfo.setClassID(idBytes);
            classInfo.toDbt(data);
            cursor.put(classKeydata);
            /*
             * Update the maps before closing the cursor, so that the cursor
             * lock prevents other writers from duplicating this entry.
             */
            classInfo.setClassFormat(classFormat);
            .put(classNameclassInfo);
            .put(new BigInteger(idBytes), classFormat);
            return classInfo;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            if (txn != null) {
                txn.commit();
            }
        }
    }
    private static byte[] incrementID(byte[] key) {
        BigInteger id = new BigInteger(key);
        id = id.add(BigInteger.valueOf(1));
        return id.toByteArray();
    }

    
Holds the class format key for a class, maintains a reference to the ObjectStreamClass. Other fields can be added when we need to store more information per class.
    private static class ClassInfo implements Serializable {
        static final long serialVersionUID = 3845446969989650562L;
        private byte[] classID;
        private transient ObjectStreamClass classFormat;
        ClassInfo() {
        }
        ClassInfo(DatabaseEntry dbt) {
            byte[] data = dbt.getData();
            int len = data[0];
             = new byte[len];
            System.arraycopy(data, 1, , 0, len);
        }
        void toDbt(DatabaseEntry dbt) {
            byte[] data = new byte[1 + .];
            data[0] = (byte.;
            System.arraycopy(, 0, data, 1, .);
            dbt.setData(data);
        }
        void setClassID(byte[] classID) {
            this. = classID;
        }
        byte[] getClassID() {
            return ;
        }
        ObjectStreamClass getClassFormat() {
            return ;
        }
        void setClassFormat(ObjectStreamClass classFormat) {
            this. = classFormat;
        }
    }

    
Return whether two class formats are equal. This determines whether a new class format is needed for an object being serialized. Formats must be identical in all respects, or a new format is needed.
    private static boolean areClassFormatsEqual(ObjectStreamClass format1,
                                                byte[] format1Bytes,
                                                ObjectStreamClass format2) {
        try {
            if (format1Bytes == null) { // using cached format1 object
                format1Bytes = getObjectBytes(format1);
            }
            byte[] format2Bytes = getObjectBytes(format2);
            return java.util.Arrays.equals(format2Bytesformat1Bytes);
        } catch (IOException e) { return false; }
    }
    /*
     * We can return the same byte[] for 0 length arrays.
     */
    private static byte[] ZERO_LENGTH_BYTE_ARRAY = new byte[0];
    private static byte[] getBytes(DatabaseEntry dbt) {
        byte[] b = dbt.getData();
        if (b == null) {
            return null;
        }
        if (dbt.getOffset() == 0 && b.length == dbt.getSize()) {
            return b;
        }
        int len = dbt.getSize();
        if (len == 0) {
            return ;
        } else {
            byte[] t = new byte[len];
            System.arraycopy(bdbt.getOffset(), t, 0, t.length);
            return t;
        }
    }
    private static byte[] getObjectBytes(Object o)
        throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        return baos.toByteArray();
    }

    
For BDB JE, returns the ClassLoader property of the catalog database environment. This ensures that the Environment's ClassLoader property is used for loading all user-supplied classes.

For BDB, this method returns null because no Environment ClassLoader property is available. This method may be overridden to return a ClassLoader.

    public ClassLoader getClassLoader() {
        try {
            return DbCompat.getClassLoader(.getEnvironment());
        } catch (DatabaseException e) {
            /*
             * DatabaseException is declared to be thrown by getEnvironment in
             * DB (not JE), but this should never happen in practice.
             */
            throw new RuntimeException(e);
        }
    }
New to GrepCode? Check out our FAQ X