opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -75,17 +75,17 @@ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The name of the entry database. */ public static final String ID2ENTRY_DATABASE_NAME = "id2entry"; public static final String ID2ENTRY_DATABASE_NAME = ID2ENTRY_INDEX_NAME; /** The name of the DN database. */ public static final String DN2ID_DATABASE_NAME = "dn2id"; public static final String DN2ID_DATABASE_NAME = DN2ID_INDEX_NAME; /** The name of the children index database. */ private static final String ID2CHILDREN_DATABASE_NAME = "id2children"; private static final String ID2CHILDREN_DATABASE_NAME = ID2CHILDREN_INDEX_NAME; /** The name of the subtree index database. */ private static final String ID2SUBTREE_DATABASE_NAME = "id2subtree"; private static final String ID2SUBTREE_DATABASE_NAME = ID2SUBTREE_INDEX_NAME; /** The name of the referral database. */ private static final String REFERRAL_DATABASE_NAME = "referral"; private static final String REFERRAL_DATABASE_NAME = REFERRAL_INDEX_NAME; /** The name of the state database. */ private static final String STATE_DATABASE_NAME = "state"; private static final String STATE_DATABASE_NAME = STATE_INDEX_NAME; /** The attribute used to return a search index debug string to the client. */ public static final String ATTR_DEBUG_SEARCH_INDEX = "debugsearchindex"; @@ -510,6 +510,7 @@ * * @throws DatabaseException If an error occurs in the JE database. */ @Override public void close() throws DatabaseException { // Close core indexes. opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistitBackend.java
New file @@ -0,0 +1,150 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.backends.persistit; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigException; import org.opends.server.admin.std.server.LocalDBBackendCfg; import org.opends.server.backends.pluggable.PluggableStorageBackend; import org.opends.server.core.DirectoryServer; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import org.opends.server.types.InitializationException; import com.persistit.exception.PersistitException; import com.sleepycat.je.DatabaseException; import static org.opends.messages.BackendMessages.*; import static org.opends.messages.JebMessages.*; import static org.opends.server.util.StaticUtils.*; /** * This is an implementation of a Directory Server Backend which stores entries * locally in a Persistit database. */ public class PersistitBackend extends PluggableStorageBackend<LocalDBBackendCfg> { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private PersistitRootContainer rootContainer; /** * Returns the root container. * * @return the root container */ public PersistitRootContainer getRootContainer() { return rootContainer; } /** {@inheritDoc} */ @Override public void initializeBackend() throws ConfigException, InitializationException { rootContainer = new PersistitRootContainer(this, cfg); rootContainer.open(); registerBaseDNs(cfg.getBaseDN()); cfg.addLocalDBChangeListener(this); } /** {@inheritDoc} */ @Override public void finalizeBackend() { super.finalizeBackend(); cfg.removeLocalDBChangeListener(this); deregisterBaseDNs(rootContainer.getSuffixContainers().keySet()); // Close the database. try { rootContainer.close(); rootContainer = null; } catch (DatabaseException e) { logger.traceException(e); logger.error(ERR_JEB_DATABASE_EXCEPTION, e.getMessage()); } // Log an informational message. logger.info(NOTE_BACKEND_OFFLINE, cfg.getBackendId()); } /** {@inheritDoc} */ @Override public long getEntryCount() { if (rootContainer != null) { try { return rootContainer.getEntryCount(); } catch (Exception e) { logger.traceException(e); } } return -1; } /** {@inheritDoc} */ @Override public Entry getEntry(DN entryDN) throws DirectoryException { PersistitSuffixContainer sc = rootContainer.getSuffixContainer(entryDN); try { return sc.getEntry(entryDN); } catch (PersistitException e) { logger.traceException(e); throw createDirectoryException(e); } } /** * Creates a customized DirectoryException from the DatabaseException thrown * by JE backend. * * @param e * The PersistitException to be converted. * @return DirectoryException created from exception. */ private DirectoryException createDirectoryException(PersistitException e) { // TODO JNR rename the exception to remove the "JEB" LocalizableMessage message = ERR_JEB_DATABASE_EXCEPTION.get(stackTraceToSingleLineString(e)); return new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistitDN2ID.java
New file @@ -0,0 +1,168 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.backends.persistit; import org.opends.server.backends.jeb.EntryID; import org.opends.server.backends.pluggable.KeyValueStore; import org.opends.server.backends.pluggable.NotImplementedException; import org.opends.server.backends.pluggable.SuffixContainer; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import com.persistit.Exchange; import com.persistit.Key; import com.persistit.Transaction; import com.persistit.Value; import com.persistit.exception.PersistitException; import static org.opends.server.backends.jeb.JebFormat.*; /** * Persistit implementation of the DN2ID index. */ class PersistitDN2ID implements KeyValueStore<DN, EntryID, Transaction, Void> { private static final String INDEX_NAME = SuffixContainer.ID2ENTRY_INDEX_NAME; private final String fullyQualifiedIndexName; private final PersistitSuffixContainer suffixContainer; private final int prefixRDNComponents; /** * Creates a new dn2id index. * * @param suffixContainer * the suffix container holding this dn2id index */ PersistitDN2ID(PersistitSuffixContainer suffixContainer) { this.suffixContainer = suffixContainer; this.fullyQualifiedIndexName = suffixContainer.getFullyQualifiedIndexName(INDEX_NAME); this.prefixRDNComponents = suffixContainer.getBaseDN().size(); } /** {@inheritDoc} */ @Override public void open() throws DirectoryException { try { this.suffixContainer.createTree(INDEX_NAME); } catch (PersistitException e) { throw new NotImplementedException(e); } } /** {@inheritDoc} */ @Override public boolean insert(Transaction txn, DN dn, EntryID entryID) throws DirectoryException { Exchange ex = null; try { ex = suffixContainer.getExchange(INDEX_NAME); appendAll(ex.getKey(), dnToDNKey(dn, prefixRDNComponents)); ex.getValue().put(entryID.longValue()); ex.store(); return true; } catch (PersistitException e) { throw new NotImplementedException(e); } finally { suffixContainer.releaseExchange(ex); } } private void appendAll(Key key, byte[] bytes) { if (bytes.length == 0) { // TODO JNR which is best here? // key.append(null); key.append((byte) ','); } else { // FIXME JNR this way to append is really not efficient for (byte b : bytes) { key.append(b); } } } /** {@inheritDoc} */ @Override public boolean put(Transaction txn, DN dn, EntryID entryID) throws DirectoryException { throw new NotImplementedException(); } /** {@inheritDoc} */ @Override public EntryID get(Transaction txn, DN dn, Void mode) throws DirectoryException { Exchange ex = null; try { ex = suffixContainer.getExchange(INDEX_NAME); appendAll(ex.getKey(), dnToDNKey(dn, prefixRDNComponents)); ex.fetch(); final Value value = ex.getValue(); if (value.isDefined()) { return new EntryID(value.getLong()); } return null; } catch (PersistitException e) { throw new NotImplementedException(e); } finally { suffixContainer.releaseExchange(ex); } } /** {@inheritDoc} */ @Override public boolean remove(Transaction txn, DN dn) throws DirectoryException { throw new NotImplementedException(); } /** {@inheritDoc} */ @Override public void close() { // nothing to do } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistitID2Entry.java
New file @@ -0,0 +1,160 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.backends.persistit; import org.forgerock.opendj.ldap.ByteString; import org.opends.server.api.CompressedSchema; import org.opends.server.backends.jeb.DataConfig; import org.opends.server.backends.jeb.EntryID; import org.opends.server.backends.jeb.ID2Entry; import org.opends.server.backends.pluggable.KeyValueStore; import org.opends.server.backends.pluggable.NotImplementedException; import org.opends.server.backends.pluggable.SuffixContainer; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import com.persistit.Exchange; import com.persistit.Value; import com.persistit.exception.PersistitException; /** * Persistit implementation of the ID2entry index. */ class PersistitID2Entry implements KeyValueStore<EntryID, Entry, Void, Void> { // TODO JNR use com.persistit.encoding.ObjectCache when decoding attributes? private static final String INDEX_NAME = SuffixContainer.ID2ENTRY_INDEX_NAME; private final String fullyQualifiedIndexName; private final PersistitSuffixContainer suffixContainer; /** TODO JNR remove. */ private final DataConfig dataConfig = new DataConfig(false, false, new CompressedSchema()); /** * Creates a new id2entry index. * * @param suffixContainer * the suffix container holding this id2entry index */ PersistitID2Entry(PersistitSuffixContainer suffixContainer) { this.suffixContainer = suffixContainer; this.fullyQualifiedIndexName = suffixContainer.getFullyQualifiedIndexName(INDEX_NAME); } /** {@inheritDoc} */ @Override public void open() throws DirectoryException { try { this.suffixContainer.createTree(INDEX_NAME); } catch (PersistitException e) { throw new NotImplementedException(e); } } /** {@inheritDoc} */ @Override public boolean insert(Void txn, EntryID entryID, Entry entry) throws DirectoryException { Exchange ex = null; try { ex = suffixContainer.getExchange(INDEX_NAME); ex.getKey().append(entryID.longValue()); ex.getValue().putByteArray(toByteArray(entry)); ex.store(); return true; } catch (PersistitException e) { throw new NotImplementedException(e); } finally { suffixContainer.releaseExchange(ex); } } private byte[] toByteArray(Entry entry) throws DirectoryException { ByteString bs = ID2Entry.entryToDatabase(entry, dataConfig); return bs.toByteArray(); } /** {@inheritDoc} */ @Override public boolean put(Void txn, EntryID entryID, Entry entry) throws DirectoryException { throw new NotImplementedException(); } /** {@inheritDoc} */ @Override public Entry get(Void txn, EntryID entryID, Void mode) throws DirectoryException { Exchange ex = null; try { ex = suffixContainer.getExchange(INDEX_NAME); ex.getKey().append(entryID.longValue()); ex.fetch(); final Value value = ex.getValue(); if (value.isDefined()) { ByteString bytes = ByteString.wrap(value.getByteArray()); CompressedSchema compressedSchema = dataConfig.getEntryEncodeConfig().getCompressedSchema(); return ID2Entry.entryFromDatabase(bytes, compressedSchema); } return null; } catch (Exception e) { throw new NotImplementedException(e); } finally { suffixContainer.releaseExchange(ex); } } /** {@inheritDoc} */ @Override public boolean remove(Void txn, EntryID entryID) throws DirectoryException { throw new NotImplementedException(); } /** {@inheritDoc} */ @Override public void close() { // nothing to do } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistitRootContainer.java
New file @@ -0,0 +1,316 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.backends.persistit; import java.io.File; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigException; import org.opends.server.admin.std.server.LocalDBBackendCfg; import org.opends.server.backends.pluggable.NotImplementedException; import org.opends.server.backends.pluggable.RootContainer; import org.opends.server.backends.pluggable.SuffixContainer; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.FilePermission; import org.opends.server.types.InitializationException; import com.persistit.Persistit; import com.persistit.Volume; import com.persistit.exception.PersistitException; import static org.opends.messages.ConfigMessages.*; import static org.opends.messages.JebMessages.*; import static org.opends.server.util.StaticUtils.*; /** * Persistit implementation of a {@link RootContainer}. */ class PersistitRootContainer implements RootContainer<PersistitSuffixContainer> { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private final PersistitBackend backend; private final LocalDBBackendCfg cfg; private Persistit db; /** The base DNs contained in this root container. */ private final ConcurrentHashMap<DN, PersistitSuffixContainer> suffixContainers = new ConcurrentHashMap<DN, PersistitSuffixContainer>(); /** * Constructor for this class. * * @param backend * the persistit backend * @param config * the configuration object */ PersistitRootContainer(PersistitBackend backend, LocalDBBackendCfg config) { this.backend = backend; this.cfg = config; } /** * Returns the persistit backend. * * @return the persistit backend */ public final PersistitBackend getBackend() { return backend; } /** {@inheritDoc} */ @Override public Map<DN, PersistitSuffixContainer> getSuffixContainers() { return suffixContainers; } /** * Code copied from * {@link org.opends.server.backends.jeb. RootContainer#open(com.sleepycat.je.EnvironmentConfig)} * * @throws ConfigException * If an configuration error occurs while creating the environment. */ void open() throws ConfigException { // Determine the backend database directory. File parentDirectory = getFileForPath(cfg.getDBDirectory()); File backendDirectory = new File(parentDirectory, cfg.getBackendId()); // Create the directory if it doesn't exist. if (!backendDirectory.exists()) { if (!backendDirectory.mkdirs()) { throw new ConfigException(ERR_JEB_CREATE_FAIL.get(backendDirectory.getPath())); } } // Make sure the directory is valid. else if (!backendDirectory.isDirectory()) { throw new ConfigException(ERR_JEB_DIRECTORY_INVALID.get(backendDirectory.getPath())); } FilePermission backendPermission; try { backendPermission = FilePermission.decodeUNIXMode(cfg.getDBDirectoryPermissions()); } catch (Exception e) { throw new ConfigException(ERR_CONFIG_BACKEND_MODE_INVALID.get(cfg.dn())); } // Make sure the mode will allow the server itself access to the database if (!backendPermission.isOwnerWritable() || !backendPermission.isOwnerReadable() || !backendPermission.isOwnerExecutable()) { throw new ConfigException(ERR_CONFIG_BACKEND_INSANE_MODE.get(cfg.getDBDirectoryPermissions())); } // Get the backend database backendDirectory permissions and apply if (FilePermission.canSetPermissions()) { try { if (!FilePermission.setPermissions(backendDirectory, backendPermission)) { logger.warn(WARN_JEB_UNABLE_SET_PERMISSIONS, backendPermission, backendDirectory); } } catch (Exception e) { // Log an warning that the permissions were not set. logger.warn(WARN_JEB_SET_PERMISSIONS_FAILED, backendDirectory, e); } } openAndRegisterSuffixContainers(backendDirectory); } private void openAndRegisterSuffixContainers(File backendDirectory) { DN[] baseDNs = backend.getBaseDNs(); final Properties properties = new Properties(); properties.setProperty("datapath", backendDirectory.toString()); properties.setProperty("logpath", backendDirectory + "/log"); properties.setProperty("logfile", "${logpath}/dj_${timestamp}.log"); properties.setProperty("buffer.count.16384", "64K"); for (int i = 0; i < baseDNs.length; i++) { // TODO JNR in the replace() down below, // persistit does not like commas and does not know how to escape them final String baseDN = toVolumeName(baseDNs[i]); properties.setProperty("volume." + (i + 1), "${datapath}/" + baseDN + ",create,pageSize:16K," + "initialSize:50M,extensionSize:1M,maximumSize:10G"); } properties.setProperty("journalpath", "${datapath}/dj_journal"); try { db = new Persistit(properties); db.initialize(); openAndRegisterSuffixContainers(baseDNs); if (logger.isTraceEnabled()) { logger.trace("Persistit (%s) environment opened with the following config: %n%s", Persistit.VERSION, properties); // Get current size of heap in bytes long heapSize = Runtime.getRuntime().totalMemory(); // Get maximum size of heap in bytes. The heap cannot grow beyond this size. // Any attempt will result in an OutOfMemoryException. long heapMaxSize = Runtime.getRuntime().maxMemory(); // Get amount of free memory within the heap in bytes. This size will increase // after garbage collection and decrease as new objects are created. long heapFreeSize = Runtime.getRuntime().freeMemory(); logger.trace("Current size of heap: %d bytes", heapSize); logger.trace("Max size of heap: %d bytes", heapMaxSize); logger.trace("Free memory in heap: %d bytes", heapFreeSize); } } catch (Exception e) { throw new NotImplementedException(e); } } private void openAndRegisterSuffixContainers(DN[] baseDNs) throws PersistitException, InitializationException, ConfigException, DirectoryException { for (DN baseDN : baseDNs) { PersistitSuffixContainer sc = openSuffixContainer(baseDN, null); registerSuffixContainer(baseDN, sc); } } private PersistitSuffixContainer openSuffixContainer(DN baseDN, String name) throws PersistitException, DirectoryException { String databasePrefix; if (name == null || name.equals("")) { databasePrefix = baseDN.toNormalizedString(); } else { databasePrefix = name; } final Volume volume = db.loadVolume(toVolumeName(baseDN)); final PersistitSuffixContainer suffixContainer = new PersistitSuffixContainer(baseDN, databasePrefix, this, db, volume); suffixContainer.open(); return suffixContainer; } private String toVolumeName(DN dn) { return dn.toString().replace(",", "_"); } private void registerSuffixContainer(DN baseDN, PersistitSuffixContainer suffixContainer) throws InitializationException { PersistitSuffixContainer sc = suffixContainers.get(baseDN); if (sc != null) { // If an entry container for this baseDN is already opened we do not allow // another to be opened. throw new InitializationException(ERR_JEB_ENTRY_CONTAINER_ALREADY_REGISTERED.get(sc.getIndexPrefix(), baseDN)); } suffixContainers.put(baseDN, suffixContainer); } /** {@inheritDoc} */ @Override public void close() { for (PersistitSuffixContainer sc : suffixContainers.values()) { sc.close(); } } /** {@inheritDoc} */ @Override public long getEntryCount() { boolean couldDetermineAllCounts = true; long result = 0; for (SuffixContainer suffixContainer : getSuffixContainers().values()) { final long suffixCount = suffixContainer.getEntryCount(); if (suffixCount != -1) { result += suffixCount; } else { couldDetermineAllCounts = false; } } return couldDetermineAllCounts ? result : -1; } /** * Return the suffix container holding a specific DN. * * @param entryDN * The DN for which to return the suffix container. * @return The suffix container holding the DN */ PersistitSuffixContainer getSuffixContainer(DN entryDN) { PersistitSuffixContainer sc = null; DN nodeDN = entryDN; while (sc == null && nodeDN != null) { sc = suffixContainers.get(nodeDN); if (sc == null) { nodeDN = nodeDN.getParentDNInSuffix(); } } return sc; } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/persistit/PersistitSuffixContainer.java
New file @@ -0,0 +1,308 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.backends.persistit; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.server.api.EntryCache; import org.opends.server.backends.jeb.EntryID; import org.opends.server.backends.pluggable.SuffixContainer; import org.opends.server.core.DirectoryServer; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import com.persistit.Exchange; import com.persistit.Persistit; import com.persistit.Volume; import com.persistit.exception.PersistitException; import com.sleepycat.je.DatabaseException; import static org.opends.messages.JebMessages.*; /** * Persistit implementation of a {@link SuffixContainer}. */ class PersistitSuffixContainer implements SuffixContainer { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The baseDN handled by this suffix container. */ private final DN baseDN; /** * Prevents name clashes for common indexes (like id2entry) across multiple * suffixes. For example when a root container contains multiple suffixes. */ private final String indexPrefix; private final PersistitRootContainer rootContainer; private final Persistit db; private final Volume volume; private PersistitID2Entry id2entry; private PersistitDN2ID dn2id; /** * Constructor for this class. * * @param baseDN * The baseDN handled by this suffix container * @param indexPrefix * the prefix to use for indexes name * @param rootContainer * the persisit root container * @param db * the persisit database * @param volume * the volume where indexes will be created */ PersistitSuffixContainer(DN baseDN, String indexPrefix, PersistitRootContainer rootContainer, Persistit db, Volume volume) { this.baseDN = baseDN; this.indexPrefix = indexPrefix; this.rootContainer = rootContainer; this.db = db; this.volume = volume; } /** * Opens the entryContainer for reading and writing. * * @throws DirectoryException * If an error occurs while opening the suffix container. */ void open() throws DirectoryException { id2entry = new PersistitID2Entry(this); id2entry.open(); dn2id = new PersistitDN2ID(this); dn2id.open(); } /** {@inheritDoc} */ @Override public void close() { dn2id.close(); id2entry.close(); } /** {@inheritDoc} */ @Override public DN getBaseDN() { return baseDN; } /** * Returns the index name prefix. * * @return the index name prefix */ public String getIndexPrefix() { return indexPrefix; } /** {@inheritDoc} */ @Override public long getEntryCount() { // FIXME To be implemented // JNR: I have not found a suitable API to return this value return -1; } /** * Returns the id2entry index. * * @return the id2entry index */ public PersistitID2Entry getID2Entry() { return id2entry; } /** * Creates a {@link Tree} for the fully qualified index name. * * @param fullyQualifiedIndexName * The fully qualified index name * @throws PersistitException * if a database exception happens */ void createTree(String fullyQualifiedIndexName) throws PersistitException { volume.getTree(fullyQualifiedIndexName, true); } /** * Returns a new {@link Exchange} working on the index provided via its fully * qualified name. * <p> * Note: exchanges obtained with this method must be released by calling * {@link #releaseExchange(Exchange)}. * * @param fullyQualifiedIndexName * The fully qualified index name * @return the exchange * @throws PersistitException * if a database exception happens */ final Exchange getExchange(String fullyQualifiedIndexName) throws PersistitException { return db.getExchange(volume, fullyQualifiedIndexName, false); } /** * Returns the fully qualified index name for this simple index name. * <p> * e.g. * * <pre> * PersistitSuffixContainer sc = ...; // initialize the suffix container * assertEquals(sc.getIndexPrefix(), "dccom"); * assertEquals(sc.getFullyQualifiedIndexName("id2entry"), "dccom_id2entry"); * </pre> * * @param simpleIndexName * the simple index name to convert to a fully qualified index name * @return the fully qualified index name */ public String getFullyQualifiedIndexName(String simpleIndexName) { return indexPrefix + "_" + simpleIndexName; } /** * Releases the provided exchange. * * @param exchange * the exchange to release */ final void releaseExchange(Exchange exchange) { if (exchange != null) { db.releaseExchange(exchange); } } /** * Indicates whether an entry with the specified DN exists. * * @param entryDN * The DN of the entry for which to determine existence. * @return <CODE>true</CODE> if the specified entry exists, or * <CODE>false</CODE> if it does not. * @throws DirectoryException * If a problem occurs while trying to make the determination. */ public boolean entryExists(DN entryDN) throws DirectoryException { // Try the entry cache first. EntryCache<?> entryCache = DirectoryServer.getEntryCache(); if (entryCache != null && entryCache.containsEntry(entryDN)) { return true; } try { // Read the ID from dn2id. EntryID id = dn2id.get(null, entryDN, null); return id != null; } catch (DatabaseException e) { logger.traceException(e); return false; } } /** * Retrieves and returns the entry associated to the provided DN. * * @param entryDN * the DN of the entry to return * @return the entry associated to the provided DN * @throws DirectoryException * if a directory exception happens * @throws PersistitException * if a database exception happens */ Entry getEntry(DN entryDN) throws DirectoryException, PersistitException { EntryCache<?> entryCache = DirectoryServer.getEntryCache(); // Try the entry cache first. Entry entry = null; if (entryCache != null) { entry = entryCache.getEntry(entryDN); } if (entry == null) { // Read dn2id. EntryID entryID = dn2id.get(null, entryDN, null); if (entryID == null) { // The entryDN does not exist. // Check for referral entries above the target entry. // TODO JNR uncomment next line // dn2uri.targetEntryReferrals(entryDN, null); return null; } // Read id2entry. entry = id2entry.get(null, entryID, null); if (entry == null) { // The entryID does not exist. throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), ERR_JEB_MISSING_ID2ENTRY_RECORD .get(entryID)); } // Put the entry in the cache making sure not to overwrite // a newer copy that may have been inserted since the time // we read the cache. if (entryCache != null) { entryCache.putEntryIfAbsent(entry, rootContainer.getBackend(), entryID.longValue()); } } return entry; } /** {@inheritDoc} */ @Override public String toString() { return getClass().getSimpleName() + " baseDN=" + baseDN; } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/persistit/package-info.java
New file @@ -0,0 +1,33 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2014 ForgeRock AS */ /** * Contains the code for the Directory Server backend that uses Persistit * as the repository for storing entry and index information. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.PRIVATE) package org.opends.server.backends.persistit; opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/KeyValueStore.java
New file @@ -0,0 +1,106 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.backends.pluggable; import java.io.Closeable; import org.opends.server.types.DirectoryException; /** * TODO JNR. * @param <K> TODO JNR * @param <V> TODO JNR * @param <T> TODO JNR * @param <M> TODO JNR */ public interface KeyValueStore<K, V, T, M> extends Closeable { /** * TODO JNR. * * @throws DirectoryException * TODO JNR */ void open() throws DirectoryException; /** * TODO JNR. * * @param txn * TODO JNR * @param key * TODO JNR * @param value * TODO JNR * @return TODO JNR * @throws DirectoryException * TODO JNR */ boolean insert(T txn, K key, V value) throws DirectoryException; /** * TODO JNR. * * @param txn * TODO JNR * @param key * TODO JNR * @param value * TODO JNR * @return TODO JNR * @throws DirectoryException * TODO JNR */ boolean put(T txn, K key, V value) throws DirectoryException; /** * TODO JNR. * * @param txn * TODO JNR * @param key * TODO JNR * @param mode * TODO JNR * @return TODO JNR * @throws DirectoryException * TODO JNR */ V get(T txn, K key, M mode) throws DirectoryException; /** * TODO JNR. * * @param txn * TODO JNR * @param key * TODO JNR * @return TODO JNR * @throws DirectoryException * TODO JNR */ boolean remove(T txn, K key) throws DirectoryException; } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NotImplementedException.java
New file @@ -0,0 +1,51 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.backends.pluggable; /** * Class that helps figuring out where code is not implemented. This class * should only be used during the implementation of pluggable storage backends. */ public class NotImplementedException extends RuntimeException { private static final long serialVersionUID = 6647576563755499711L; /** Default constructor. */ public NotImplementedException() { super("Code is not implemented"); } /** * Default constructor. * * @param cause * the cause */ public NotImplementedException(Throwable cause) { super("Code is not implemented", cause); } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/PluggableStorageBackend.java
@@ -25,15 +25,20 @@ */ package org.opends.server.backends.pluggable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.ConditionResult; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.util.Reject; import org.opends.server.admin.Configuration; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.BackendCfg; import org.opends.server.api.Backend; import org.opends.server.core.AddOperation; import org.opends.server.core.DeleteOperation; @@ -45,6 +50,7 @@ import org.opends.server.types.BackupConfig; import org.opends.server.types.BackupDirectory; import org.opends.server.types.CanceledOperationException; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; @@ -64,29 +70,65 @@ * @param <C> * the type of the BackendCfg for the current backend */ public abstract class PluggableStorageBackend<C extends Configuration> extends Backend<C> public abstract class PluggableStorageBackend<C extends BackendCfg> extends Backend<C> implements ConfigurationChangeListener<C> { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The configuration object. */ protected C cfg; /** The set of base DNs for this backend. */ private DN[] baseDNs; /** {@inheritDoc} */ @Override public void configureBackend(final Configuration cfg) throws ConfigException public void configureBackend(final C cfg) throws ConfigException { Reject.ifNull(cfg); this.cfg = cfg; baseDNs = this.cfg.getBaseDN().toArray(new DN[0]); } /** {@inheritDoc} */ @Override public void initializeBackend() throws InitializationException public boolean isConfigurationChangeAcceptable(C cfg, List<LocalizableMessage> unacceptableReasons) { for (DN baseDN : this.baseDNs) return false; } /** {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange(C cfg) { return null; } /** {@inheritDoc} */ @Override public void initializeBackend() throws ConfigException, InitializationException { registerBaseDNs(cfg.getBaseDN()); } /** * Associates the current backend with the provided baseDNs in the directory * server. * * @param baseDNs * the base DNs to be associated with this backend * @throws InitializationException * If a problem occurs during initialization that is not related to * the server configuration. */ public void registerBaseDNs(Collection<DN> baseDNs) throws InitializationException { for (DN baseDN : baseDNs) { try { DirectoryServer.registerBaseDN(baseDN, this, true); DirectoryServer.registerBaseDN(baseDN, this, false); } catch (final Exception e) { @@ -101,7 +143,19 @@ { super.finalizeBackend(); for (DN baseDN : this.baseDNs) deregisterBaseDNs(cfg.getBaseDN()); } /** * Dissociates the current backend from the provided baseDNs in the directory * server. * * @param baseDNs * the base DNs to dissociate from this backend */ public void deregisterBaseDNs(Collection<DN> baseDNs) { for (DN baseDN : baseDNs) { try { @@ -125,7 +179,7 @@ @Override public void preloadEntryCache() throws UnsupportedOperationException { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @@ -139,7 +193,7 @@ @Override public boolean isIndexed(final AttributeType attributeType, final IndexType indexType) { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @@ -151,7 +205,7 @@ throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), ERR_BACKEND_GET_ENTRY_NULL.get(getBackendID())); } throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @@ -170,7 +224,7 @@ @Override public long numSubordinates(final DN entryDN, final boolean subtree) throws DirectoryException { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @@ -210,9 +264,9 @@ /** {@inheritDoc} */ @Override public void search(final SearchOperation searchOperation) throws DirectoryException public void search(final SearchOperation searchOperation) throws DirectoryException, CanceledOperationException { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @@ -263,14 +317,14 @@ @Override public boolean supportsBackup() { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @Override public boolean supportsBackup(BackupConfig backupConfig, StringBuilder unsupportedReason) { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @@ -293,7 +347,7 @@ @Override public boolean supportsRestore() { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @@ -308,7 +362,13 @@ @Override public long getEntryCount() { throw new RuntimeException("Not implemented"); throw new NotImplementedException(); } /** {@inheritDoc} */ @Override public String toString() { return getClass().getSimpleName() + " baseDNs=" + Arrays.toString(baseDNs); } } opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java
@@ -24,6 +24,7 @@ */ package org.opends.server.backends.pluggable; import java.io.Closeable; import java.util.Map; import org.opends.server.types.DN; @@ -43,7 +44,7 @@ * @param <T> * the type of the suffix containers */ public interface RootContainer<T extends SuffixContainer> public interface RootContainer<T extends SuffixContainer> extends Closeable { /** opendj-sdk/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/SuffixContainer.java
@@ -24,6 +24,8 @@ */ package org.opends.server.backends.pluggable; import java.io.Closeable; import org.opends.server.types.DN; /** @@ -32,10 +34,39 @@ * stores a.k.a indexes. It stores entries in these key-values stores and * maintain the indexes all in sync on updates. */ public interface SuffixContainer public interface SuffixContainer extends Closeable { /** * The name of the index associating normalized DNs to ids. LDAP DNs uniquely * identify entries. */ String DN2ID_INDEX_NAME = "dn2id"; /** * The name of the index associating entry ids to entries. Entry ids are * monotonically increasing unique longs and entries are serialized versions * of LDAP entries. */ String ID2ENTRY_INDEX_NAME = "id2entry"; /** * The name of the index associating an entry id to the entry id set of all * its children. */ String ID2CHILDREN_INDEX_NAME = "id2children"; /** * The name of the index associating an entry id to the entry id set of all * its subordinates. */ String ID2SUBTREE_INDEX_NAME = "id2subtree"; /** The name of the index associating normalized DNs to normalized URIs. */ String REFERRAL_INDEX_NAME = "referral"; /** * The name of the index which associates indexes with their trust state, i.e. * does the index needs to be rebuilt ? */ String STATE_INDEX_NAME = "state"; /** * Returns the baseDN that this suffix container is responsible for. * * @return the baseDN that this suffix container is responsible for