00001
00002
00003
00004
00005
00006
00007
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
00048
00049
00050
00051
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
00103
00104
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
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
00120 data.setData(new byte[1]);
00121
00122 db.putNoOverwrite(null, key, data);
00123 }
00124 }
00125
00126
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
00139 public synchronized byte[] getClassID(ObjectStreamClass classFormat)
00140 throws DatabaseException, ClassNotFoundException {
00141
00142 ClassInfo classInfo = getClassInfo(classFormat);
00143 return classInfo.getClassID();
00144 }
00145
00146
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
00163
00164 BigInteger classIDObj = new BigInteger(classID);
00165 ObjectStreamClass classFormat =
00166 (ObjectStreamClass) formatMap.get(classIDObj);
00167 if (classFormat == null) {
00168
00169
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
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
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
00214
00215
00216 String className = classFormat.getName();
00217 ClassInfo classInfo = (ClassInfo) classMap.get(className);
00218 if (classInfo != null) {
00219 return classInfo;
00220 } else {
00221
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
00229 DatabaseEntry data = new DatabaseEntry();
00230 OperationStatus status = db.get(null, key, data, LockMode.DEFAULT);
00231 if (status != OperationStatus.SUCCESS) {
00232
00233
00234
00235
00236 classInfo = putClassInfo(new ClassInfo(), className, key,
00237 classFormat);
00238 } else {
00239
00240
00241
00242
00243 classInfo = new ClassInfo(data);
00244 DatabaseEntry formatData = new DatabaseEntry();
00245 ObjectStreamClass storedClassFormat =
00246 getClassFormat(classInfo.getClassID(), formatData);
00247
00248
00249
00250
00251
00252 if (!areClassFormatsEqual(storedClassFormat,
00253 getBytes(formatData),
00254 classFormat)) {
00255 classInfo = putClassInfo(classInfo, className, key,
00256 classFormat);
00257 }
00258
00259
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
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
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
00304 idBytes = incrementID(idBytes);
00305 data.setData(idBytes);
00306 cursor.put(key, data);
00307
00308
00309
00310
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
00331
00332
00333
00334 classInfo.setClassID(idBytes);
00335 classInfo.toDbt(data);
00336
00337 cursor.put(classKey, data);
00338
00339
00340
00341
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) {
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 }