001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.objectserver.persistence.sleepycat;
006:
007: import org.apache.commons.io.FileUtils;
008:
009: import com.sleepycat.bind.serial.ClassCatalog;
010: import com.sleepycat.bind.serial.StoredClassCatalog;
011: import com.sleepycat.je.Database;
012: import com.sleepycat.je.DatabaseConfig;
013: import com.sleepycat.je.DatabaseEntry;
014: import com.sleepycat.je.DatabaseException;
015: import com.sleepycat.je.Environment;
016: import com.sleepycat.je.EnvironmentConfig;
017: import com.sleepycat.je.LockMode;
018: import com.sleepycat.je.OperationStatus;
019: import com.sleepycat.je.Transaction;
020: import com.tc.logging.CustomerLogging;
021: import com.tc.logging.TCLogger;
022: import com.tc.logging.TCLogging;
023: import com.tc.util.concurrent.ThreadUtil;
024:
025: import java.io.File;
026: import java.io.IOException;
027: import java.util.ArrayList;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.LinkedList;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Properties;
034:
035: public class DBEnvironment {
036:
037: private static final TCLogger clogger = CustomerLogging
038: .getDSOGenericLogger();
039: private static final TCLogger logger = TCLogging
040: .getLogger(DBEnvironment.class);
041:
042: private static final String OBJECTID_SEQUENCE_NAME = "objectids";
043: private static final String ROOT_DB_NAME = "roots";
044: private static final String OBJECT_DB_NAME = "objects";
045: private static final String OID_DB_NAME = "oids";
046:
047: private static final String CLIENTID_SEQUENCE_NAME = "clientids";
048: private static final String CLIENT_STATE_DB_NAME = "clientstates";
049: private static final String TRANSACTION_DB_NAME = "transactions";
050: private static final String TRANSACTION_SEQUENCE_DB_NAME = "transactionsequence";
051: private static final String STRING_INDEX_DB_NAME = "stringindex";
052: private static final String CLASS_DB_NAME = "classdefinitions";
053: private static final String MAP_DB_NAME = "mapsdatabase";
054: private static final String CLUSTER_STATE_STORE = "clusterstatestore";
055:
056: private static final Object CONTROL_LOCK = new Object();
057:
058: private static final DBEnvironmentStatus STATUS_INIT = new DBEnvironmentStatus(
059: "INIT");
060: private static final DBEnvironmentStatus STATUS_ERROR = new DBEnvironmentStatus(
061: "ERROR");
062: private static final DBEnvironmentStatus STATUS_OPENING = new DBEnvironmentStatus(
063: "OPENING");
064: private static final DBEnvironmentStatus STATUS_OPEN = new DBEnvironmentStatus(
065: "OPEN");
066: private static final DBEnvironmentStatus STATUS_CLOSING = new DBEnvironmentStatus(
067: "CLOSING");
068: private static final DBEnvironmentStatus STATUS_CLOSED = new DBEnvironmentStatus(
069: "CLOSED");
070:
071: private static final DatabaseEntry CLEAN_FLAG_KEY = new DatabaseEntry(
072: new byte[] { 1 });
073: private static final byte IS_CLEAN = 1;
074: private static final byte IS_DIRTY = 2;
075: private static final long SLEEP_TIME_ON_STARTUP_ERROR = 500;
076: private static final int STARTUP_RETRY_COUNT = 5;
077:
078: private final List createdDatabases;
079: private final Map databasesByName;
080: private final File envHome;
081: private EnvironmentConfig ecfg;
082: private DatabaseConfig dbcfg;
083: private ClassCatalogWrapper catalog;
084:
085: private Environment env;
086: private Database controlDB;
087: private DBEnvironmentStatus status = STATUS_INIT;
088: private DatabaseOpenResult openResult = null;
089:
090: private final boolean paranoid;
091:
092: public DBEnvironment(boolean paranoid, File envHome)
093: throws IOException {
094: this (paranoid, envHome, new Properties());
095: }
096:
097: public DBEnvironment(boolean paranoid, File envHome,
098: Properties jeProperties) throws IOException {
099: this (new HashMap(), new LinkedList(), paranoid, envHome);
100: this .ecfg = new EnvironmentConfig(jeProperties);
101: this .ecfg.setTransactional(true);
102: this .ecfg.setAllowCreate(true);
103: this .ecfg.setReadOnly(false);
104: // this.ecfg.setTxnWriteNoSync(!paranoid);
105: this .ecfg.setTxnNoSync(!paranoid);
106: this .dbcfg = new DatabaseConfig();
107: this .dbcfg.setAllowCreate(true);
108: this .dbcfg.setTransactional(true);
109:
110: logger.info("Env config = " + this .ecfg + " DB Config = "
111: + this .dbcfg + " JE Properties = " + jeProperties);
112: }
113:
114: // For tests
115: DBEnvironment(boolean paranoid, File envHome,
116: EnvironmentConfig ecfg, DatabaseConfig dbcfg)
117: throws IOException {
118: this (new HashMap(), new LinkedList(), paranoid, envHome, ecfg,
119: dbcfg);
120: }
121:
122: // For tests
123: DBEnvironment(Map databasesByName, List createdDatabases,
124: boolean paranoid, File envHome, EnvironmentConfig ecfg,
125: DatabaseConfig dbcfg) throws IOException {
126: this (databasesByName, createdDatabases, paranoid, envHome);
127: this .ecfg = ecfg;
128: this .dbcfg = dbcfg;
129: }
130:
131: /**
132: * Note: it is not currently safe to create more than one of these instances in the same process. Sleepycat is
133: * supposed to keep more than one process from opening a writable handle to the same database, but it allows you to
134: * create more than one writable handle within the same process. So, don't do that.
135: */
136: private DBEnvironment(Map databasesByName, List createdDatabases,
137: boolean paranoid, File envHome) throws IOException {
138: this .databasesByName = databasesByName;
139: this .createdDatabases = createdDatabases;
140: this .paranoid = paranoid;
141: this .envHome = envHome;
142: FileUtils.forceMkdir(this .envHome);
143: }
144:
145: public boolean isParanoidMode() {
146: return paranoid;
147: }
148:
149: public synchronized DatabaseOpenResult open()
150: throws TCDatabaseException {
151: assertInit();
152: status = STATUS_OPENING;
153: try {
154: env = openEnvironment();
155: synchronized (CONTROL_LOCK) {
156: // XXX: Note: this doesn't guard against multiple instances in different
157: // classloaders...
158: controlDB = env.openDatabase(null, "control",
159: this .dbcfg);
160: openResult = new DatabaseOpenResult(isClean());
161: if (!openResult.isClean()) {
162: this .status = STATUS_INIT;
163: forceClose();
164: return openResult;
165: }
166: }
167: if (!this .paranoid)
168: setDirty();
169: this .catalog = new ClassCatalogWrapper(env, dbcfg);
170: newDatabase(env, OBJECTID_SEQUENCE_NAME);
171: newDatabase(env, OBJECT_DB_NAME);
172: newDatabase(env, OID_DB_NAME);
173: newDatabase(env, ROOT_DB_NAME);
174:
175: newDatabase(env, CLIENTID_SEQUENCE_NAME);
176: newDatabase(env, CLIENT_STATE_DB_NAME);
177: newDatabase(env, TRANSACTION_DB_NAME);
178: newDatabase(env, TRANSACTION_SEQUENCE_DB_NAME);
179: newDatabase(env, STRING_INDEX_DB_NAME);
180: newDatabase(env, CLASS_DB_NAME);
181: newDatabase(env, MAP_DB_NAME);
182: newDatabase(env, CLUSTER_STATE_STORE);
183: } catch (DatabaseException e) {
184: this .status = STATUS_ERROR;
185: forceClose();
186: throw new TCDatabaseException(e);
187: } catch (Error e) {
188: this .status = STATUS_ERROR;
189: forceClose();
190: throw e;
191: } catch (RuntimeException e) {
192: this .status = STATUS_ERROR;
193: forceClose();
194: throw e;
195: }
196:
197: this .status = STATUS_OPEN;
198: return openResult;
199: }
200:
201: private void cinfo(Object message) {
202: clogger.info("DB Environment: " + message);
203: }
204:
205: public synchronized void close() throws TCDatabaseException {
206: assertOpen();
207: status = STATUS_CLOSING;
208: cinfo("Closing...");
209:
210: try {
211: for (Iterator i = createdDatabases.iterator(); i.hasNext();) {
212: Database db = (Database) i.next();
213: cinfo("Closing database: " + db.getDatabaseName()
214: + "...");
215: db.close();
216: }
217: cinfo("Closing class catalog...");
218: this .catalog.close();
219: setClean();
220: if (this .controlDB != null) {
221: cinfo("Closing control database...");
222: this .controlDB.close();
223: }
224: if (this .env != null) {
225: cinfo("Closing environment...");
226: this .env.close();
227: }
228: } catch (DatabaseException de) {
229: throw new TCDatabaseException(de);
230: }
231: this .controlDB = null;
232: this .env = null;
233:
234: status = STATUS_CLOSED;
235: cinfo("Closed.");
236: }
237:
238: public synchronized boolean isOpen() {
239: return STATUS_OPEN.equals(status);
240: }
241:
242: // This is for testing and cleanup on error.
243: synchronized void forceClose() {
244: List toClose = new ArrayList(createdDatabases);
245: toClose.add(controlDB);
246: for (Iterator i = toClose.iterator(); i.hasNext();) {
247: try {
248: Database db = (Database) i.next();
249: if (db != null)
250: db.close();
251: } catch (DatabaseException e) {
252: e.printStackTrace();
253: }
254: }
255:
256: try {
257: if (this .catalog != null)
258: this .catalog.close();
259: } catch (DatabaseException e) {
260: e.printStackTrace();
261: }
262:
263: try {
264: if (env != null)
265: env.close();
266: } catch (DatabaseException e) {
267: e.printStackTrace();
268: }
269: }
270:
271: public File getEnvironmentHome() {
272: return envHome;
273: }
274:
275: public synchronized Environment getEnvironment()
276: throws TCDatabaseException {
277: assertOpen();
278: return env;
279: }
280:
281: public synchronized Database getObjectDatabase()
282: throws TCDatabaseException {
283: assertOpen();
284: return (Database) databasesByName.get(OBJECT_DB_NAME);
285: }
286:
287: public synchronized Database getOidDatabase()
288: throws TCDatabaseException {
289: assertOpen();
290: return (Database) databasesByName.get(OID_DB_NAME);
291: }
292:
293: public synchronized ClassCatalogWrapper getClassCatalogWrapper()
294: throws TCDatabaseException {
295: assertOpen();
296: return catalog;
297: }
298:
299: public synchronized Database getRootDatabase()
300: throws TCDatabaseException {
301: assertOpen();
302: return (Database) databasesByName.get(ROOT_DB_NAME);
303: }
304:
305: public synchronized Database getObjectIDDB()
306: throws TCDatabaseException {
307: assertOpen();
308: return (Database) databasesByName.get(OBJECTID_SEQUENCE_NAME);
309: }
310:
311: public Database getClientStateDatabase() throws TCDatabaseException {
312: assertOpen();
313: return (Database) databasesByName.get(CLIENT_STATE_DB_NAME);
314: }
315:
316: public Database getClientIDDatabase() throws TCDatabaseException {
317: assertOpen();
318: return (Database) databasesByName.get(CLIENTID_SEQUENCE_NAME);
319: }
320:
321: public Database getTransactionDatabase() throws TCDatabaseException {
322: assertOpen();
323: return (Database) databasesByName.get(TRANSACTION_DB_NAME);
324: }
325:
326: public Database getTransactionSequenceDatabase()
327: throws TCDatabaseException {
328: assertOpen();
329: return (Database) databasesByName
330: .get(TRANSACTION_SEQUENCE_DB_NAME);
331: }
332:
333: public Database getClassDatabase() throws TCDatabaseException {
334: assertOpen();
335: return (Database) databasesByName.get(CLASS_DB_NAME);
336: }
337:
338: public Database getMapsDatabase() throws TCDatabaseException {
339: assertOpen();
340: return (Database) databasesByName.get(MAP_DB_NAME);
341: }
342:
343: public Database getStringIndexDatabase() throws TCDatabaseException {
344: assertOpen();
345: return (Database) databasesByName.get(STRING_INDEX_DB_NAME);
346: }
347:
348: public Database getClusterStateStoreDatabase()
349: throws TCDatabaseException {
350: assertOpen();
351: return (Database) databasesByName.get(CLUSTER_STATE_STORE);
352: }
353:
354: private void assertNotError() throws TCDatabaseException {
355: if (STATUS_ERROR == status)
356: throw new TCDatabaseException(
357: "Attempt to operate on an environment in an error state.");
358: }
359:
360: private void assertInit() throws TCDatabaseException {
361: if (STATUS_INIT != status)
362: throw new DatabaseOpenException(
363: "Database environment isn't in INIT state.");
364: }
365:
366: private void assertOpening() {
367: if (STATUS_OPENING != status)
368: throw new AssertionError(
369: "Database environment should be opening but isn't");
370: }
371:
372: private void assertOpen() throws TCDatabaseException {
373: assertNotError();
374: if (STATUS_OPEN != status)
375: throw new DatabaseNotOpenException(
376: "Database environment should be open but isn't.");
377: }
378:
379: private void assertClosing() {
380: if (STATUS_CLOSING != status)
381: throw new AssertionError(
382: "Database environment should be closing but isn't");
383: }
384:
385: private boolean isClean() throws TCDatabaseException {
386: assertOpening();
387: DatabaseEntry value = new DatabaseEntry(new byte[] { 0 });
388: Transaction tx = newTransaction();
389: OperationStatus stat;
390: try {
391: stat = controlDB.get(tx, CLEAN_FLAG_KEY, value,
392: LockMode.DEFAULT);
393: tx.commit();
394: } catch (DatabaseException e) {
395: throw new TCDatabaseException(e);
396: }
397: return OperationStatus.NOTFOUND.equals(stat)
398: || (OperationStatus.SUCCESS.equals(stat) && value
399: .getData()[0] == IS_CLEAN);
400: }
401:
402: private void setDirty() throws TCDatabaseException {
403: assertOpening();
404: DatabaseEntry value = new DatabaseEntry(new byte[] { IS_DIRTY });
405: Transaction tx = newTransaction();
406: OperationStatus stat;
407: try {
408: stat = controlDB.put(tx, CLEAN_FLAG_KEY, value);
409: } catch (DatabaseException e) {
410: throw new TCDatabaseException(e);
411: }
412: if (!OperationStatus.SUCCESS.equals(stat))
413: throw new TCDatabaseException(
414: "Unexpected operation status "
415: + "trying to unset clean flag: " + stat);
416: try {
417: tx.commitSync();
418: } catch (DatabaseException e) {
419: throw new TCDatabaseException(e);
420: }
421: }
422:
423: private Transaction newTransaction() throws TCDatabaseException {
424: try {
425: Transaction tx = env.beginTransaction(null, null);
426: return tx;
427: } catch (DatabaseException de) {
428: throw new TCDatabaseException(de);
429: }
430: }
431:
432: private void setClean() throws TCDatabaseException {
433: assertClosing();
434: DatabaseEntry value = new DatabaseEntry(new byte[] { IS_CLEAN });
435: Transaction tx = newTransaction();
436: OperationStatus stat;
437: try {
438: stat = controlDB.put(tx, CLEAN_FLAG_KEY, value);
439: } catch (DatabaseException e) {
440: throw new TCDatabaseException(e);
441: }
442: if (!OperationStatus.SUCCESS.equals(stat))
443: throw new TCDatabaseException(
444: "Unexpected operation status "
445: + "trying to set clean flag: " + stat);
446: try {
447: tx.commitSync();
448: } catch (DatabaseException e) {
449: throw new TCDatabaseException(e);
450: }
451: }
452:
453: private void newDatabase(Environment e, String name)
454: throws TCDatabaseException {
455: try {
456: Database db = e.openDatabase(null, name, dbcfg);
457: createdDatabases.add(db);
458: databasesByName.put(name, db);
459: } catch (DatabaseException de) {
460: throw new TCDatabaseException(de);
461: }
462: }
463:
464: private Environment openEnvironment() throws TCDatabaseException {
465: int count = 0;
466: while (true) {
467: try {
468: return new Environment(envHome, ecfg);
469: } catch (DatabaseException dbe) {
470: if (++count <= STARTUP_RETRY_COUNT) {
471: logger.warn("Unable to open DB environment. "
472: + dbe.getMessage() + " Retrying after "
473: + SLEEP_TIME_ON_STARTUP_ERROR + " ms");
474: ThreadUtil.reallySleep(SLEEP_TIME_ON_STARTUP_ERROR);
475: } else {
476: throw new TCDatabaseException(dbe);
477: }
478: }
479: }
480: }
481:
482: private static final class DBEnvironmentStatus {
483: private final String description;
484:
485: DBEnvironmentStatus(String desc) {
486: this .description = desc;
487: }
488:
489: public String toString() {
490: return this .description;
491: }
492: }
493:
494: public static final class ClassCatalogWrapper {
495:
496: private final StoredClassCatalog catalog;
497: private boolean closed = false;
498:
499: private ClassCatalogWrapper(Environment env, DatabaseConfig cfg)
500: throws DatabaseException {
501: catalog = new StoredClassCatalog(env.openDatabase(null,
502: "java_class_catalog", cfg));
503: }
504:
505: public final ClassCatalog getClassCatalog() {
506: return this .catalog;
507: }
508:
509: synchronized void close() throws DatabaseException {
510: if (closed)
511: throw new IllegalStateException("Already closed.");
512: this .catalog.close();
513: closed = true;
514: }
515: }
516:
517: }
|