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}