001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.backends.jeb; 028 029import java.io.Closeable; 030 031import org.forgerock.i18n.slf4j.LocalizedLogger; 032import org.opends.server.util.ServerConstants; 033import org.opends.server.util.StaticUtils; 034 035import com.sleepycat.je.*; 036 037/** 038 * This class is a wrapper around the JE database object and provides basic 039 * read and write methods for entries. 040 */ 041public abstract class DatabaseContainer implements Closeable 042{ 043 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 044 045 /** The database entryContainer. */ 046 protected final EntryContainer entryContainer; 047 /** The name of the database within the entryContainer. */ 048 protected String name; 049 050 /** The JE database configuration. */ 051 protected DatabaseConfig dbConfig; 052 /** The reference to the JE Environment. */ 053 private final Environment env; 054 /** A JE database handle opened through this database container. */ 055 private Database database; 056 057 /** 058 * Create a new DatabaseContainer object. 059 * 060 * @param name The name of the entry database. 061 * @param env The JE Environment. 062 * @param entryContainer The entryContainer of the entry database. 063 */ 064 protected DatabaseContainer(String name, Environment env, EntryContainer entryContainer) 065 { 066 this.env = env; 067 this.entryContainer = entryContainer; 068 this.name = name; 069 } 070 071 /** 072 * Opens a JE database in this database container. If the provided 073 * database configuration is transactional, a transaction will be 074 * created and used to perform the open. 075 * 076 * @throws DatabaseException if a JE database error occurs while 077 * opening the index. 078 */ 079 public void open() throws DatabaseException 080 { 081 if (dbConfig.getTransactional()) 082 { 083 // Open the database under a transaction. 084 Transaction txn = entryContainer.beginTransaction(); 085 try 086 { 087 database = env.openDatabase(txn, name, dbConfig); 088 if (logger.isTraceEnabled()) 089 { 090 logger.trace("JE database %s opened. txnid=%d", database.getDatabaseName(), txn.getId()); 091 } 092 EntryContainer.transactionCommit(txn); 093 } 094 catch (DatabaseException e) 095 { 096 EntryContainer.transactionAbort(txn); 097 throw e; 098 } 099 } 100 else 101 { 102 database = env.openDatabase(null, name, dbConfig); 103 if (logger.isTraceEnabled()) 104 { 105 logger.trace("JE database %s opened. txnid=none", database.getDatabaseName()); 106 } 107 } 108 } 109 110 /** 111 * Flush any cached database information to disk and close the 112 * database container. 113 * 114 * The database container should not be closed while other processes 115 * acquired the container. The container should not be closed 116 * while cursors handles into the database remain open, or 117 * transactions that include operations on the database have not yet 118 * been committed or aborted. 119 * 120 * The container may not be accessed again after this method is 121 * called, regardless of the method's success or failure. 122 * 123 * @throws DatabaseException if an error occurs. 124 */ 125 @Override 126 public synchronized void close() throws DatabaseException 127 { 128 if(dbConfig.getDeferredWrite()) 129 { 130 database.sync(); 131 } 132 database.close(); 133 database = null; 134 135 if(logger.isTraceEnabled()) 136 { 137 logger.trace("Closed database %s", name); 138 } 139 } 140 141 /** 142 * Replace or insert a record into a JE database, with optional debug logging. 143 * This is a simple wrapper around the JE Database.put method. 144 * @param txn The JE transaction handle, or null if none. 145 * @param key The record key. 146 * @param data The record value. 147 * @return The operation status. 148 * @throws DatabaseException If an error occurs in the JE operation. 149 */ 150 OperationStatus put(Transaction txn, DatabaseEntry key, DatabaseEntry data) throws DatabaseException 151 { 152 OperationStatus status = database.put(txn, key, data); 153 if (logger.isTraceEnabled()) 154 { 155 logger.trace(messageToLog(status, database, txn, key, data)); 156 } 157 return status; 158 } 159 160 /** 161 * Read a record from a JE database, with optional debug logging. This is a 162 * simple wrapper around the JE Database.get method. 163 * @param txn The JE transaction handle, or null if none. 164 * @param key The key of the record to be read. 165 * @param data The record value returned as output. Its byte array does not 166 * need to be initialized by the caller. 167 * @param lockMode The JE locking mode to be used for the read. 168 * @return The operation status. 169 * @throws DatabaseException If an error occurs in the JE operation. 170 */ 171 OperationStatus read(Transaction txn, DatabaseEntry key, DatabaseEntry data, LockMode lockMode) 172 throws DatabaseException 173 { 174 OperationStatus status = database.get(txn, key, data, lockMode); 175 if (logger.isTraceEnabled()) 176 { 177 logger.trace(messageToLog(status, database, txn, key, data)); 178 } 179 return status; 180 } 181 182 /** 183 * Insert a record into a JE database, with optional debug logging. This is a 184 * simple wrapper around the JE Database.putNoOverwrite method. 185 * @param txn The JE transaction handle, or null if none. 186 * @param key The record key. 187 * @param data The record value. 188 * @return The operation status. 189 * @throws DatabaseException If an error occurs in the JE operation. 190 */ 191 OperationStatus insert(Transaction txn, DatabaseEntry key, DatabaseEntry data) throws DatabaseException 192 { 193 OperationStatus status = database.putNoOverwrite(txn, key, data); 194 if (logger.isTraceEnabled()) 195 { 196 logger.trace(messageToLog(status, database, txn, key, data)); 197 } 198 return status; 199 } 200 201 /** 202 * Delete a record from a JE database, with optional debug logging. This is a 203 * simple wrapper around the JE Database.delete method. 204 * @param txn The JE transaction handle, or null if none. 205 * @param key The key of the record to be read. 206 * @return The operation status. 207 * @throws DatabaseException If an error occurs in the JE operation. 208 */ 209 OperationStatus delete(Transaction txn, DatabaseEntry key) throws DatabaseException 210 { 211 OperationStatus status = database.delete(txn, key); 212 if (logger.isTraceEnabled()) 213 { 214 logger.trace(messageToLog(status, database, txn, key, null)); 215 } 216 return status; 217 } 218 219 /** 220 * Open a JE cursor on the JE database. This is a simple wrapper around 221 * the JE Database.openCursor method. 222 * @param txn A JE database transaction to be used by the cursor, 223 * or null if none. 224 * @param cursorConfig The JE cursor configuration. 225 * @return A JE cursor. 226 * @throws DatabaseException If an error occurs while attempting to open 227 * the cursor. 228 */ 229 public Cursor openCursor(Transaction txn, CursorConfig cursorConfig) 230 throws DatabaseException 231 { 232 return database.openCursor(txn, cursorConfig); 233 } 234 235 /** 236 * Open a JE disk ordered cursor on the JE database. This is a 237 * simple wrapper around the JE Database.openCursor method. 238 * @param cursorConfig The JE disk ordered cursor configuration. 239 * @return A JE disk ordered cursor. 240 * @throws DatabaseException If an error occurs while attempting to open 241 * the cursor. 242 */ 243 public DiskOrderedCursor openCursor(DiskOrderedCursorConfig cursorConfig) 244 throws DatabaseException 245 { 246 return database.openCursor(cursorConfig); 247 } 248 249 /** 250 * Get the count of key/data pairs in the database in a JE database. 251 * This is a simple wrapper around the JE Database.count method. 252 * @return The count of key/data pairs in the database. 253 * @throws DatabaseException If an error occurs in the JE operation. 254 */ 255 public long getRecordCount() throws DatabaseException 256 { 257 long count = database.count(); 258 if (logger.isTraceEnabled()) 259 { 260 logger.trace(messageToLog(OperationStatus.SUCCESS, database, null, null, null)); 261 } 262 return count; 263 } 264 265 /** 266 * Get a string representation of this object. 267 * @return return A string representation of this object. 268 */ 269 @Override 270 public String toString() 271 { 272 return name; 273 } 274 275 /** 276 * Get the JE database name for this database container. 277 * 278 * @return JE database name for this database container. 279 */ 280 public String getName() 281 { 282 return name; 283 } 284 285 /** 286 * Preload the database into cache. 287 * 288 * @param config The preload configuration. 289 * @return Statistics about the preload process. 290 * @throws DatabaseException If an JE database error occurs 291 * during the preload. 292 */ 293 public PreloadStats preload(PreloadConfig config) 294 throws DatabaseException 295 { 296 return database.preload(config); 297 } 298 299 /** 300 * Set the JE database name to use for this container. 301 * 302 * @param name The database name to use for this container. 303 */ 304 void setName(String name) 305 { 306 this.name = name; 307 } 308 309 /** Returns the message to log given the provided information. */ 310 private String messageToLog(OperationStatus status, Database database, 311 Transaction txn, DatabaseEntry key, DatabaseEntry data) 312 { 313 StringBuilder builder = new StringBuilder(); 314 builder.append(" ("); 315 builder.append(status); 316 builder.append(")"); 317 builder.append(" db="); 318 try 319 { 320 builder.append(database.getDatabaseName()); 321 } 322 catch (DatabaseException de) 323 { 324 builder.append(de); 325 } 326 if (txn != null) 327 { 328 builder.append(" txnid="); 329 try 330 { 331 builder.append(txn.getId()); 332 } 333 catch (DatabaseException de) 334 { 335 builder.append(de); 336 } 337 } 338 else 339 { 340 builder.append(" txnid=none"); 341 } 342 343 builder.append(ServerConstants.EOL); 344 if (key != null) 345 { 346 builder.append("key:"); 347 builder.append(ServerConstants.EOL); 348 StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4); 349 } 350 351 // If the operation was successful we log the same common information 352 // plus the data 353 if (status == OperationStatus.SUCCESS && data != null) 354 { 355 builder.append("data(len="); 356 builder.append(data.getSize()); 357 builder.append("):"); 358 builder.append(ServerConstants.EOL); 359 StaticUtils.byteArrayToHexPlusAscii(builder, data.getData(), 4); 360 } 361 return builder.toString(); 362 } 363 364}