KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > bind > serial > StoredClassCatalog


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2000,2006 Oracle. All rights reserved.
5  *
6  * $Id: StoredClassCatalog.java,v 1.46 2006/10/30 21:14:07 bostic Exp $
7  */

8
9 package com.sleepycat.bind.serial;
10
11 import java.io.ByteArrayInputStream JavaDoc;
12 import java.io.ByteArrayOutputStream JavaDoc;
13 import java.io.IOException JavaDoc;
14 import java.io.ObjectInputStream JavaDoc;
15 import java.io.ObjectOutputStream JavaDoc;
16 import java.io.ObjectStreamClass JavaDoc;
17 import java.io.Serializable JavaDoc;
18 import java.math.BigInteger JavaDoc;
19 import java.util.HashMap JavaDoc;
20
21 import com.sleepycat.compat.DbCompat;
22 import com.sleepycat.je.Cursor;
23 import com.sleepycat.je.CursorConfig;
24 import com.sleepycat.je.Database;
25 import com.sleepycat.je.DatabaseConfig;
26 import com.sleepycat.je.DatabaseEntry;
27 import com.sleepycat.je.DatabaseException;
28 import com.sleepycat.je.EnvironmentConfig;
29 import com.sleepycat.je.LockMode;
30 import com.sleepycat.je.OperationStatus;
31 import com.sleepycat.je.Transaction;
32 import com.sleepycat.util.RuntimeExceptionWrapper;
33 import com.sleepycat.util.UtfOps;
34
35 /**
36  * A <code>ClassCatalog</code> that is stored in a <code>Database</code>.
37  *
38  * <p>A single <code>StoredClassCatalog</code> object is normally used along
39  * with a set of databases that stored serialized objects.</p>
40  *
41  * @author Mark Hayes
42  */

43 public class StoredClassCatalog implements ClassCatalog {
44
45     /*
46      * Record types ([key] [data]):
47      *
48      * [0] [next class ID]
49      * [1 / class ID] [ObjectStreamClass (class format)]
50      * [2 / class name] [ClassInfo (has 8 byte class ID)]
51      */

52     private static final byte REC_LAST_CLASS_ID = (byte) 0;
53     private static final byte REC_CLASS_FORMAT = (byte) 1;
54     private static final byte REC_CLASS_INFO = (byte) 2;
55
56     private static final byte[] LAST_CLASS_ID_KEY = {REC_LAST_CLASS_ID};
57
58     private Database db;
59     private HashMap JavaDoc classMap;
60     private HashMap JavaDoc formatMap;
61     private LockMode writeLockMode;
62     private boolean cdbMode;
63     private boolean txnMode;
64
65     /**
66      * Creates a catalog based on a given database. To save resources, only a
67      * single catalog object should be used for each unique catalog database.
68      *
69      * @param database an open database to use as the class catalog. It must
70      * be a BTREE database and must not allow duplicates.
71      *
72      * @throws DatabaseException if an error occurs accessing the database.
73      *
74      * @throws IllegalArgumentException if the database is not a BTREE database
75      * or if it configured to allow duplicates.
76      */

77     public StoredClassCatalog(Database database)
78         throws DatabaseException, IllegalArgumentException JavaDoc {
79
80         db = database;
81         DatabaseConfig dbConfig = db.getConfig();
82         EnvironmentConfig envConfig = db.getEnvironment().getConfig();
83
84         writeLockMode = (DbCompat.getInitializeLocking(envConfig) ||
85                          envConfig.getTransactional()) ? LockMode.RMW
86                                                        : LockMode.DEFAULT;
87         cdbMode = DbCompat.getInitializeCDB(envConfig);
88         txnMode = dbConfig.getTransactional();
89
90         if (!DbCompat.isTypeBtree(dbConfig)) {
91             throw new IllegalArgumentException JavaDoc(
92                     "The class catalog must be a BTREE database.");
93         }
94         if (DbCompat.getSortedDuplicates(dbConfig) ||
95             DbCompat.getUnsortedDuplicates(dbConfig)) {
96             throw new IllegalArgumentException JavaDoc(
97                     "The class catalog database must not allow duplicates.");
98         }
99
100         /*
101          * Create the class format and class info maps. Note that these are not
102          * synchronized, and therefore the methods that use them are
103          * synchronized.
104          */

105         classMap = new HashMap JavaDoc();
106         formatMap = new HashMap JavaDoc();
107
108         DatabaseEntry key = new DatabaseEntry(LAST_CLASS_ID_KEY);
109         DatabaseEntry data = new DatabaseEntry();
110         if (dbConfig.getReadOnly()) {
111             /* Check that the class ID record exists. */
112             OperationStatus status = db.get(null, key, data, null);
113             if (status != OperationStatus.SUCCESS) {
114                 throw new IllegalStateException JavaDoc
115                     ("A read-only catalog database may not be empty");
116             }
117         } else {
118             /* Add the initial class ID record if it doesn't exist. */
119             data.setData(new byte[1]); // zero ID
120
/* Use putNoOverwrite to avoid phantoms. */
121             db.putNoOverwrite(null, key, data);
122         }
123     }
124
125     // javadoc is inherited
126
public synchronized void close()
127         throws DatabaseException {
128
129         if (db != null) {
130             db.close();
131         }
132         db = null;
133         formatMap = null;
134         classMap = null;
135     }
136
137     // javadoc is inherited
138
public synchronized byte[] getClassID(ObjectStreamClass JavaDoc classFormat)
139         throws DatabaseException, ClassNotFoundException JavaDoc {
140
141         ClassInfo classInfo = getClassInfo(classFormat);
142         return classInfo.getClassID();
143     }
144
145     // javadoc is inherited
146
public synchronized ObjectStreamClass JavaDoc getClassFormat(byte[] classID)
147         throws DatabaseException, ClassNotFoundException JavaDoc {
148
149         return getClassFormat(classID, new DatabaseEntry());
150     }
151
152     /**
153      * Internal function for getting the class format. Allows passing the
154      * DatabaseEntry object for the data, so the bytes of the class format can
155      * be examined afterwards.
156      */

157     private ObjectStreamClass JavaDoc getClassFormat(byte[] classID,
158                          DatabaseEntry data)
159         throws DatabaseException, ClassNotFoundException JavaDoc {
160
161         /* First check the map and, if found, add class info to the map. */
162
163         BigInteger JavaDoc classIDObj = new BigInteger JavaDoc(classID);
164         ObjectStreamClass JavaDoc classFormat =
165             (ObjectStreamClass JavaDoc) formatMap.get(classIDObj);
166         if (classFormat == null) {
167
168             /* Make the class format key. */
169
170             byte[] keyBytes = new byte[classID.length + 1];
171             keyBytes[0] = REC_CLASS_FORMAT;
172             System.arraycopy(classID, 0, keyBytes, 1, classID.length);
173             DatabaseEntry key = new DatabaseEntry(keyBytes);
174
175             /* Read the class format. */
176
177             OperationStatus status = db.get(null, key, data, LockMode.DEFAULT);
178             if (status != OperationStatus.SUCCESS) {
179                 throw new ClassNotFoundException JavaDoc("Catalog class ID not found");
180             }
181             try {
182                 ObjectInputStream JavaDoc ois =
183                     new ObjectInputStream JavaDoc(
184                         new ByteArrayInputStream JavaDoc(data.getData(),
185                                                  data.getOffset(),
186                                                  data.getSize()));
187                 classFormat = (ObjectStreamClass JavaDoc) ois.readObject();
188             } catch (IOException JavaDoc e) {
189                 throw new RuntimeExceptionWrapper(e);
190             }
191
192             /* Update the class format map. */
193
194             formatMap.put(classIDObj, classFormat);
195         }
196         return classFormat;
197     }
198
199     /**
200      * Get the ClassInfo for a given class name, adding it and its
201      * ObjectStreamClass to the database if they are not already present, and
202      * caching both of them using the class info and class format maps. When a
203      * class is first loaded from the database, the stored ObjectStreamClass is
204      * compared to the current ObjectStreamClass loaded by the Java class
205      * loader; if they are different, a new class ID is assigned for the
206      * current format.
207      */

208     private ClassInfo getClassInfo(ObjectStreamClass JavaDoc classFormat)
209         throws DatabaseException, ClassNotFoundException JavaDoc {
210
211         /*
212          * First check for a cached copy of the class info, which if
213          * present always contains the class format object
214          */

215         String JavaDoc className = classFormat.getName();
216         ClassInfo classInfo = (ClassInfo) classMap.get(className);
217         if (classInfo != null) {
218             return classInfo;
219         } else {
220             /* Make class info key. */
221             char[] nameChars = className.toCharArray();
222             byte[] keyBytes = new byte[1 + UtfOps.getByteLength(nameChars)];
223             keyBytes[0] = REC_CLASS_INFO;
224             UtfOps.charsToBytes(nameChars, 0, keyBytes, 1, nameChars.length);
225             DatabaseEntry key = new DatabaseEntry(keyBytes);
226
227             /* Read class info. */
228             DatabaseEntry data = new DatabaseEntry();
229             OperationStatus status = db.get(null, key, data, LockMode.DEFAULT);
230             if (status != OperationStatus.SUCCESS) {
231                 /*
232                  * Not found in the database, write class info and class
233                  * format.
234                  */

235                 classInfo = putClassInfo(new ClassInfo(), className, key,
236                                          classFormat);
237             } else {
238                 /*
239                  * Read class info to get the class format key, then read class
240                  * format.
241                  */

242                 classInfo = new ClassInfo(data);
243                 DatabaseEntry formatData = new DatabaseEntry();
244                 ObjectStreamClass JavaDoc storedClassFormat =
245                     getClassFormat(classInfo.getClassID(), formatData);
246
247                 /*
248                  * Compare the stored class format to the current class format,
249                  * and if they are different then generate a new class ID.
250                  */

251                 if (!areClassFormatsEqual(storedClassFormat,
252                                           getBytes(formatData),
253                                           classFormat)) {
254                     classInfo = putClassInfo(classInfo, className, key,
255                                              classFormat);
256                 }
257
258                 /* Update the class info map. */
259                 classInfo.setClassFormat(classFormat);
260                 classMap.put(className, classInfo);
261             }
262         }
263         return classInfo;
264     }
265
266     /**
267      * Assign a new class ID (increment the current ID record), write the
268      * ObjectStreamClass record for this new ID, and update the ClassInfo
269      * record with the new ID also. The ClassInfo passed as an argument is the
270      * one to be updated.
271      */

272     private ClassInfo putClassInfo(ClassInfo classInfo,
273                    String JavaDoc className,
274                    DatabaseEntry classKey,
275                    ObjectStreamClass JavaDoc classFormat)
276         throws DatabaseException, ClassNotFoundException JavaDoc {
277
278         /* An intent-to-write cursor is needed for CDB. */
279         CursorConfig cursorConfig = null;
280         if (cdbMode) {
281             cursorConfig = new CursorConfig();
282             DbCompat.setWriteCursor(cursorConfig, true);
283         }
284         Cursor cursor = null;
285         Transaction txn = null;
286         try {
287             if (txnMode) {
288                 txn = db.getEnvironment().beginTransaction(null, null);
289             }
290             cursor = db.openCursor(txn, cursorConfig);
291
292             /* Get the current class ID. */
293             DatabaseEntry key = new DatabaseEntry(LAST_CLASS_ID_KEY);
294             DatabaseEntry data = new DatabaseEntry();
295             OperationStatus status = cursor.getSearchKey(key, data,
296                                                          writeLockMode);
297             if (status != OperationStatus.SUCCESS) {
298                 throw new IllegalStateException JavaDoc("Class ID not initialized");
299             }
300             byte[] idBytes = getBytes(data);
301
302             /* Increment the ID by one and write the updated record. */
303             idBytes = incrementID(idBytes);
304             data.setData(idBytes);
305             cursor.put(key, data);
306
307             /*
308              * Write the new class format record whose key is the ID just
309              * assigned.
310              */

311             byte[] keyBytes = new byte[1 + idBytes.length];
312             keyBytes[0] = REC_CLASS_FORMAT;
313             System.arraycopy(idBytes, 0, keyBytes, 1, idBytes.length);
314             key.setData(keyBytes);
315
316             ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
317             ObjectOutputStream JavaDoc oos;
318             try {
319                 oos = new ObjectOutputStream JavaDoc(baos);
320                 oos.writeObject(classFormat);
321             } catch (IOException JavaDoc e) {
322                 throw new RuntimeExceptionWrapper(e);
323             }
324             data.setData(baos.toByteArray());
325
326             cursor.put(key, data);
327
328             /*
329              * Write the new class info record, using the key passed in; this
330              * is done last so that a reader who gets the class info record
331              * first will always find the corresponding class format record.
332              */

333             classInfo.setClassID(idBytes);
334             classInfo.toDbt(data);
335
336             cursor.put(classKey, data);
337
338             /*
339              * Update the maps before closing the cursor, so that the cursor
340              * lock prevents other writers from duplicating this entry.
341              */

342             classInfo.setClassFormat(classFormat);
343             classMap.put(className, classInfo);
344             formatMap.put(new BigInteger JavaDoc(idBytes), classFormat);
345             return classInfo;
346         } finally {
347             if (cursor != null) {
348                 cursor.close();
349             }
350             if (txn != null) {
351                 txn.commit();
352             }
353         }
354     }
355
356     private static byte[] incrementID(byte[] key) {
357
358         BigInteger JavaDoc id = new BigInteger JavaDoc(key);
359         id = id.add(BigInteger.valueOf(1));
360         return id.toByteArray();
361     }
362
363     /**
364      * Holds the class format key for a class, maintains a reference to the
365      * ObjectStreamClass. Other fields can be added when we need to store more
366      * information per class.
367      */

368     private static class ClassInfo implements Serializable JavaDoc {
369
370         private byte[] classID;
371         private transient ObjectStreamClass JavaDoc classFormat;
372
373         ClassInfo() {
374         }
375
376         ClassInfo(DatabaseEntry dbt) {
377
378             byte[] data = dbt.getData();
379             int len = data[0];
380             classID = new byte[len];
381             System.arraycopy(data, 1, classID, 0, len);
382         }
383
384         void toDbt(DatabaseEntry dbt) {
385
386             byte[] data = new byte[1 + classID.length];
387             data[0] = (byte) classID.length;
388             System.arraycopy(classID, 0, data, 1, classID.length);
389             dbt.setData(data);
390         }
391
392         void setClassID(byte[] classID) {
393
394             this.classID = classID;
395         }
396
397         byte[] getClassID() {
398
399             return classID;
400         }
401
402         ObjectStreamClass JavaDoc getClassFormat() {
403
404             return classFormat;
405         }
406
407         void setClassFormat(ObjectStreamClass JavaDoc classFormat) {
408
409             this.classFormat = classFormat;
410         }
411     }
412
413     /**
414      * Return whether two class formats are equal. This determines whether a
415      * new class format is needed for an object being serialized. Formats must
416      * be identical in all respects, or a new format is needed.
417      */

418     private static boolean areClassFormatsEqual(ObjectStreamClass JavaDoc format1,
419                                                 byte[] format1Bytes,
420                                                 ObjectStreamClass JavaDoc format2) {
421         try {
422             if (format1Bytes == null) { // using cached format1 object
423
format1Bytes = getObjectBytes(format1);
424             }
425             byte[] format2Bytes = getObjectBytes(format2);
426             return java.util.Arrays.equals(format2Bytes, format1Bytes);
427         } catch (IOException JavaDoc e) { return false; }
428     }
429
430     /*
431      * We can return the same byte[] for 0 length arrays.
432      */

433     private static byte[] ZERO_LENGTH_BYTE_ARRAY = new byte[0];
434
435     private static byte[] getBytes(DatabaseEntry dbt) {
436         byte[] b = dbt.getData();
437         if (b == null) {
438             return null;
439         }
440         if (dbt.getOffset() == 0 && b.length == dbt.getSize()) {
441             return b;
442         }
443     int len = dbt.getSize();
444     if (len == 0) {
445         return ZERO_LENGTH_BYTE_ARRAY;
446     } else {
447         byte[] t = new byte[len];
448         System.arraycopy(b, dbt.getOffset(), t, 0, t.length);
449         return t;
450     }
451     }
452
453     private static byte[] getObjectBytes(Object JavaDoc o)
454         throws IOException JavaDoc {
455
456         ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
457         ObjectOutputStream JavaDoc oos = new ObjectOutputStream JavaDoc(baos);
458         oos.writeObject(o);
459         return baos.toByteArray();
460     }
461 }
462
Popular Tags