/* * 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 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2014-2015 ForgeRock AS */ package org.opends.server.backends.pluggable; import static org.opends.server.backends.pluggable.CursorTransformer.*; import static org.opends.server.backends.pluggable.DnKeyFormat.*; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import org.forgerock.opendj.ldap.ByteSequence; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ByteStringBuilder; import org.forgerock.opendj.ldap.Functions; import org.forgerock.util.Function; import org.forgerock.util.Pair; import org.forgerock.util.promise.NeverThrowsException; import org.opends.server.backends.pluggable.OnDiskMergeImporter.SequentialCursorDecorator; import org.opends.server.backends.pluggable.spi.Cursor; import org.opends.server.backends.pluggable.spi.ReadableTransaction; import org.opends.server.backends.pluggable.spi.SequentialCursor; import org.opends.server.backends.pluggable.spi.StorageRuntimeException; import org.opends.server.backends.pluggable.spi.TreeName; import org.opends.server.backends.pluggable.spi.UpdateFunction; import org.opends.server.backends.pluggable.spi.WriteableTransaction; import org.opends.server.types.CanceledOperationException; import org.opends.server.types.DN; import org.opends.server.types.Operation; /** * This class represents the dn2id index, which has one record * for each entry. The key is the normalized entry DN and the value * is the entry ID. */ @SuppressWarnings("javadoc") class DN2ID extends AbstractTree { private static final Function<ByteString, Void, NeverThrowsException> TO_VOID_KEY = Functions.returns(null); private static final CursorTransformer.ValueTransformer<ByteString, ByteString, EntryID, NeverThrowsException> TO_ENTRY_ID = new CursorTransformer.ValueTransformer<ByteString, ByteString, EntryID, NeverThrowsException>() { @Override public EntryID transform(ByteString key, ByteString value) { return new EntryID(value); } }; private final DN baseDN; /** * Create a DN2ID instance for in a given entryContainer. * * @param treeName The name of the DN tree. * @param baseDN The base DN of the tree. * @throws StorageRuntimeException If an error occurs in the storage. */ DN2ID(TreeName treeName, DN baseDN) throws StorageRuntimeException { super(treeName); this.baseDN = baseDN; } /** * Adds a new record into the DN tree replacing any existing record having the same DN. * * @param txn a non null transaction * @param dn The entry DN, which is the key to the record. * @param entryID The entry ID, which is the value of the record. * @throws StorageRuntimeException If an error occurred while attempting to insert the new record. */ void put(final WriteableTransaction txn, DN dn, final EntryID entryID) throws StorageRuntimeException { txn.put(getName(), toKey(dn), toValue(entryID)); } boolean insert(final WriteableTransaction txn, DN dn, final EntryID entryID) throws StorageRuntimeException { return txn.update(getName(), toKey(dn), new UpdateFunction() { @Override public ByteSequence computeNewValue(ByteSequence oldEntryID) { if (oldEntryID != null) { // no change return oldEntryID; } // it did not exist before, insert the new value return toValue(entryID); } }); } ByteString toKey(DN dn) { return dnToDNKey(dn, baseDN.size()); } ByteString toValue(final EntryID entryID) { // TODO JNR do we want to use compacted longs? return entryID.toByteString(); } /** * Remove a record from the DN tree. * @param txn a non null transaction * @param dn The entry DN, which is the key to the record. * @return true if the record was removed, false if it was not removed. * @throws StorageRuntimeException If an error occurred while attempting to remove * the record. */ boolean remove(WriteableTransaction txn, DN dn) throws StorageRuntimeException { return txn.delete(getName(), toKey(dn)); } /** * Fetch the entry ID for a given DN. * @param txn a non null transaction * @param dn The DN for which the entry ID is desired. * @return The entry ID, or null if the given DN is not in the DN tree. * @throws StorageRuntimeException If an error occurs in the storage. */ EntryID get(ReadableTransaction txn, DN dn) throws StorageRuntimeException { final ByteString value = txn.read(getName(), toKey(dn)); return value != null ? new EntryID(value) : null; } <V> SequentialCursor<ByteString, ByteString> openCursor(SequentialCursor<ByteString, ByteString> dn2IdCursor, TreeVisitor<V> treeVisitor) { return new TreeVisitorCursor<>(dn2IdCursor, treeVisitor); } Cursor<Void, EntryID> openCursor(ReadableTransaction txn, DN dn) { return transformKeysAndValues(openCursor0(txn, dn), TO_VOID_KEY, TO_ENTRY_ID); } private Cursor<ByteString, ByteString> openCursor0(ReadableTransaction txn, DN dn) { final Cursor<ByteString, ByteString> cursor = txn.openCursor(getName()); cursor.positionToKey(toKey(dn)); return cursor; } SequentialCursor<Void, EntryID> openChildrenCursor(ReadableTransaction txn, DN dn) { return transformKeysAndValues(new ChildrenCursor(openCursor0(txn, dn)), TO_VOID_KEY, TO_ENTRY_ID); } SequentialCursor<Void, EntryID> openSubordinatesCursor(ReadableTransaction txn, DN dn) { return transformKeysAndValues(new SubtreeCursor(openCursor0(txn, dn)), TO_VOID_KEY, TO_ENTRY_ID); } List<Pair<Long, Long>> renameSubtree(WriteableTransaction txn, DN oldName, DN newName, RootContainer rootContainer, boolean renumberEntryIDs, Operation operation) throws CanceledOperationException { try (SequentialCursor<ByteString, ByteString> cursor = new SubtreeCursor(openCursor0(txn, oldName))) { List<Pair<Long, Long>> renamedEntryIDs = new ArrayList<>(); int oldTargetDnKeyLength = toKey(oldName).length(); ByteString newTargetDnKey = toKey(newName); do { ByteString currentDnKey = cursor.getKey(); EntryID oldID = new EntryID(cursor.getValue()); cursor.delete(); ByteString newDnKeySuffix = currentDnKey.subSequence(oldTargetDnKeyLength, currentDnKey.length()); ByteSequence newDnKey = new ByteStringBuilder(newTargetDnKey).appendBytes(newDnKeySuffix); EntryID newID = renumberEntryIDs ? rootContainer.getNextEntryID() : oldID; txn.put(getName(), newDnKey, newID.toByteString()); renamedEntryIDs.add(Pair.of(oldID.longValue(), newID.longValue())); if (operation != null) { operation.checkIfCanceled(false); } } while (cursor.next()); return renamedEntryIDs; } } /** * Check if two DN have a parent-child relationship. * * @param parent * The potential parent * @param child * The potential child of parent * @return true if child is a direct children of parent, false otherwise. */ static boolean isChild(ByteSequence parent, ByteSequence child) { if (!child.startsWith(parent)) { return false; } // Immediate children should only have one RDN separator past the parent length int nbSeparator = 0; for (int i = parent.length() ; i < child.length(); i++) { if (child.byteAt(i) == DN.NORMALIZED_RDN_SEPARATOR) { nbSeparator++; if (nbSeparator > 1) { return false; } } } return nbSeparator == 1; } @Override public String keyToString(ByteString key) { return key.length() > 0 ? keyToDNString(key) : baseDN.toString(); } @Override public String valueToString(ByteString value) { return new EntryID(value).toString(); } @Override public ByteString generateKey(String key) { try { return toKey(DN.valueOf(key)); } catch (Exception e) { return ByteString.valueOfBytes(key.getBytes()); } } /** * Decorator overriding the next() behavior to iterate through children of the entry pointed by the given cursor at * creation. */ private static final class ChildrenCursor extends SequentialCursorDecorator<Cursor<ByteString, ByteString>, ByteString, ByteString> { private final ByteStringBuilder builder; private final ByteSequence limit; private boolean cursorOnParent; ChildrenCursor(Cursor<ByteString, ByteString> delegate) { super(delegate); builder = new ByteStringBuilder(128); limit = delegate.isDefined() ? afterKey(delegate.getKey()) : ByteString.empty(); cursorOnParent = true; } @Override public boolean next() { if (cursorOnParent) { // Go to the first children delegate.next(); cursorOnParent = false; } else { // Go to the next sibling delegate.positionToKeyOrNext(nextSibling()); } return isDefined() && delegate.getKey().compareTo(limit) < 0; } private ByteSequence nextSibling() { return builder.clear().appendBytes(delegate.getKey()).appendByte(0x1); } } /** * Decorator overriding the next() behavior to iterate through subordinates of the entry pointed by the given cursor * at creation. */ private static final class SubtreeCursor extends SequentialCursorDecorator<Cursor<ByteString, ByteString>, ByteString, ByteString> { private final ByteSequence limit; SubtreeCursor(Cursor<ByteString, ByteString> delegate) { super(delegate); limit = delegate.isDefined() ? afterKey(delegate.getKey()) : ByteString.empty(); } @Override public boolean next() { return delegate.next() && delegate.getKey().compareTo(limit) < 0; } } /** Keep track of information during the visit. */ private static final class ParentInfo<V> { private final ByteString parentDN; private final V visitorData; ParentInfo(ByteString parentDN, V visitorData) { this.parentDN = parentDN; this.visitorData = visitorData; } } /** Allows to visit dn2id tree without exposing internal encoding. */ static interface TreeVisitor<V> { V beginParent(EntryID parentID); void onChild(V parent, EntryID childID); void endParent(V parent); } /** Perform dn2id cursoring to expose parent and children to the {@link TreeVisitor}. */ private static final class TreeVisitorCursor<V> implements SequentialCursor<ByteString, ByteString> { private final SequentialCursor<ByteString, ByteString> delegate; private final LinkedList<ParentInfo<V>> parentsInfoStack; private final TreeVisitor<V> visitor; TreeVisitorCursor(SequentialCursor<ByteString, ByteString> delegate, TreeVisitor<V> visitor) { this.delegate = delegate; this.parentsInfoStack = new LinkedList<>(); this.visitor = visitor; } @Override public boolean next() { if (delegate.next()) { final ByteString dn = delegate.getKey(); final EntryID entryID = new EntryID(delegate.getValue()); popCompleteParents(dn); notifyChild(entryID); pushNewParent(dn, entryID); return true; } popCompleteParents(DN.NULL_DN.toNormalizedByteString()); return false; } private void pushNewParent(final ByteString dn, final EntryID entryID) { parentsInfoStack.push(new ParentInfo<>(dn, visitor.beginParent(entryID))); } private void notifyChild(final EntryID entryID) { if (!parentsInfoStack.isEmpty()) { visitor.onChild(parentsInfoStack.peek().visitorData, entryID); } } private void popCompleteParents(ByteString dn) { ParentInfo<V> currentParent; while ((currentParent = parentsInfoStack.peek()) != null && !isChild(currentParent.parentDN, dn)) { visitor.endParent(parentsInfoStack.pop().visitorData); } } @Override public boolean isDefined() { return delegate.isDefined(); } @Override public ByteString getKey() throws NoSuchElementException { return delegate.getKey(); } @Override public ByteString getValue() throws NoSuchElementException { return delegate.getValue(); } @Override public void delete() throws NoSuchElementException, UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public void close() { delegate.close(); } } }