blob: e7c2a13bd727fe4396a656fc7c35d940b989ea0c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.api.ldap.util.tree;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.directory.api.i18n.I18n;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class storing nodes in a tree designed to map DNs.<br>
* Branch nodes in this tree refers to child nodes. Leaf nodes in the tree
* don't have any children. <br>
* A node may contain a reference to an object whose suffix is the path through the
* nodes of the tree from the root. <br>
* A node may also have no attached element.<br>
* Each child node is referenced by a Rdn, and holds the full Dn corresponding to its position<br>
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @param <N> The type of node we store
*/
public class DnNode<N>
{
/** The logger for this class */
private static final Logger LOG = LoggerFactory.getLogger( DnNode.class );
/** The stored element */
private N nodeElement;
/** The node's key */
private Rdn nodeRdn;
/** The node's Dn */
private Dn nodeDn;
/** The node's depth in the tree */
private int depth;
/** The parent, if any */
private DnNode<N> parent;
/** Stores the list of all the descendant */
private Map<String, DnNode<N>> children;
//-------------------------------------------------------------------------
// Constructors
//-------------------------------------------------------------------------
/**
* Creates a new instance of DnNode.
*/
public DnNode()
{
children = new HashMap<>();
nodeDn = Dn.EMPTY_DN;
nodeRdn = Rdn.EMPTY_RDN;
}
/**
* Creates a new instance of DnNode.
*
* @param element the element to store
*/
public DnNode( N element )
{
this.nodeElement = element;
children = new HashMap<>();
}
/**
* Creates a new instance of DnNode.
*
* @param dn the node's Dn
* @param element the element to store
*/
public DnNode( Dn dn, N element )
{
if ( ( dn == null ) || ( dn.isEmpty() ) )
{
children = new HashMap<>();
this.nodeDn = Dn.EMPTY_DN;
return;
}
try
{
DnNode<N> rootNode = createNode( dn, element, dn.size() );
// Now copy back the created node into this
this.children = rootNode.children;
this.depth = rootNode.depth;
this.nodeDn = rootNode.nodeDn;
this.nodeElement = rootNode.nodeElement;
this.nodeRdn = rootNode.nodeRdn;
this.parent = null;
}
catch ( LdapException le )
{
// Special cas e: the Dn is empty, this is not allowed
throw new IllegalArgumentException( le.getMessage(), le );
}
}
//-------------------------------------------------------------------------
// Helper methods
//-------------------------------------------------------------------------
/**
* Check that the Dn is not null
*
* @param dn The Dn to check
* @throws LdapException If teh Dn is null or empty
*/
private void checkDn( Dn dn ) throws LdapException
{
if ( ( dn == null ) || dn.isEmpty() )
{
String message = I18n.err( I18n.ERR_12000_CANNOT_PROCESS_EMPTY_DN );
LOG.error( message );
throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, message );
}
}
/**
* Create a new DnNode, recursively creating all the intermediate nodes.
*
* @param dn The nod's Dn
* @param element The element to set
* @param nbRdns The number of RDNs in the Dn
* @param nbRdns The number of level to create
* @return The created Node
* @throws LdapException If the Dn is null or empty
*/
private DnNode<N> createNode( Dn dn, N element, int nbRdns ) throws LdapException
{
checkDn( dn );
DnNode<N> rootNode = null;
// No parent : add from the current position
for ( Rdn rdn : dn.getRdns() )
{
if ( nbRdns == 0 )
{
break;
}
if ( rootNode == null )
{
// Create the new top node
DnNode<N> node = new DnNode<>( element );
node.nodeRdn = rdn;
node.nodeDn = dn;
node.depth = dn.size() + depth;
rootNode = node;
}
else
{
DnNode<N> node = new DnNode<>();
node.nodeRdn = rdn;
node.nodeDn = rootNode.nodeDn.getParent();
node.depth = node.nodeDn.size() + depth;
rootNode.parent = node;
node.children.put( rootNode.nodeRdn.getNormName(), rootNode );
rootNode = node;
}
nbRdns--;
}
return rootNode;
}
/**
* Store the given element into the node
*
* @param element The element to set
*/
private synchronized void setElement( N element )
{
this.nodeElement = element;
}
/**
* Tells if the implementation is a leaf node. If it's a branch node
* then false is returned.
*
* @return <code>true</code> if the class is a leaf node, false otherwise.
*/
public synchronized boolean isLeaf()
{
return !hasChildren();
}
/**
* Tells if the implementation is a leaf node. If it's a branch node
* then false is returned.
*
* @param dn The Dn we want to check
* @return <code>true</code> if this is a leaf node, false otherwise.
*/
public synchronized boolean isLeaf( Dn dn )
{
DnNode<N> node = getNode( dn );
if ( node == null )
{
return false;
}
return node.children.size() == 0;
}
/**
* Returns the number of entries under this node. It includes
* the node itself, plus the number of all it children and descendants.
*
* @return The number of descendents
*/
public synchronized int size()
{
// The node itself
int size = 1;
// Iterate through the children if any
if ( children.size() != 0 )
{
for ( DnNode<N> node : children.values() )
{
size += node.size();
}
}
return size;
}
/**
* @return Return the stored element, if any
*/
public synchronized N getElement()
{
return nodeElement;
}
/**
* @return Return the stored element, if any
* @param dn The Dn we want to get the element for
*/
public synchronized N getElement( Dn dn )
{
DnNode<N> node = getNode( dn );
if ( node == null )
{
return null;
}
return node.nodeElement;
}
/**
* @return True if the Node stores an element. BranchNode may not hold any
* element.
*/
public synchronized boolean hasElement()
{
return nodeElement != null;
}
/**
* @return True if the Node stores an element. BranchNode may not hold any
* element.
* @param dn The Dn we want to get the element for
*/
public synchronized boolean hasElement( Dn dn )
{
DnNode<N> node = getNode( dn );
if ( node == null )
{
return false;
}
return node.nodeElement != null;
}
/**
* Recursively check if the node has a descendant having an element
*
* @param node The node to start from
* @return <tt>true</tt> if the node has some descendant
*/
private synchronized boolean hasDescendantElement( DnNode<N> node )
{
if ( node == null )
{
return false;
}
if ( node.hasElement() )
{
return true;
}
for ( DnNode<N> child : node.getChildren().values() )
{
if ( hasDescendantElement( child ) )
{
return true;
}
}
// Nothing found ...
return false;
}
/**
* @return True if one of the node below the current node has one element,
* False otherwise
* @param dn The Dn we want to get the element for
*/
public synchronized boolean hasDescendantElement( Dn dn )
{
DnNode<N> node = getNode( dn );
if ( node == null )
{
return false;
}
// We must be at the right place in the tree
if ( node.getDn().size() != dn.size() )
{
return false;
}
if ( node.hasChildren() )
{
for ( DnNode<N> child : node.getChildren().values() )
{
if ( hasDescendantElement( child ) )
{
return true;
}
}
}
return false;
}
/**
* Recursively get all the elements from nodes having an element
*
* @param node The node to start with
* @param descendants The list of descendant to fulfill
*/
private synchronized void getDescendantElements( DnNode<N> node, List<N> descendants )
{
if ( node == null )
{
return;
}
if ( node.hasElement() )
{
descendants.add( node.getElement() );
// Stop here
return;
}
for ( DnNode<N> child : node.getChildren().values() )
{
getDescendantElements( child, descendants );
}
}
/**
* @return True if one of the node below the current node has one element,
* False otherwise
* @param dn The Dn we want to get the element for
*/
public synchronized List<N> getDescendantElements( Dn dn )
{
List<N> descendants = new ArrayList<>();
DnNode<N> node = getNode( dn );
if ( node == null )
{
return descendants;
}
// We must be at the right place in the tree
if ( node.getDn().size() != dn.size() )
{
return descendants;
}
if ( node.hasChildren() )
{
for ( DnNode<N> child : node.getChildren().values() )
{
getDescendantElements( child, descendants );
}
}
return descendants;
}
/**
* Tells if the current DnNode has some children or not
*
* @return <code>true</code> if the node has some children
*/
public synchronized boolean hasChildren()
{
return ( children != null ) && children.size() != 0;
}
/**
* Tells if a node has some children or not.
*
* @param dn the node's Dn
* @return <code>true</code> if the node has some children
* @throws LdapException if the Dn is null or empty
*/
public synchronized boolean hasChildren( Dn dn ) throws LdapException
{
checkDn( dn );
DnNode<N> node = getNode( dn );
return ( node != null ) && node.hasChildren();
}
/**
* @return The list of DnNode
*/
public synchronized Map<String, DnNode<N>> getChildren()
{
return children;
}
/**
* @return The parent DnNode, if any
*/
public synchronized DnNode<N> getParent()
{
return parent;
}
/**
* @return True if the current DnNode has a parent
*/
public synchronized boolean hasParent()
{
return parent != null;
}
/**
* Tells if there is a parent for a given Dn,. This parent should be a
* subset of the given dn.<br>
* For instance, if we have stored dc=acme, dc=org into the tree,
* the Dn: ou=example, dc=acme, dc=org will have a parent
* <br>For the Dn ou=apache, dc=org, there is no parent, so false will be returned.
*
* @param dn the normalized distinguished name to resolve to a parent
* @return true if there is a parent associated with the normalized dn
*/
public synchronized boolean hasParent( Dn dn )
{
List<Rdn> rdns = dn.getRdns();
DnNode<N> currentNode = this;
DnNode<N> parentNode = null;
// Iterate through all the Rdn until we find the associated element
for ( int i = rdns.size() - 1; i >= 0; i-- )
{
Rdn rdn = rdns.get( i );
if ( rdn.equals( currentNode.nodeRdn ) )
{
parentNode = currentNode;
}
else if ( currentNode.hasChildren() )
{
currentNode = currentNode.children.get( rdn.getNormName() );
if ( currentNode == null )
{
break;
}
parentNode = currentNode;
}
else
{
break;
}
}
return parentNode != null;
}
/**
* Add a new node in the tree. The added node won't have any element.
*
* @param dn The node's Dn
* @return the corresponding node
* @throws LdapException if the Dn is null or empty
*/
public synchronized DnNode<N> add( Dn dn ) throws LdapException
{
return add( dn, null );
}
/**
* Add a new node in the tree. We can't add a node if its Dn is empty. The
* added element is attached to the node, which is named by the Dn's Rdn.<br>
*
* @param dn The node's Dn
* @param element The element to associate with this Node. Can be null.
* @return the corresponding node
* @throws LdapException if the Dn is null or empty
*/
public synchronized DnNode<N> add( Dn dn, N element ) throws LdapException
{
checkDn( dn );
// We first have to find the Node which will be the parent
DnNode<N> parentNode = getNode( dn );
if ( parentNode == null )
{
// No parent : add a new node to the root
DnNode<N> childNode = createNode( dn, element, dn.size() );
childNode.parent = this;
children.put( childNode.nodeRdn.getNormName(), childNode );
return childNode;
}
else
{
// We have a parent. Add the new node to the found parent
int nbRdns = dn.size() - parentNode.depth;
if ( nbRdns == 0 )
{
// That means the added Dn is already present. Check if it already has an element
if ( parentNode.hasElement() )
{
String message = I18n.err( I18n.ERR_12001_CANNOT_ADD_NODE_CHILD_EXISTS );
LOG.error( message );
throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, message );
}
// We may try to add twice the same Dn, without any element
else if ( element == null )
{
String message = I18n.err( I18n.ERR_12002_CANNOT_ADD_NODE_ALREADY_EXISTS );
LOG.error( message );
throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, message );
}
// All is fine : we are just injecting some data into an existing node
else
{
parentNode.setElement( element );
return parentNode;
}
}
else
{
DnNode<N> childNode = createNode( dn, element, nbRdns );
// done. now, add the newly created tree to the parent node
childNode.parent = parentNode;
parentNode.children.put( childNode.nodeRdn.getNormName(), childNode );
return childNode;
}
}
}
/**
* Removes a node from the tree.
*
* @param dn the node's Dn
* @throws LdapException if the Dn is null or empty
*/
public synchronized void remove( Dn dn ) throws LdapException
{
checkDn( dn );
// Find the parent first : we won't be able to remove
// a node if it's not present in the tree !
DnNode<N> parentNode = getNode( dn );
if ( parentNode == null )
{
return;
}
// Now, check that this parent has the same Dn than the one
// we gave and that there is no children
if ( ( dn.size() != parentNode.depth ) || parentNode.hasChildren() )
{
return;
}
// Ok, no children, same Dn, let's remove what we can.
parentNode = parentNode.getParent();
for ( Rdn rdn : dn.getRdns() )
{
parentNode.children.remove( rdn.getNormName() );
if ( parentNode.children.size() > 0 )
{
// We have to stop here, because the parent's node is shared with other Node.
break;
}
parentNode = parentNode.getParent();
}
}
/**
* Tells if the current DnBranchNode contains another node associated
* with an rdn.
*
* @param rdn The name we are looking for
* @return <code>true</code> if the tree instance contains this name
*/
public synchronized boolean contains( Rdn rdn )
{
return children.containsKey( rdn.getNormName() );
}
/**
* Get's a child using an rdn string.
*
* @param rdn the rdn to use as the node key
* @return the child node corresponding to the rdn.
*/
public synchronized DnNode<N> getChild( Rdn rdn )
{
if ( children.containsKey( rdn.getNormName() ) )
{
return children.get( rdn.getNormName() );
}
return null;
}
/**
* @return The Node's Rdn
*/
public synchronized Rdn getRdn()
{
return nodeRdn;
}
/**
* Get the Node for a given Dn, if present in the tree.<br>
* For instance, if we have stored dc=acme, dc=org into the tree,
* the Dn: ou=example, dc=acme, dc=org will have a parent, and
* dc=acme, dc=org will be returned.
* <br>For the Dn ou=apache, dc=org, there is no parent, so null will be returned.
*
* @param dn the normalized distinguished name to resolve to a parent
* @return the Node associated with the normalized dn
*/
public synchronized DnNode<N> getNode( Dn dn )
{
DnNode<N> currentNode = this;
DnNode<N> parentNode = null;
// Iterate through all the Rdn until we find the associated partition
for ( int i = dn.size() - 1; i >= 0; i-- )
{
Rdn rdn = dn.getRdn( i );
if ( currentNode.hasChildren() )
{
currentNode = currentNode.children.get( rdn.getNormName() );
if ( currentNode == null )
{
break;
}
parentNode = currentNode;
}
else
{
break;
}
}
return parentNode;
}
/**
* Get the closest Node for a given Dn which has an element, if present in the tree.<br>
* For instance, if we have stored dc=acme, dc=org into the tree,
* the Dn: ou=example, dc=acme, dc=org will have a parent, and
* dc=acme, dc=org will be returned if it has an associated element.
* <br>For the Dn ou=apache, dc=org, there is no parent, so null will be returned.
*
* @param dn the normalized distinguished name to resolve to a parent
* @return the Node associated with the normalized dn
*/
public synchronized boolean hasParentElement( Dn dn )
{
List<Rdn> rdns = dn.getRdns();
DnNode<N> currentNode = this;
boolean hasElement = false;
// Iterate through all the Rdn until we find the associated partition
for ( int i = rdns.size() - 1; i >= 0; i-- )
{
Rdn rdn = rdns.get( i );
if ( currentNode.hasChildren() )
{
currentNode = currentNode.children.get( rdn.getNormName() );
if ( currentNode == null )
{
break;
}
if ( currentNode.hasElement() )
{
hasElement = true;
}
parent = currentNode;
}
else
{
break;
}
}
return hasElement;
}
/**
* Get the closest Node for a given Dn which has an element, if present in the tree.<br>
* For instance, if we have stored dc=acme, dc=org into the tree,
* the Dn: ou=example, dc=acme, dc=org will have a parent, and
* dc=acme, dc=org will be returned if it has an associated element.
* <br>For the Dn ou=apache, dc=org, there is no parent, so null will be returned.
*
* @param dn the normalized distinguished name to resolve to a parent
* @return the Node associated with the normalized dn
*/
public synchronized DnNode<N> getParentWithElement( Dn dn )
{
List<Rdn> rdns = dn.getRdns();
DnNode<N> currentNode = this;
DnNode<N> element = null;
// Iterate through all the Rdn until we find the associated partition
for ( int i = rdns.size() - 1; i >= 1; i-- )
{
Rdn rdn = rdns.get( i );
if ( currentNode.hasChildren() )
{
currentNode = currentNode.children.get( rdn.getNormName() );
if ( currentNode == null )
{
break;
}
if ( currentNode.hasElement() )
{
element = currentNode;
}
parent = currentNode;
}
else
{
break;
}
}
return element;
}
/**
* Get the closest Node for a given Dn which has an element, if present in the tree.<br>
* For instance, if we have stored dc=acme, dc=org into the tree,
* the Dn: ou=example, dc=acme, dc=org will have a parent, and
* dc=acme, dc=org will be returned if it has an associated element.
* <br>For the Dn ou=apache, dc=org, there is no parent, so null will be returned.
*
* @return the Node associated with the normalized dn
*/
public synchronized DnNode<N> getParentWithElement()
{
DnNode<N> currentNode = parent;
while ( currentNode != null )
{
if ( currentNode.nodeElement != null )
{
return currentNode;
}
currentNode = currentNode.parent;
}
return null;
}
/**
* rename the DnNode's Dn
*
* @param newRdn the new Rdn of this node
* @throws LdapException If the rename failed
*/
public synchronized void rename( Rdn newRdn ) throws LdapException
{
Dn temp = nodeDn.getParent();
temp = temp.add( newRdn );
Rdn oldRdn = nodeRdn;
nodeRdn = temp.getRdn();
nodeDn = temp;
if ( parent != null )
{
parent.children.remove( oldRdn.getNormName() );
parent.children.put( nodeRdn.getNormName(), this );
}
updateAfterModDn( nodeDn );
}
/**
* move the DnNode's Dn
*
* @param newParent the new parent Dn
* @throws LdapException If the move failed
*/
public synchronized void move( Dn newParent ) throws LdapException
{
DnNode<N> tmp = null;
Dn tmpDn = null;
// check if the new parent Dn is child of the parent
if ( newParent.isDescendantOf( parent.nodeDn ) )
{
tmp = parent;
tmpDn = parent.nodeDn;
}
// if yes, then drill for the new parent node
if ( tmpDn != null )
{
int parentNodeSize = tmpDn.size();
int count = newParent.size() - parentNodeSize;
while ( count-- > 0 )
{
tmp = tmp.getChild( newParent.getRdn( parentNodeSize++ ) );
}
}
// if not, we have to traverse all the way up to the
// root node and then find the new parent node
if ( tmp == null )
{
tmp = this;
while ( tmp.parent != null )
{
tmp = tmp.parent;
}
tmp = tmp.getNode( newParent );
}
nodeDn = newParent.add( nodeRdn );
updateAfterModDn( nodeDn );
if ( parent != null )
{
parent.children.remove( nodeRdn.getNormName() );
}
parent = tmp;
parent.children.put( nodeRdn.getNormName(), this );
}
/**
* update the children's Dn based on the new parent Dn created
* after a rename or move operation
*
* @param newParentDn The new parent's Dn
* @throws LdapInvalidDnException The parent DN is invalid
*/
private synchronized void updateAfterModDn( Dn newParentDn ) throws LdapInvalidDnException
{
if ( children != null )
{
for ( DnNode<N> child : children.values() )
{
child.nodeDn = newParentDn.add( child.nodeRdn );
child.updateAfterModDn( child.nodeDn );
}
}
}
private String toString( String tabs )
{
if ( nodeRdn == null )
{
return tabs;
}
StringBuilder sb = new StringBuilder();
sb.append( tabs );
boolean hasChildren = hasChildren();
if ( isLeaf() )
{
sb.append( "Leaf[" ).append( nodeDn ).append( "]: " ).append( "'" ).append( nodeElement ).append( "'" );
return sb.toString();
}
sb.append( "Branch[" ).append( nodeDn ).append( "]: " );
if ( nodeElement != null )
{
sb.append( "'" ).append( nodeElement ).append( "'" );
}
tabs += " ";
sb.append( '\n' );
boolean isFirst = true;
if ( hasChildren )
{
for ( Map.Entry<String, DnNode<N>> entry : children.entrySet() )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( "\n" );
}
DnNode<N> child = entry.getValue();
sb.append( child.toString( tabs ) );
}
}
return sb.toString();
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return toString( "" );
}
/**
* @return the dn
*/
public synchronized Dn getDn()
{
return nodeDn;
}
}