You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by sm...@apache.org on 2014/10/22 17:44:54 UTC

[35/51] [partial] Rename packages from org.openldap.fortress to org.apache.directory.fortress.core. Change default suffix to org.apache. Switch default ldap api from unbound to apache ldap.

http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/687ee1ad/src/main/java/org/apache/directory/fortress/core/ldap/LdapCounters.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/directory/fortress/core/ldap/LdapCounters.java b/src/main/java/org/apache/directory/fortress/core/ldap/LdapCounters.java
new file mode 100644
index 0000000..6f83b4d
--- /dev/null
+++ b/src/main/java/org/apache/directory/fortress/core/ldap/LdapCounters.java
@@ -0,0 +1,122 @@
+/*
+ *   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
+ *
+ *     http://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.fortress.core.ldap;
+
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ *
+ */
+public class LdapCounters
+{
+    AtomicInteger readCtr = new AtomicInteger( 0 );
+    AtomicInteger searchCtr = new AtomicInteger( 0 );
+    AtomicInteger compareCtr = new AtomicInteger( 0 );
+    AtomicInteger addCtr = new AtomicInteger( 0 );
+    AtomicInteger modCtr = new AtomicInteger( 0 );
+    AtomicInteger deleteCtr = new AtomicInteger( 0 );
+    AtomicInteger bindCtr = new AtomicInteger( 0 );
+
+
+    public void incrementSearch()
+    {
+        searchCtr.incrementAndGet();
+    }
+
+
+    public void incrementRead()
+    {
+        readCtr.incrementAndGet();
+    }
+
+
+    public void incrementCompare()
+    {
+        compareCtr.incrementAndGet();
+    }
+
+
+    public void incrementAdd()
+    {
+        addCtr.incrementAndGet();
+    }
+
+
+    public void incrementMod()
+    {
+        modCtr.incrementAndGet();
+    }
+
+
+    public void incrementDelete()
+    {
+        deleteCtr.incrementAndGet();
+    }
+
+
+    public void incrementBind()
+    {
+        bindCtr.incrementAndGet();
+    }
+
+
+    public long getSearch()
+    {
+        return searchCtr.intValue();
+    }
+
+
+    public long getRead()
+    {
+        return readCtr.intValue();
+    }
+
+
+    public long getCompare()
+    {
+        return compareCtr.intValue();
+    }
+
+
+    public long getAdd()
+    {
+        return addCtr.intValue();
+    }
+
+
+    public long getMod()
+    {
+        return modCtr.intValue();
+    }
+
+
+    public long getDelete()
+    {
+        return deleteCtr.intValue();
+    }
+
+
+    public long getBind()
+    {
+        return bindCtr.intValue();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/687ee1ad/src/main/java/org/apache/directory/fortress/core/ldap/PoolMgr.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/directory/fortress/core/ldap/PoolMgr.java b/src/main/java/org/apache/directory/fortress/core/ldap/PoolMgr.java
new file mode 100755
index 0000000..e960ad3
--- /dev/null
+++ b/src/main/java/org/apache/directory/fortress/core/ldap/PoolMgr.java
@@ -0,0 +1,619 @@
+/*
+ *   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
+ *
+ *     http://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.fortress.core.ldap;
+
+import org.apache.directory.fortress.core.GlobalIds;
+import org.apache.directory.fortress.core.cfg.Config;
+import org.apache.directory.fortress.core.util.crypto.EncryptUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPConnection;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPException;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPControl;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPConstraints;
+
+/**
+ * This class uses {@link ConnectionPool} to manage pools of {@code com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPConnection}
+ * to supply resource connections to Fortress DAO utilities.  The methods in the class are used by internal Fortress functions
+ * and are not intended for used by external clients.  This class maintains 3 pools of connections.
+ * <ol>
+ * <li>Connections of type, {@link PoolMgr.ConnType#USER}, use {@link #connPoolUser} for user authentication and password change operations.</li>
+ * <li>Connections of type, {@link PoolMgr.ConnType#ADMIN}, use {@link #connPoolAdmin} and are used for maintenance and interrogation of ldap server objects.</li>
+ * <li>Connections of type, {@link PoolMgr.ConnType#LOG}, use {@link #connPoolLog} and used for pulling slapd log data from the server,  </li>
+ * </ol>
+ * <p/>
+ * This class uses <a href="http://www.unboundid.com/products/ldap-sdk/">UnboundID LDAP SDK for Java</a> as client to
+ * process LDAP operations.  The UnboundID SDK is distributed under 3 open source licenses and is free to use and distribute in
+ * other open source or proprietary software packages.  For more info see, <a href="http://www.unboundid.com/products/ldap-sdk/docs/">LDAP SDK for Java</a>
+ * <p/>
+ * The {@link ConnectionPool} class derives source code from the Mozilla Java LDAP SDK.  For more
+ * info on the license this derived code adheres, see: <a href="http://www.mozilla.org/MPL/MPL-1.1.html/">Mozilla Public License Version 1.1</a>
+ * <p/>
+ * This class is thread safe.
+ * <p/>
+ *
+ * @author Shawn McKinney
+ */
+class PoolMgr
+{
+    // Property names for ldap connection pools:
+    private static final String LDAP_ADMIN_POOL_UID = "admin.user";
+    private static final String LDAP_ADMIN_POOL_PW = "admin.pw";
+    private static final String LDAP_LOG_POOL_UID = "log.admin.user";
+    private static final String LDAP_LOG_POOL_PW = "log.admin.pw";
+    private static final String LDAP_ADMIN_POOL_MIN = "min.admin.conn";
+    private static final String LDAP_ADMIN_POOL_MAX = "max.admin.conn";
+    private static final String LDAP_USER_POOL_MIN = "min.user.conn";
+    private static final String LDAP_USER_POOL_MAX = "max.user.conn";
+    private static final String LDAP_LOG_POOL_MIN = "min.log.conn";
+    private static final String LDAP_LOG_POOL_MAX = "max.log.conn";
+    private static final String LDAP_VERSION = "ldapVersion";
+    private static final String LDAP_CONNECTION_TIMEOUT = "connTimeout";
+    private static final String LDAP_DEBUG_FLAG = "debug.ldap.pool";
+    private static final String LDAP_HOST = "host";
+    private static final String LDAP_PORT = "port";
+
+    // 3 types of connection pools are managed by ths class:
+    static enum ConnType
+    {
+        /**
+         * Admin connections used for most of the Fortress internal operations.  Internal bind on connection
+         * will be performed using config param found {@link #LDAP_ADMIN_POOL_UID}
+         */
+        ADMIN,
+
+        /**
+         * User connections for non-admin binds and password mods.  Connections will not be bound
+         * to user prior to returning to caller.
+         */
+        USER,
+
+        /**
+         * All slapd log operations use this connection pool.   Internal bind on connection
+         * will be performed using config param found {@link #LDAP_LOG_POOL_UID}
+         */
+        LOG
+    }
+
+    // Used to synch the getConnection method:
+    private static final Object adminSynchLock = new Object();
+    private static final Object userSynchLock = new Object();
+    private static final Object logSynchLock = new Object();
+
+    // Canaries in the coal mine:
+    private static LDAPConnection testAdminConn;
+    private static LDAPConnection testUConn;
+    private static LDAPConnection testLConn;
+
+    // Logging
+    private static final String CLS_NM = PoolMgr.class.getName();
+    private static final Logger LOG = LoggerFactory.getLogger( CLS_NM );
+
+    // Declare the index for connection pool array:
+    private static final int ADMIN = 0;
+    private static final int USER = 1;
+    private static final int AUDIT = 2;
+
+    // Contains the adminUserId LDAP connections:
+    private static final ConnectionPool connPoolAdmin = null;
+    private static final ConnectionPool connPoolUser = null;
+    private static final ConnectionPool connPoolLog = null;
+    private static final ConnectionPool[] connPool = {connPoolAdmin, connPoolUser, connPoolLog};
+
+    // this modules uses openldap pw policies
+    private static final LDAPControl pwPolicyControl = new LDAPControl(GlobalIds.OPENLDAP_PW_RESPONSE_CONTROL, false, null);
+    private static String adminPw;
+    private static String adminUserId = null;
+    private static final boolean isDebugEnabled = Config.getBoolean(LDAP_DEBUG_FLAG, false);
+    private static int connectionTimeout ;
+    private static int ldapRevision;
+
+    // Load all of the static member variables of this class & initialize the admin connection pools:
+    static
+    {
+        try
+        {
+            adminUserId = Config.getProperty(LDAP_ADMIN_POOL_UID);
+            if(EncryptUtil.isEnabled())
+            {
+                adminPw = EncryptUtil.decrypt(Config.getProperty(LDAP_ADMIN_POOL_PW));
+            }
+            else
+            {
+                adminPw = Config.getProperty(LDAP_ADMIN_POOL_PW);
+            }
+
+            // Default ldap version to v3:
+            ldapRevision = Config.getInt(LDAP_VERSION, 3);
+            // Default 10 seconds for client wait on new connection requests from pool:
+            connectionTimeout = Config.getInt(LDAP_CONNECTION_TIMEOUT, 10000);
+            createAdminPool();
+        }
+
+        // If we can't initialize the connection pools we're dead in the water.
+        catch (com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPException le)
+        {
+            String error = " Static Initializer Block caught com.unboundid.ldap.sdk.migrate.ldapjdk.LdapException=" + le;
+            LOG.error( error );
+        }
+        catch (Exception e)
+        {
+            String error = " Static Initializer Block caught java.lang.Exception=" + e;
+            LOG.error( error );
+        }
+    }
+
+
+    /**
+     * Method performs an LDAP bind for a user/password combination.  This function is valid
+     * if and only if the user entity is a member of the USERS data set.  The LDAP directory
+     * will return the OpenLDAP PW Policy control.
+     *
+     * @param ld       connection to ldap server.
+     * @param userId   contains the LDAP dn to the user entry.
+     * @param password contains the password in clear text.
+     * @return boolean value - true if bind successful, false otherwise.
+     * @throws LDAPException in the event of LDAP error.
+     */
+    static boolean bind(LDAPConnection ld, String userId, char[] password)
+        throws LDAPException
+    {
+        return bindUser(userId, password, ld);
+    }
+
+    /**
+     * Close the LDAP connection.
+     *
+     * @param ld   handle to ldap connection object.
+     * @param type specifies the type of connection - ADMIN, USER Or LOG.
+     */
+    static void closeConnection(LDAPConnection ld, ConnType type)
+    {
+        switch (type)
+        {
+            case ADMIN:
+                if (ld != null)
+                {
+                    connPool[ADMIN].close(ld);
+                }
+                break;
+
+            case USER:
+                if (ld != null)
+                {
+                    connPool[USER].close(ld);
+                }
+                break;
+
+            case LOG:
+                if (ld != null)
+                {
+                    connPool[AUDIT].close( ld );
+                }
+                break;
+        }
+    }
+
+    /**
+     * Get a connection to the LDAP server.
+     *
+     * @param type type specifies the type of connection - ADMIN, USER Or LOG.
+     * @return ldap connection.
+     * @throws LDAPException
+     */
+    static LDAPConnection getConnection(ConnType type)
+        throws LDAPException
+    {
+        LDAPConnection ld = null;
+        ConnectionPool cp = null;
+        Object lockObj = null;
+        String szType = null;
+        switch (type)
+        {
+            case ADMIN:
+                cp = connPool[ADMIN];
+                lockObj = adminSynchLock;
+                szType = "ADMIN";
+                break;
+
+            case USER:
+                cp = connPool[USER];
+                lockObj = userSynchLock;
+                szType = "USER";
+                break;
+
+            case LOG:
+                cp = connPool[AUDIT];
+                lockObj = logSynchLock;
+                szType = "LOG";
+                break;
+        }
+        try
+        {
+            synchronized (lockObj)
+            {
+                // check the connection pool reference
+                if (cp == null)
+                {
+                    String info = "getConnection " + szType + " initializing pool";
+                    LOG.info( info );
+                    cp = recoverPool(type);
+                }
+                if (connectionTimeout > 0)
+                {
+                    ld = cp.getConnection(connectionTimeout);
+                }
+                else
+                {
+                    ld = cp.getConnection();
+                }
+                // Did the pool object return a null value?
+                if (ld == null)
+                {
+                    String MSG_HDR = "getConnection " + szType;
+                    String warning = MSG_HDR + " detected null connection";
+                    LOG.warn( warning );
+                    // Is the canary is still alive?
+                    // todo: recheck this sequence, make sure still good.
+                    if (!checkConnection(type))
+                    {
+                        warning += szType + " attempt to recover pool";
+                        LOG.warn( warning );
+                        cp = recoverPool(type);
+                        ld = cp.getConnection();
+                        if (ld == null || !ld.isConnected())
+                        {
+                            // Give up:
+                            String error = MSG_HDR + " could not recover";
+                            LOG.error( error );
+                            throw new LDAPException(error, LDAPException.LDAP_TIMEOUT);
+                        }
+                    }
+                    // todo: think about this scenario some more.  should it attempt recovery of pool here?
+                    else
+                    {
+                        // Cannot establish a good connection, give up:
+                        String error = MSG_HDR + " could not retrieve connection";
+                        LOG.error( error );
+                        throw new LDAPException(error, LDAPException.CONNECT_ERROR);
+                    }
+                }
+                // Did the pool object return a bad connection?
+                else if (!ld.isConnected())
+                {
+                    String MSG_HDR = "getConnection " + szType;
+                    String warning = MSG_HDR + " detected bad connection, retry";
+                    LOG.warn( warning );
+                    // attempt to reconnect:
+                    ld.connect(Config.getProperty(LDAP_HOST, "localhost"), Config.getInt(LDAP_PORT, 389));
+                    // if admin connection type must bind here using stored creds:
+                    if(type.equals(ConnType.ADMIN))
+                    {
+                        ld.bind(ldapRevision, adminUserId, adminPw);
+                    }
+                    // Did the reconnect succeed?
+                    if (!ld.isConnected())
+                    {
+                        warning += szType + " cannot reconnect, attempt pool recovery";
+                        LOG.warn( warning );
+                        // Try one last ditch effort to recover entire pool.
+                        cp = recoverPool(type);
+                        ld = cp.getConnection();
+                        // Still bad?
+                        if (ld == null || !ld.isConnected())
+                        {
+                            // Give up:
+                            String error = MSG_HDR + " recovery failed";
+                            LOG.error( error );
+                            throw new LDAPException(error, LDAPException.SERVER_DOWN);
+                        }
+                    }
+                }
+            }
+        }
+        catch (LDAPException e)
+        {
+            String MSG_HDR = "getConnection " + szType;
+            String warning = MSG_HDR + " detected bad connection, retry caught LDAPException=" + e;
+            LOG.warn( warning );
+            // Todo: Test these scenarios:
+            // Did the pool object return a null value or bad conn?
+            if (ld != null && !ld.isConnected()
+                // Make sure this ldap exception wasn't thrown directly above:
+                && e.getLDAPResultCode() != LDAPException.SERVER_DOWN
+                && e.getLDAPResultCode() != LDAPException.CONNECT_ERROR
+                && e.getLDAPResultCode() != LDAPException.LDAP_TIMEOUT)
+            {
+                warning += " attempt to reconnect";
+                LOG.warn( warning );
+                // attempt reconnect:
+                ld.connect(Config.getProperty(LDAP_HOST, "localhost"), Config.getInt(LDAP_PORT, 389));
+                // if admin connection type must bind here using stored creds:
+                if(type.equals(ConnType.ADMIN))
+                {
+                    ld.bind(ldapRevision, adminUserId, adminPw);
+                }
+                // Did it work?
+                if (!ld.isConnected())
+                {
+                    // Give up:
+                    warning = MSG_HDR + " failed to reconnect";
+                    LOG.error( warning );
+                    throw e;
+                }
+            }
+            else
+            {
+                // Give up
+                warning = MSG_HDR + " failed";
+                LOG.error( warning );
+                throw e;
+            }
+        }
+        return ld;
+    }
+
+
+    /**
+     * Internal function is used to create a new pool of admin connections to ldap server.
+     *
+     * @throws LDAPException
+     */
+    private static void createAdminPool()
+        throws LDAPException
+    {
+        String adminUserId = Config.getProperty(LDAP_ADMIN_POOL_UID);
+        String adminPw;
+        if(EncryptUtil.isEnabled())
+        {
+            adminPw = EncryptUtil.decrypt(Config.getProperty(LDAP_ADMIN_POOL_PW));
+        }
+        else
+        {
+            adminPw = Config.getProperty(LDAP_ADMIN_POOL_PW);
+        }
+
+        String host = Config.getProperty(LDAP_HOST, "localhost");
+        int port = Config.getInt(LDAP_PORT, 389);
+        int min = Config.getInt(LDAP_ADMIN_POOL_MIN, 1);
+        int max = Config.getInt(LDAP_ADMIN_POOL_MAX, 10);
+        LOG.info( "createAdminPool min [" + min + "] max [" + max + "] host [" + host + "] port [" + port
+            + "]" );
+        testAdminConn = new LDAPConnection();
+        connPool[ADMIN] = new ConnectionPool(min, max, host, port, adminUserId, adminPw);
+        if (isDebugEnabled)
+        {
+            connPool[ADMIN].setDebug(true);
+        }
+    }
+
+
+    /**
+     * Internal function is used to create a new pool of user connections to ldap server.
+     *
+     * @throws LDAPException
+     */
+    private static void createUserPool()
+        throws LDAPException
+    {
+        String host = Config.getProperty(LDAP_HOST, "localhost");
+        int port = Config.getInt(LDAP_PORT, 389);
+        int min = Config.getInt(LDAP_USER_POOL_MIN, 1);
+        int max = Config.getInt(LDAP_USER_POOL_MAX, 5);
+        String adminUserId = Config.getProperty(LDAP_ADMIN_POOL_UID);
+        String adminPw;
+        if(EncryptUtil.isEnabled())
+        {
+            adminPw = EncryptUtil.decrypt(Config.getProperty(LDAP_ADMIN_POOL_PW));
+        }
+        else
+        {
+            adminPw = Config.getProperty(LDAP_ADMIN_POOL_PW);
+        }
+
+        LOG.info( "createUserPool min [" + min + "] max [" + max + "] host [" + host + "] port [" + port + "]" );
+        connPool[USER] = new ConnectionPool(min, max, host, port, adminUserId, adminPw);
+        if (isDebugEnabled)
+        {
+            connPool[USER].setDebug(true);
+        }
+    }
+
+    /**
+     * Internal function is used to create a new pool of slapd log connections to ldap server.
+     *
+     * @throws LDAPException
+     */
+    private static void createLogPool()
+        throws LDAPException
+    {
+        String logUserId = Config.getProperty(LDAP_LOG_POOL_UID);
+        String logUserPw;
+        if(EncryptUtil.isEnabled())
+        {
+            logUserPw = EncryptUtil.decrypt(Config.getProperty(LDAP_LOG_POOL_PW));
+        }
+        else
+        {
+            logUserPw = Config.getProperty(LDAP_LOG_POOL_PW);
+        }
+
+        String host = Config.getProperty(LDAP_HOST, "localhost");
+        int port = Config.getInt(LDAP_PORT, 389);
+        int min = Config.getInt(LDAP_LOG_POOL_MIN, 1);
+        int max = Config.getInt(LDAP_LOG_POOL_MAX, 5);
+        LOG.info( "createLogPool min [" + min + "] max [" + max + "] host [" + host + "] port [" + port + "]" );
+        connPool[AUDIT] = new ConnectionPool(min, max, host, port, logUserId, logUserPw);
+        if (isDebugEnabled)
+        {
+            connPool[AUDIT].setDebug( true );
+        }
+    }
+
+    /**
+     * Method is used to perform a bind operation on the given connection object.  Connection will contain the
+     * password policy control.
+     *
+     * @param userId   contains the LDAP dn to the user entry.
+     * @param password contains the password in clear text.
+     * @param ld       contains a valid ldap connection.
+     * @return boolean value - true if bind successful, false otherwise.
+     * @throws LDAPException in the event of LDAP error.
+     */
+    private static boolean bindUser(String userId, char[] password, LDAPConnection ld)
+        throws LDAPException
+    {
+        boolean result;
+        if (ld == null)
+        {
+            String error = "bindUser detected null ldap connection";
+            LOG.error( error );
+            throw new LDAPException(error, LDAPException.CONNECT_ERROR);
+        }
+        if (GlobalIds.IS_OPENLDAP)
+        {
+            LDAPConstraints lCon = new LDAPConstraints();
+            lCon.setServerControls(pwPolicyControl);
+            ld.authenticate(ldapRevision, userId, new String(password), lCon);
+            result = true;
+        }
+        else
+        {
+            ld.authenticate(ldapRevision, userId, new String(password));
+            result = true;
+        }
+        return result;
+    }
+
+    /**
+     * This method will recover a connection pool in the event the connections become stale due to some network
+     * or system issue.
+     *
+     * @param type contains connection type of request.
+     * @return ConnectionPool reference to newly created connection pool.
+     * @throws LDAPException in the event of ldap system error or the routine fails to reestablish the pool successfully.
+     */
+    private static ConnectionPool recoverPool(ConnType type) throws LDAPException
+    {
+        ConnectionPool cp = null;
+        switch (type)
+        {
+            case ADMIN:
+                if (connPool[ADMIN] != null)
+                {
+                    connPool[ADMIN].destroy();
+                }
+                createAdminPool();
+                if (connPool[ADMIN] == null)
+                {
+                    String error = "recoverPool LDAP_ADMIN_POOL_UID failed";
+                    LOG.error(error);
+                    throw new LDAPException(error, LDAPException.CONNECT_ERROR);
+                }
+                cp = connPool[ADMIN];
+                break;
+
+            case USER:
+                if (connPool[USER] != null)
+                {
+                    connPool[USER].destroy();
+                }
+                createUserPool();
+                if (connPool[USER] == null)
+                {
+                    String error = "recoverPool USER failed";
+                    LOG.error(error);
+                    throw new LDAPException(error, LDAPException.CONNECT_ERROR);
+                }
+                cp = connPool[USER];
+                break;
+            case LOG:
+                if (connPool[AUDIT] != null)
+                {
+                    connPool[AUDIT].destroy();
+                }
+                createLogPool();
+                if (connPool[AUDIT] == null)
+                {
+                    String error = "recoverPool LOG failed";
+                    LOG.error(error);
+                    throw new LDAPException(error, LDAPException.CONNECT_ERROR);
+                }
+                cp = connPool[AUDIT];
+                break;
+        }
+        return cp;
+    }
+
+    /**
+     * System health method will determine the integrity of a given connection associated with a specified pool is good.
+     *
+     * @param type specifies the type of connection - ADMIN, USER Or LOG.
+     * @return true if connection is good, false otherwise.
+     * @throws LDAPException in the event of ldap error.
+     */
+    private static boolean checkConnection(ConnType type)
+        throws LDAPException
+    {
+        boolean rc = false;
+        LDAPConnection conn = null;
+        String szType = null;
+        switch (type)
+        {
+            case ADMIN:
+                conn = testAdminConn;
+                szType = "LDAP_ADMIN_POOL_UID";
+                break;
+            case USER:
+                conn = testUConn;
+                szType = "USER";
+                break;
+            case LOG:
+                conn = testLConn;
+                szType = "LOG";
+                break;
+        }
+        String info = "checkConnection is checking " + szType + " Connection";
+        LOG.info( info );
+        if (conn != null)
+        {
+            if (conn.isConnected())
+            {
+                LOG.debug( "checkConnection for type: {}, is good", szType );
+                rc = true;
+            }
+            else
+            {
+                info = "checkConnection -  " + szType + " connection bad";
+                LOG.info( info );
+                conn.reconnect();
+                if (conn.isConnected())
+                {
+                    info = "checkConnection -  " + szType + " connection reestablished";
+                    LOG.info( info );
+                    rc = true;
+                }
+            }
+        }
+        info = "checkConnetion status code=" + rc;
+        LOG.info( info );
+        return rc;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/687ee1ad/src/main/java/org/apache/directory/fortress/core/ldap/UnboundIdDataProvider.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/directory/fortress/core/ldap/UnboundIdDataProvider.java b/src/main/java/org/apache/directory/fortress/core/ldap/UnboundIdDataProvider.java
new file mode 100644
index 0000000..944dcb0
--- /dev/null
+++ b/src/main/java/org/apache/directory/fortress/core/ldap/UnboundIdDataProvider.java
@@ -0,0 +1,1277 @@
+/*
+ *   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
+ *
+ *     http://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.fortress.core.ldap;
+
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.directory.fortress.core.GlobalIds;
+import org.apache.directory.fortress.core.cfg.Config;
+import org.apache.directory.fortress.core.rbac.FortEntity;
+import org.apache.directory.fortress.core.rbac.Hier;
+import org.apache.directory.fortress.core.rbac.Relationship;
+import org.apache.directory.fortress.core.util.attr.VUtil;
+import org.apache.directory.fortress.core.util.time.CUtil;
+import org.apache.directory.fortress.core.util.time.Constraint;
+
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPAttribute;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPAttributeSet;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPConnection;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPControl;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPDN;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPEntry;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPException;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPModification;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPModificationSet;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPReferralException;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPSearchConstraints;
+import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPSearchResults;
+
+
+/**
+ * Abstract class contains methods to perform low-level entity to ldap persistence.  These methods are called by the
+ * Fortress DAO's, i.e. {@link org.apache.directory.fortress.core.rbac.dao.unboundid.UserDAO}. {@link org.apache.directory.fortress.core.rbac.dao.unboundid.RoleDAO}, {@link org.apache.directory.fortress.core.rbac.dao.unboundid.PermDAO}, ....
+ * These are low-level data utilities, very little if any data validations are performed here.
+ * <p/>
+ * This class is thread safe.
+ * <p/>
+ *
+ * @author Shawn McKinney
+ */
+public abstract class UnboundIdDataProvider
+{
+    private static final String OPENLDAP_PROXY_CONTROL = "2.16.840.1.113730.3.4.18";
+    private static final int MAX_DEPTH = 100;
+    private static final String CLS_NM = UnboundIdDataProvider.class.getName();
+    private static final Logger LOG = LoggerFactory.getLogger( CLS_NM );
+    private static final LdapCounters counters = new LdapCounters();
+
+
+    /**
+     * Given a contextId and a fortress param name return the LDAP dn.
+     *
+     * @param contextId is to determine what sub-tree to use.
+     * @param root      contains the fortress parameter name that corresponds with a particular LDAP container.
+     * @return String contains the dn to use for operation.
+     */
+    protected String getRootDn( String contextId, String root )
+    {
+        String szDn = Config.getProperty( root );
+        StringBuilder dn = new StringBuilder();
+
+        // The contextId must not be null, or "HOME" or "null"
+        if ( VUtil.isNotNullOrEmpty( contextId ) && !contextId.equalsIgnoreCase( GlobalIds.NULL )
+            && !contextId.equals( GlobalIds.HOME ) )
+        {
+            int idx = szDn.indexOf( Config.getProperty( GlobalIds.SUFFIX ) );
+
+            if ( idx != -1 )
+            {
+                // Found. The DN is ,ou=<contextId>,  
+                dn.append( szDn.substring( 0, idx - 1 ) ).append( "," ).append( GlobalIds.OU ).append( "=" )
+                    .append( contextId ).append( "," ).append( szDn.substring( idx ) );
+            }
+        }
+        else
+        {
+            dn.append( szDn );
+        }
+
+        return dn.toString();
+    }
+
+
+    /**
+     * Given a contextId return the LDAP dn that includes the suffix.
+     *
+     * @param contextId is to determine what sub-tree to use.
+     * @return String contains the dn to use for operation.
+     */
+    protected String getRootDn( String contextId )
+    {
+        StringBuilder dn = new StringBuilder();
+        if ( VUtil.isNotNullOrEmpty( contextId ) && !contextId.equalsIgnoreCase( GlobalIds.NULL )
+            && !contextId.equals( GlobalIds.HOME ) )
+        {
+            dn.append( GlobalIds.OU ).append( "=" ).append( contextId ).append( "," )
+                .append( Config.getProperty( GlobalIds.SUFFIX ) );
+        }
+        else
+        {
+            dn.append( Config.getProperty( GlobalIds.SUFFIX ) );
+        }
+        return dn.toString();
+    }
+
+
+    /**
+     * Read the ldap record from specified location.
+     *
+     * @param ld   handle to ldap connection.
+     * @param dn   contains ldap distinguished name.
+     * @param atrs array contains array names to pull back.
+     * @return ldap entry.
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected LDAPEntry read( LDAPConnection ld, String dn, String[] atrs )
+        throws LDAPException
+    {
+        counters.incrementRead();
+
+        return ld.read( dn, atrs );
+    }
+
+
+    /**
+     * Read the ldap record from specified location with user assertion.
+     *
+     * @param ld     handle to ldap connection.
+     * @param dn     contains ldap distinguished name.
+     * @param atrs   array contains array names to pull back.                                        , PoolMgr.ConnType.USER
+     * @param userDn string value represents the identity of user on who's behalf the request was initiated.  The value will be stored in openldap auditsearch record AuthZID's attribute.
+     * @return ldap entry.
+     * @throws LDAPException                in the event system error occurs.
+     * @throws UnsupportedEncodingException for search control errors.
+     */
+    protected LDAPEntry read( LDAPConnection ld, String dn, String[] atrs, String userDn )
+        throws LDAPException, UnsupportedEncodingException
+    {
+        counters.incrementRead();
+        LDAPControl proxyCtl = new LDAPControl( OPENLDAP_PROXY_CONTROL, true,
+            ( GlobalIds.DN + ": " + userDn ).getBytes( GlobalIds.UTF8 ) );
+        LDAPSearchConstraints opt = new LDAPSearchConstraints();
+        opt.setServerControls( proxyCtl );
+        return ld.read( dn, atrs, opt );
+    }
+
+
+    /**
+     * Add a new ldap entry to the directory.  Do not add audit context.
+     *
+     * @param ld    handle to ldap connection.
+     * @param entry contains data to add..
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void add( LDAPConnection ld, LDAPEntry entry )
+        throws LDAPException
+    {
+        counters.incrementAdd();
+        ld.add( entry );
+    }
+
+
+    /**
+     * Add a new ldap entry to the directory.  Add audit context.
+     *
+     * @param ld     handle to ldap connection.
+     * @param entry  contains data to add..
+     * @param entity contains audit context.
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void add( LDAPConnection ld, LDAPEntry entry, FortEntity entity )
+        throws LDAPException
+    {
+        counters.incrementAdd();
+
+        if ( GlobalIds.IS_AUDIT && entity != null && entity.getAdminSession() != null )
+        {
+            LDAPAttributeSet attrs = entry.getAttributeSet();
+
+            if ( VUtil.isNotNullOrEmpty( entity.getAdminSession().getInternalUserId() ) )
+            {
+                attrs.add( new LDAPAttribute( GlobalIds.FT_MODIFIER, entity.getAdminSession().getInternalUserId() ) );
+            }
+
+            if ( VUtil.isNotNullOrEmpty( entity.getModCode() ) )
+            {
+                attrs.add( new LDAPAttribute( GlobalIds.FT_MODIFIER_CODE, entity.getModCode() ) );
+            }
+
+            if ( VUtil.isNotNullOrEmpty( entity.getModId() ) )
+            {
+                attrs.add( new LDAPAttribute( GlobalIds.FT_MODIFIER_ID, entity.getModId() ) );
+            }
+        }
+
+        ld.add( entry );
+    }
+
+
+    /**
+     * Update exiting ldap entry to the directory.  Do not add audit context.
+     *
+     * @param ld   handle to ldap connection.
+     * @param dn   contains distinguished node of entry.
+     * @param mods contains data to modify.
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void modify( LDAPConnection ld, String dn, LDAPModificationSet mods )
+        throws LDAPException
+    {
+        counters.incrementMod();
+        ld.modify( dn, mods );
+    }
+
+
+    /**
+     * Update exiting ldap entry to the directory.  Add audit context.
+     *
+     * @param ld     handle to ldap connection.
+     * @param dn     contains distinguished node of entry.
+     * @param mods   contains data to modify.
+     * @param entity contains audit context.
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void modify( LDAPConnection ld, String dn, LDAPModificationSet mods, FortEntity entity )
+        throws LDAPException
+    {
+        counters.incrementMod();
+        audit( mods, entity );
+        ld.modify( dn, mods );
+    }
+
+
+    /**
+     * Delete exiting ldap entry from the directory.  Do not add audit context.
+     *
+     * @param ld handle to ldap connection.
+     * @param dn contains distinguished node of entry targeted for removal..
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void delete( LDAPConnection ld, String dn )
+        throws LDAPException
+    {
+        counters.incrementDelete();
+        ld.delete( dn );
+    }
+
+
+    /**
+     * Delete exiting ldap entry from the directory.  Add audit context.  This method will call modify prior to delete which will
+     * force corresponding audit record to be written to slapd access log.
+     *
+     * @param ld     handle to ldap connection.
+     * @param dn     contains distinguished node of entry targeted for removal..
+     * @param entity contains audit context.
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void delete( LDAPConnection ld, String dn, FortEntity entity )
+        throws LDAPException
+    {
+        counters.incrementDelete();
+        LDAPModificationSet mods = new LDAPModificationSet();
+        audit( mods, entity );
+        if ( mods.size() > 0 )
+            modify( ld, dn, mods );
+        ld.delete( dn );
+    }
+
+
+    /**
+     * Delete exiting ldap entry and all descendants from the directory.  Do not add audit context.
+     *
+     * @param ld handle to ldap connection.
+     * @param dn contains distinguished node of entry targeted for removal..
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void deleteRecursive( LDAPConnection ld, String dn )
+        throws LDAPException
+    {
+        int recursiveCount = 0;
+        deleteRecursive( dn, ld, recursiveCount );
+    }
+
+
+    /**
+     * Delete exiting ldap entry and all descendants from the directory.  Add audit context.  This method will call modify prior to delete which will
+     * force corresponding audit record to be written to slapd access log.
+     *
+     * @param ld     handle to ldap connection.
+     * @param dn     contains distinguished node of entry targeted for removal..
+     * @param entity contains audit context.
+     * @throws LDAPException in the event system error occurs.
+     */
+    protected void deleteRecursive( LDAPConnection ld, String dn, FortEntity entity )
+        throws LDAPException
+    {
+        LDAPModificationSet mods = new LDAPModificationSet();
+        audit( mods, entity );
+        if ( mods.size() > 0 )
+            modify( ld, dn, mods );
+        deleteRecursive( ld, dn );
+    }
+
+
+    /**
+     * Used to recursively remove all nodes up to record pointed to by dn attribute.
+     *
+     * @param dn             contains distinguished node of entry targeted for removal..
+     * @param ld             handle to ldap connection.
+     * @param recursiveCount keeps track of how many iterations have been performed.
+     * @throws LDAPException in the event system error occurs.
+     */
+    private void deleteRecursive( String dn, LDAPConnection ld, int recursiveCount )
+        throws LDAPException
+    {
+        String method = "deleteRecursive";
+        // Sanity check - only allow max tree depth of 100
+        if ( recursiveCount++ > MAX_DEPTH )
+        {
+            // too deep inside of a recursive sequence;
+            String error = "." + method + " dn [" + dn + "] depth error in recursive";
+            throw new LDAPException( error, LDAPException.OPERATION_ERROR );
+        }
+
+        String theDN;
+        // Find child nodes
+        LDAPSearchResults res = search( ld, dn, LDAPConnection.SCOPE_ONE, "objectclass=*", GlobalIds.NO_ATRS, false, 0 );
+
+        // Iterate over all entries under this entry
+        while ( res.hasMoreElements() )
+        {
+            try
+            {
+                // Next directory entry
+                LDAPEntry entry = res.next();
+                theDN = entry.getDN();
+                // continue down:
+                deleteRecursive( theDN, ld, recursiveCount );
+                recursiveCount--;
+            }
+            catch ( LDAPReferralException lre )
+            {
+                // cannot continue;
+                String error = "." + method + " dn [" + dn + "] caught LDAPReferralException="
+                    + lre.errorCodeToString() + "=" + lre.getLDAPErrorMessage();
+                throw new LDAPException( error, lre.getLDAPResultCode() );
+            }
+            catch ( LDAPException ldape )
+            {
+                // cannot continue;
+                String error = "." + method + " dn [" + dn + "] caught LDAPException="
+                    + ldape.errorCodeToString() + "=" + ldape.getLDAPErrorMessage();
+                throw new LDAPException( error, ldape.getLDAPResultCode() );
+            }
+        }
+        // delete the node:
+        counters.incrementDelete();
+        delete( ld, dn );
+    }
+
+
+    /**
+     * Add the audit context variables to the modfication set.
+     *
+     * @param mods   used to update ldap attributes.
+     * @param entity contains audit context.
+     * @throws LDAPException in the event of error with ldap client.
+     */
+    private void audit( LDAPModificationSet mods, FortEntity entity )
+    {
+        if ( GlobalIds.IS_AUDIT && entity != null && entity.getAdminSession() != null )
+        {
+            if ( VUtil.isNotNullOrEmpty( entity.getAdminSession().getInternalUserId() ) )
+            {
+                LDAPAttribute modifier = new LDAPAttribute( GlobalIds.FT_MODIFIER, entity.getAdminSession()
+                    .getInternalUserId() );
+                mods.add( LDAPModification.REPLACE, modifier );
+            }
+            if ( VUtil.isNotNullOrEmpty( entity.getModCode() ) )
+            {
+                LDAPAttribute modCode = new LDAPAttribute( GlobalIds.FT_MODIFIER_CODE, entity.getModCode() );
+                mods.add( LDAPModification.REPLACE, modCode );
+            }
+            if ( VUtil.isNotNullOrEmpty( entity.getModId() ) )
+            {
+                LDAPAttribute modId = new LDAPAttribute( GlobalIds.FT_MODIFIER_ID, entity.getModId() );
+                mods.add( LDAPModification.REPLACE, modId );
+            }
+        }
+    }
+
+
+    /**
+     * Perform normal ldap search accepting default batch size.
+     *
+     * @param ld        is LDAPConnection object used for all communication with host.
+     * @param baseDn    contains address of distinguished name to begin ldap search
+     * @param scope     indicates depth of search starting at basedn.  0 (base dn), 1 (one level down) or 2 (infinite) are valid values.
+     * @param filter    contains the search criteria
+     * @param atrs      is the requested list of attritubutes to return from directory search.
+     * @param attrsOnly if true pull back attribute names only.
+     * @return result set containing ldap entries returned from directory.
+     * @throws LDAPException thrown in the event of error in ldap client or server code.
+     */
+    protected LDAPSearchResults search( LDAPConnection ld,
+        String baseDn,
+        int scope,
+        String filter,
+        String[] atrs,
+        boolean attrsOnly )
+        throws LDAPException
+    {
+        counters.incrementSearch();
+        LDAPSearchResults result;
+        result = ld.search( baseDn, scope, filter, atrs, attrsOnly );
+        return result;
+    }
+
+
+    /**
+     * Perform normal ldap search specifying default batch size.
+     *
+     * @param ld        is LDAPConnection object used for all communication with host.
+     * @param baseDn    contains address of distinguished name to begin ldap search
+     * @param scope     indicates depth of search starting at basedn.  0 (base dn), 1 (one level down) or 2 (infinite) are valid values.
+     * @param filter    contains the search criteria
+     * @param atrs      is the requested list of attritubutes to return from directory search.
+     * @param attrsOnly if true pull back attribute names only.
+     * @param batchSize Will block until this many entries are ready to return from server.  0 indicates to block until all results are ready.
+     * @return result set containing ldap entries returned from directory.
+     * @throws LDAPException thrown in the event of error in ldap client or server code.
+     */
+    protected LDAPSearchResults search( LDAPConnection ld,
+        String baseDn,
+        int scope,
+        String filter,
+        String[] atrs,
+        boolean attrsOnly,
+        int batchSize )
+        throws LDAPException
+    {
+        counters.incrementSearch();
+        LDAPSearchResults result;
+        LDAPSearchConstraints ldCons = new LDAPSearchConstraints();
+        // Returns the maximum number of search results that are to be returned; 0 means there is no limit.
+        ldCons.setMaxResults( 0 );
+        ldCons.setBatchSize( batchSize );
+        result = ld.search( baseDn, scope, filter, atrs, attrsOnly, ldCons );
+        return result;
+    }
+
+
+    /**
+     * Perform normal ldap search specifying default batch size and max entries to return.
+     *
+     * @param ld         is LDAPConnection object used for all communication with host.
+     * @param baseDn     contains address of distinguished name to begin ldap search
+     * @param scope      indicates depth of search starting at basedn.  0 (base dn), 1 (one level down) or 2 (infinite) are valid values.
+     * @param filter     contains the search criteria
+     * @param atrs       is the requested list of attritubutes to return from directory search.
+     * @param attrsOnly  if true pull back attribute names only.
+     * @param batchSize  Will block until this many entries are ready to return from server.  0 indicates to block until all results are ready.
+     * @param maxEntries specifies the maximum number of entries to return in this search query.
+     * @return result set containing ldap entries returned from directory.
+     * @throws LDAPException thrown in the event of error in ldap client or server code.
+     */
+    protected LDAPSearchResults search( LDAPConnection ld,
+        String baseDn,
+        int scope,
+        String filter,
+        String[] atrs,
+        boolean attrsOnly,
+        int batchSize,
+        int maxEntries )
+        throws LDAPException
+    {
+        counters.incrementSearch();
+        LDAPSearchResults result;
+        LDAPSearchConstraints ldCons = new LDAPSearchConstraints();
+        // Returns the maximum number of search results that are to be returned;
+        ldCons.setMaxResults( maxEntries );
+        ldCons.setBatchSize( batchSize );
+        result = ld.search( baseDn, scope, filter, atrs, attrsOnly, ldCons );
+        return result;
+    }
+
+
+    /**
+     * This method will search the directory and return at most one record.  If more than one record is found
+     * an ldap exception will be thrown.
+     *
+     * @param ld        is LDAPConnection object used for all communication with host.
+     * @param baseDn    contains address of distinguished name to begin ldap search
+     * @param scope     indicates depth of search starting at basedn.  0 (base dn), 1 (one level down) or 2 (infinite) are valid values.
+     * @param filter    contains the search criteria
+     * @param atrs      is the requested list of attritubutes to return from directory search.
+     * @param attrsOnly if true pull back attribute names only.
+     * @return entry   containing target ldap node.
+     * @throws LDAPException thrown in the event of error in ldap client or server code.
+     */
+    protected LDAPEntry searchNode( LDAPConnection ld,
+        String baseDn,
+        int scope, String filter,
+        String[] atrs,
+        boolean attrsOnly )
+        throws LDAPException
+    {
+        LDAPSearchResults result = ld.search( baseDn, scope,
+            filter, atrs, attrsOnly );
+        if ( result.getCount() > 1 )
+        {
+            throw new LDAPException( "searchNode failed to return unique record for LDAP search of base DN ["
+                + baseDn + "] filter [" + filter + "]" );
+        }
+        return result.next();
+    }
+
+
+    /**
+     * This search method uses OpenLDAP Proxy Authorization Control to assert arbitrary user identity onto connection.
+     *
+     * @param ld        is LDAPConnection object used for all communication with host.
+     * @param baseDn    contains address of distinguished name to begin ldap search
+     * @param scope     indicates depth of search starting at basedn.  0 (base dn), 1 (one level down) or 2 (infinite) are valid values.
+     * @param filter    contains the search criteria
+     * @param atrs      is the requested list of attritubutes to return from directory search.
+     * @param attrsOnly if true pull back attribute names only.
+     * @param userDn    string value represents the identity of user on who's behalf the request was initiated.  The value will be stored in openldap auditsearch record AuthZID's attribute.
+     * @return entry   containing target ldap node.
+     * @throws LDAPException thrown in the event of error in ldap client or server code.
+     */
+    protected LDAPEntry searchNode( LDAPConnection ld,
+        String baseDn,
+        int scope,
+        String filter,
+        String[] atrs,
+        boolean attrsOnly,
+        String userDn )
+        throws LDAPException, UnsupportedEncodingException
+    {
+        counters.incrementSearch();
+        LDAPControl proxyCtl = new LDAPControl( OPENLDAP_PROXY_CONTROL, true,
+            ( GlobalIds.DN + ": " + userDn ).getBytes( GlobalIds.UTF8 ) );
+        LDAPSearchConstraints opt = new LDAPSearchConstraints();
+        opt.setServerControls( proxyCtl );
+        LDAPSearchResults result = ld.search( baseDn, scope, filter, atrs, attrsOnly, opt );
+        if ( result.getCount() > 1 )
+        {
+            throw new LDAPException( "searchNode failed to return unique record for LDAP search of base DN ["
+                + baseDn + "] filter [" + filter + "]" );
+        }
+        return result.next();
+    }
+
+
+    /**
+     * This method uses the compare ldap func to assert audit record into the directory server's configured audit logger.
+     *
+     * @param ld        is LDAPConnection object used for all communication with host.
+     * @param dn        contains address of distinguished name to begin ldap search
+     * @param userDn    dn for user node
+     * @param attribute attribute used for compare
+     * @return true if compare operation succeeds
+     * @throws LDAPException                thrown in the event of error in ldap client or server code.
+     * @throws UnsupportedEncodingException in the event the server cannot perform the operation.
+     */
+    protected boolean compareNode( LDAPConnection ld,
+        String dn,
+        String userDn,
+        LDAPAttribute attribute )
+        throws LDAPException, UnsupportedEncodingException
+    {
+        counters.incrementCompare();
+        LDAPControl proxyCtl = new LDAPControl( OPENLDAP_PROXY_CONTROL, true,
+            ( GlobalIds.DN + ": " + userDn ).getBytes( GlobalIds.UTF8 ) );
+        LDAPSearchConstraints opt = new LDAPSearchConstraints();
+        opt.setServerControls( proxyCtl );
+        return ld.compare( dn, attribute, opt );
+    }
+
+
+    /**
+     * Method wraps ldap client to return multi-occurring attribute values by name within a given entry and returns as a list of strings.
+     *
+     * @param entry         contains the target ldap entry.
+     * @param attributeName name of ldap attribute to retrieve.
+     * @return List of type string containing attribute values.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected List<String> getAttributes( LDAPEntry entry, String attributeName )
+    {
+        List<String> attrValues = new ArrayList<>();
+        LDAPAttribute attr;
+        Enumeration values;
+        attr = entry.getAttribute( attributeName );
+        if ( attr != null )
+        {
+            values = attr.getStringValues();
+        }
+        else
+        {
+            return null;
+        }
+        if ( values != null )
+        {
+            while ( values.hasMoreElements() )
+            {
+                attrValues.add( ( String ) values.nextElement() );
+            }
+        }
+        return attrValues;
+    }
+
+
+    protected byte[] getPhoto( LDAPEntry entry, String attributeName )
+    {
+        byte[] photo = null;
+        LDAPAttribute attr = entry.getAttribute( attributeName );
+        if ( attr != null )
+        {
+            // Get the values as byte arrays
+            Enumeration enumVals =
+                attr.getByteValues();
+            // Get the first value - if there's more
+            // than one
+            if ( enumVals.hasMoreElements() )
+            {
+                photo =
+                    ( byte[] ) enumVals.nextElement();
+            }
+        }
+        return photo;
+    }
+
+
+    /**
+     * Method wraps ldap client to return multi-occurring attribute values by name within a given entry and returns as a set of strings.
+     *
+     * @param entry         contains the target ldap entry.
+     * @param attributeName name of ldap attribute to retrieve.
+     * @return List of type string containing attribute values.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected Set<String> getAttributeSet( LDAPEntry entry, String attributeName )
+    {
+        // create Set with case insensitive comparator:
+        Set<String> attrValues = new TreeSet<>( String.CASE_INSENSITIVE_ORDER );
+        LDAPAttribute attr;
+        Enumeration values;
+        attr = entry.getAttribute( attributeName );
+        if ( attr != null )
+        {
+            values = attr.getStringValues();
+        }
+        else
+        {
+            return null;
+        }
+        if ( values != null )
+        {
+            while ( values.hasMoreElements() )
+            {
+                attrValues.add( ( String ) values.nextElement() );
+            }
+        }
+        return attrValues;
+    }
+
+
+    /**
+     * Method wraps ldap client to return multi-occurring attribute values by name within a given entry and return as a list of type {@link org.apache.directory.fortress.core.rbac.Relationship}.
+     *
+     * @param entry         contains the target ldap entry.
+     * @param attributeName name of ldap attribute to retrieve.
+     * @return List of type {@link org.apache.directory.fortress.core.rbac.Relationship} containing parent-child relationships.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected List<Relationship> getRelationshipAttributes( LDAPEntry entry, String attributeName )
+    {
+        List<Relationship> attrValues = new ArrayList<>();
+        LDAPAttribute attr;
+        Enumeration values;
+
+        attr = entry.getAttribute( attributeName );
+        if ( attr != null )
+        {
+            values = attr.getStringValues();
+        }
+        else
+        {
+            return null;
+        }
+        if ( values != null )
+        {
+            while ( values.hasMoreElements() )
+            {
+                String edge = ( String ) values.nextElement();
+                int indx = edge.indexOf( GlobalIds.PROP_SEP );
+                if ( indx >= 1 )
+                {
+                    // This LDAP attr is stored as a name-value pair separated by a ':'.
+                    // Separate the parent from the child:
+                    String child = edge.substring( 0, indx );
+                    String parent = edge.substring( indx + 1 );
+
+                    // Load the parent/child relationship values into a helper class:
+                    Relationship rel = new Relationship( child, parent );
+                    attrValues.add( rel );
+                }
+                else
+                {
+                    String warning = "getRelAttributes detected incorrect data in role relationship field: "
+                        + edge;
+                    LOG.warn( warning );
+                }
+            }
+        }
+        return attrValues;
+    }
+
+
+    /**
+     * Method wraps ldap client to return attribute value by name within a given entry and returns as a string.
+     *
+     * @param entry         contains the target ldap entry.
+     * @param attributeName name of ldap attribute to retrieve.
+     * @return value contained in a string variable.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected String getAttribute( LDAPEntry entry, String attributeName )
+    {
+        String attrValue = null;
+        LDAPAttribute attr;
+        Enumeration values;
+        attr = entry.getAttribute( attributeName );
+        if ( attr != null )
+        {
+            values = attr.getStringValues();
+        }
+        else
+        {
+            return null;
+        }
+        if ( values != null )
+        {
+            attrValue = ( String ) values.nextElement();
+        }
+        return attrValue;
+    }
+
+
+    /**
+     * Method will retrieve the relative distinguished name from a distinguished name variable.
+     *
+     * @param dn contains ldap distinguished name.
+     * @return rDn as string.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected String getRdn( String dn )
+    {
+        String[] dnList;
+        dnList = LDAPDN.explodeDN( dn, true );
+        return dnList[0];
+    }
+
+
+    /**
+     * Create multi-occurring ldap attribute given array of strings and attribute name.
+     *
+     * @param name   contains attribute name to create.
+     * @param values array of string that contains attribute values.
+     * @return LDAPAttribute containing multi-occurring attribute set.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected LDAPAttribute createAttributes( String name, String values[] )
+        throws LDAPException
+    {
+        LDAPAttribute attr = new LDAPAttribute( name );
+        for ( String value : values )
+        {
+            encodeSafeText( value, value.length() );
+            attr.addValue( value );
+        }
+        return attr;
+    }
+
+
+    /**
+     * Create ldap attribute given an attribute name and value.
+     *
+     * @param name  contains attribute name to create.
+     * @param value string contains attribute value.
+     * @return LDAPAttribute containing new ldap attribute.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected LDAPAttribute createAttribute( String name, String value )
+        throws LDAPException
+    {
+        LDAPAttribute attr = new LDAPAttribute( name );
+        encodeSafeText( value, value.length() );
+        attr.addValue( value );
+        return attr;
+    }
+
+
+    /**
+     * Convert constraint from raw ldap format to application entity.
+     *
+     * @param le         ldap entry containing constraint.
+     * @param ftDateTime reference to {@link org.apache.directory.fortress.util.time.Constraint} containing formatted data.
+     * @throws LDAPException in the event of ldap client error.
+     */
+    protected void unloadTemporal( LDAPEntry le, Constraint ftDateTime )
+    {
+        String szRawData = getAttribute( le, GlobalIds.CONSTRAINT );
+
+        if ( szRawData != null && szRawData.length() > 0 )
+        {
+            CUtil.setConstraint( szRawData, ftDateTime );
+        }
+    }
+
+
+    /**
+     * Given an ldap attribute name and a list of attribute values, construct an ldap attribute set to be added to directory.
+     *
+     * @param list     list of type string containing attribute values to load into attribute set.
+     * @param attrs    contains ldap attribute set targeted for adding.
+     * @param attrName name of ldap attribute being added.
+     */
+    protected void loadAttrs( List<String> list, LDAPAttributeSet attrs, String attrName )
+    {
+        if ( list != null && list.size() > 0 )
+        {
+            LDAPAttribute attr = null;
+            for ( String val : list )
+            {
+                if ( attr == null )
+                {
+                    attr = new LDAPAttribute( attrName, val );
+                }
+                else
+                {
+                    attr.addValue( val );
+                }
+            }
+            if ( attr != null )
+            {
+                attrs.add( attr );
+            }
+        }
+    }
+
+
+    /**
+     * Given a collection of {@link org.apache.directory.fortress.core.rbac.Relationship}, convert to raw data name-value format and load into ldap attribute set in preparation for ldap add.
+     *
+     * @param list     contains List of type {@link org.apache.directory.fortress.core.rbac.Relationship} targeted for adding to ldap.
+     * @param attrs    collection of ldap attributes containing parent-child relationships in raw ldap format.
+     * @param attrName contains the name of the ldap attribute to be added.
+     */
+    protected void loadRelationshipAttrs( List<Relationship> list, LDAPAttributeSet attrs, String attrName )
+    {
+        if ( list != null )
+        {
+            LDAPAttribute attr = null;
+            for ( Relationship rel : list )
+            {
+                // This LDAP attr is stored as a name-value pair separated by a ':'.
+                if ( attr == null )
+                {
+                    attr = new LDAPAttribute( attrName, rel.getChild() + GlobalIds.PROP_SEP + rel.getParent() );
+                }
+                else
+                {
+                    attr.addValue( rel.getChild() + GlobalIds.PROP_SEP + rel.getParent() );
+                }
+            }
+            if ( attr != null )
+            {
+                attrs.add( attr );
+            }
+        }
+    }
+
+
+    /**
+     * Given an ldap attribute name and a set of attribute values, construct an ldap attribute set to be added to directory.
+     *
+     * @param values   set of type string containing attribute values to load into attribute set.
+     * @param attrs    contains ldap attribute set targeted for adding.
+     * @param attrName name of ldap attribute being added.
+     */
+    protected void loadAttrs( Set<String> values, LDAPAttributeSet attrs, String attrName )
+    {
+        if ( values != null && values.size() > 0 )
+        {
+            LDAPAttribute attr = null;
+            for ( String value : values )
+            {
+                if ( attr == null )
+                {
+                    attr = new LDAPAttribute( attrName, value );
+                }
+                else
+                {
+                    attr.addValue( value );
+                }
+            }
+            if ( attr != null )
+            {
+                attrs.add( attr );
+            }
+        }
+    }
+
+
+    /**
+     * Given a multi-occurring ldap attribute name and a list of attribute values, construct an ldap modification set to be updated in directory.
+     * This function will replace all existing attributes with new values.
+     *
+     * @param list     list of type string containing attribute values to load into modification set.
+     * @param mods     contains ldap modification set targeted for updating.
+     * @param attrName name of ldap attribute being modified.
+     */
+    protected void loadAttrs( List<String> list, LDAPModificationSet mods, String attrName )
+    {
+        loadAttrs( list, mods, attrName, true );
+    }
+
+    /**
+     * Given a multi-occurring ldap attribute name and a list of attribute values, construct an ldap modification set to be updated in directory.
+     *
+     * @param list     list of type string containing attribute values to load into modification set.
+     * @param mods     contains ldap modification set targeted for updating.
+     * @param attrName name of ldap attribute being modified.
+     * @param replace boolean value if true will replace existing attributes with new..
+     */
+    protected void loadAttrs( List<String> list, LDAPModificationSet mods, String attrName, boolean replace )
+    {
+        if ( list != null && list.size() > 0 )
+        {
+            LDAPAttribute attr = new LDAPAttribute( attrName );
+            if(replace)
+            {
+                mods.add( LDAPModification.REPLACE, attr );
+            }
+
+            for ( String val : list )
+            {
+                attr = new LDAPAttribute( attrName, val );
+                mods.add( LDAPModification.ADD, attr );
+            }
+        }
+    }
+
+    /**
+     * Given a collection of {@link org.apache.directory.fortress.core.rbac.Relationship}s, convert to raw data name-value format and load into ldap modification set in preparation for ldap modify.
+     *
+     * @param list     contains List of type {@link org.apache.directory.fortress.core.rbac.Relationship} targeted for updating in ldap.
+     * @param mods     ldap modification set containing parent-child relationships in raw ldap format.
+     * @param attrName contains the name of the ldap attribute to be updated.
+     * @param op       specifies type of mod: {@link Hier.Op#ADD}, {@link org.apache.directory.fortress.core.rbac.Hier.Op#MOD}, {@link Hier.Op#REM}
+     */
+    protected void loadRelationshipAttrs( List<Relationship> list, LDAPModificationSet mods, String attrName, Hier.Op op )
+    {
+        if ( list != null )
+        {
+            LDAPAttribute attr;
+            for ( Relationship rel : list )
+            {
+                // This LDAP attr is stored as a name-value pair separated by a ':'.
+                attr = new LDAPAttribute( attrName, rel.getChild() + GlobalIds.PROP_SEP + rel.getParent() );
+                switch ( op )
+                {
+                    case ADD:
+                        mods.add( LDAPModification.ADD, attr );
+                        break;
+                    case MOD:
+                        mods.add( LDAPModification.REPLACE, attr );
+                        break;
+                    case REM:
+                        mods.add( LDAPModification.DELETE, attr );
+                        break;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Given an ldap attribute name and a set of attribute values, construct an ldap modification set to be updated in directory.
+     *
+     * @param values   set of type string containing attribute values to load into modification set.
+     * @param mods     contains ldap modification set targeted for updating.
+     * @param attrName name of ldap attribute being updated.
+     */
+    protected void loadAttrs( Set<String> values, LDAPModificationSet mods, String attrName )
+    {
+        if ( values != null && values.size() > 0 )
+        {
+            LDAPAttribute attr = new LDAPAttribute( attrName );
+            mods.add( LDAPModification.REPLACE, attr );
+            for ( String value : values )
+            {
+                attr = new LDAPAttribute( attrName, value );
+                mods.add( LDAPModification.ADD, attr );
+            }
+        }
+    }
+
+
+    /**
+     * Given a collection of {@link java.util.Properties}, convert to raw data name-value format and load into ldap modification set in preparation for ldap modify.
+     *
+     * @param props    contains {@link java.util.Properties} targeted for updating in ldap.
+     * @param mods     ldap modification set containing name-value pairs in raw ldap format.
+     * @param attrName contains the name of the ldap attribute to be updated.
+     * @param replace  boolean variable, if set to true use {@link LDAPModification#REPLACE} else {@link LDAPModification#ADD}.
+     */
+    protected void loadProperties( Properties props, LDAPModificationSet mods, String attrName, boolean replace )
+    {
+        loadProperties( props, mods, attrName, GlobalIds.PROP_SEP, replace );
+    }
+
+
+    /**
+     * Given a collection of {@link java.util.Properties}, convert to raw data name-value format and load into ldap modification set in preparation for ldap modify.
+     *
+     * @param props    contains {@link java.util.Properties} targeted for updating in ldap.
+     * @param mods     ldap modification set containing name-value pairs in raw ldap format.
+     * @param attrName contains the name of the ldap attribute to be updated.
+     * @param separator contains the delimiter for the property.
+     * @param replace  boolean variable, if set to true use {@link LDAPModification#REPLACE} else {@link LDAPModification#ADD}.
+     */
+    protected void loadProperties( Properties props, LDAPModificationSet mods, String attrName, char separator, boolean replace )
+    {
+        if ( props != null && props.size() > 0 )
+        {
+            LDAPAttribute prop = new LDAPAttribute( attrName );
+            if ( replace )
+                mods.add( LDAPModification.REPLACE, prop );
+
+            for ( Enumeration e = props.propertyNames(); e.hasMoreElements(); )
+            {
+                String key = ( String ) e.nextElement();
+                String val = props.getProperty( key );
+                // This LDAP attr is stored as a name-value pair separated by a ':'.
+                prop = new LDAPAttribute( attrName, key + separator + val );
+                mods.add( LDAPModification.ADD, prop );
+            }
+        }
+    }
+
+
+    /**
+     * Given a collection of {@link java.util.Properties}, convert to raw data name-value format and load into ldap modification set in preparation for ldap modify.
+     *
+     * @param props    contains {@link java.util.Properties} targeted for removal from ldap.
+     * @param mods     ldap modification set containing name-value pairs in raw ldap format to be removed.
+     * @param attrName contains the name of the ldap attribute to be removed.
+     */
+    protected void removeProperties( Properties props, LDAPModificationSet mods, String attrName )
+    {
+        if ( props != null && props.size() > 0 )
+        {
+            LDAPAttribute prop;
+            for ( Enumeration e = props.propertyNames(); e.hasMoreElements(); )
+            {
+                String key = ( String ) e.nextElement();
+                String val = props.getProperty( key );
+                // This LDAP attr is stored as a name-value pair separated by a ':'.
+                prop = new LDAPAttribute( attrName, key + GlobalIds.PROP_SEP + val );
+                mods.add( LDAPModification.DELETE, prop );
+            }
+        }
+    }
+
+
+    /**
+     * Given a collection of {@link java.util.Properties}, convert to raw data name-value format and load into ldap modification set in preparation for ldap add.
+     *
+     * @param props    contains {@link java.util.Properties} targeted for adding to ldap.
+     * @param attrs    ldap attribute set containing name-value pairs in raw ldap format.
+     * @param attrName contains the name of the ldap attribute to be added.
+     */
+    protected void loadProperties( Properties props, LDAPAttributeSet attrs, String attrName )
+    {
+        loadProperties( props, attrs, attrName, ':' );
+    }
+
+
+    /**
+     * Given a collection of {@link java.util.Properties}, convert to raw data name-value format and load into ldap modification set in preparation for ldap add.
+     *
+     * @param props    contains {@link java.util.Properties} targeted for adding to ldap.
+     * @param attrs    ldap attribute set containing name-value pairs in raw ldap format.
+     * @param attrName contains the name of the ldap attribute to be added.
+     */
+    protected void loadProperties( Properties props, LDAPAttributeSet attrs, String attrName, char separator )
+    {
+        if ( props != null && props.size() > 0 )
+        {
+            LDAPAttribute attr = null;
+            for ( Enumeration e = props.propertyNames(); e.hasMoreElements(); )
+            {
+                // This LDAP attr is stored as a name-value pair separated by a ':'.
+                String key = ( String ) e.nextElement();
+                String val = props.getProperty( key );
+                String prop = key + separator + val;
+                if ( attr == null )
+                {
+                    attr = new LDAPAttribute( attrName, prop );
+                }
+                else
+                {
+                    attr.addValue( prop );
+                }
+            }
+            if ( attr != null )
+            {
+                attrs.add( attr );
+            }
+        }
+    }
+
+
+    /**
+     * @param value
+     * @param validLen
+     * @return String containing encoded data.
+     * @throws LDAPException
+     */
+    protected String encodeSafeText( String value, int validLen )
+        throws LDAPException
+    {
+        if ( VUtil.isNotNullOrEmpty( value ) )
+        {
+            int length = value.length();
+            if ( length > validLen )
+            {
+                String error = "encodeSafeText value [" + value + "] invalid length [" + length + "]";
+                throw new LDAPException( error, LDAPException.PARAM_ERROR );
+            }
+            if ( GlobalIds.LDAP_FILTER_SIZE_FOUND )
+            {
+                value = VUtil.escapeLDAPSearchFilter( value );
+            }
+        }
+        return value;
+    }
+
+
+    /**
+     * Calls the PoolMgr to perform an LDAP bind for a user/password combination.  This function is valid
+     * if and only if the user entity is a member of the USERS data set.  The LDAP directory
+     * will return the OpenLDAP PW Policy control.
+     *
+     * @param ld       connection to ldap server.
+     * @param userId   contains the LDAP dn to the user entry.
+     * @param password contains the password in clear text.
+     * @return boolean value - true if bind successful, false otherwise.
+     * @throws LDAPException in the event of LDAP error.
+     */
+    protected boolean bind( LDAPConnection ld, String userId, char[] password )
+        throws LDAPException
+    {
+        counters.incrementBind();
+        return PoolMgr.bind( ld, userId, password );
+    }
+
+
+    /**
+     * Calls the PoolMgr to close the Admin LDAP connection.
+     *
+     * @param ld handle to ldap connection object.
+     */
+    protected void closeAdminConnection( LDAPConnection ld )
+    {
+        PoolMgr.closeConnection( ld, PoolMgr.ConnType.ADMIN );
+    }
+
+
+    /**
+     * Calls the PoolMgr to close the User LDAP connection.
+     *
+     * @param ld handle to ldap connection object.
+     */
+    protected void closeUserConnection( LDAPConnection ld )
+    {
+        PoolMgr.closeConnection( ld, PoolMgr.ConnType.USER );
+    }
+
+
+    /**
+     * Calls the PoolMgr to close the Log LDAP connection.
+     *
+     * @param ld handle to ldap connection object.
+     */
+    protected void closeLogConnection( LDAPConnection ld )
+    {
+        PoolMgr.closeConnection( ld, PoolMgr.ConnType.LOG );
+    }
+
+
+    /**
+     * Calls the PoolMgr to get a User connection to the LDAP server.
+     *
+     * @return ldap connection.
+     * @throws LDAPException
+     */
+    protected LDAPConnection getUserConnection() throws LDAPException
+    {
+        return PoolMgr.getConnection( PoolMgr.ConnType.USER );
+    }
+
+
+    /**
+     * Calls the PoolMgr to get an Admin connection to the LDAP server.
+     *
+     * @return ldap connection.
+     * @throws LDAPException
+     */
+    protected LDAPConnection getAdminConnection() throws LDAPException
+    {
+        return PoolMgr.getConnection( PoolMgr.ConnType.ADMIN );
+    }
+
+
+    /**
+     * Calls the PoolMgr to get a Log connection to the LDAP server.
+     *
+     * @return ldap connection.
+     * @throws LDAPException
+     */
+    protected LDAPConnection getLogConnection() throws LDAPException
+    {
+        return PoolMgr.getConnection( PoolMgr.ConnType.LOG );
+    }
+
+
+    /**
+     * Return to call reference to dao counter object with running totals for ldap operations add, mod, delete, search, etc.
+     *
+     * @return {@link LdapCounters} contains long values of atomic ldap operations for current running process.
+     */
+    public static LdapCounters getLdapCounters()
+    {
+        return counters;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/directory-fortress-core/blob/687ee1ad/src/main/java/org/apache/directory/fortress/core/ldap/container/OrganizationalUnit.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/directory/fortress/core/ldap/container/OrganizationalUnit.java b/src/main/java/org/apache/directory/fortress/core/ldap/container/OrganizationalUnit.java
new file mode 100755
index 0000000..d701b34
--- /dev/null
+++ b/src/main/java/org/apache/directory/fortress/core/ldap/container/OrganizationalUnit.java
@@ -0,0 +1,179 @@
+/*
+ *   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
+ *
+ *     http://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.fortress.core.ldap.container;
+
+
+/**
+ * This class contains the container node for the OpenLDAP Directory Information Tree.  A container node is used to
+ * group other related nodes, i.e. 'ou=People' or 'ou'Roles'.
+ * <br />The organizational unit object class is 'organizationalUnit' <br />
+ * <p/>
+ * organizational unit structural object class is used to organize groups of nodes within the DIT.
+ * <ul>
+ * <li>  ------------------------------------------
+ * <li> <code>#Standard object class from RFC2256</code>
+ * <li> <code>objectclass ( 2.5.6.5 NAME 'organizationalUnit'</code>
+ * <li> <code>DESC 'RFC2256: an organizational unit'</code>
+ * <li> <code>SUP top STRUCTURAL</code>
+ * <li> <code>MUST ou</code>
+ * <li> <code>MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $</code>
+ * <li> <code>x121Address $ registeredAddress $ destinationIndicator $</code>
+ * <li> <code>preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $</code>
+ * <li> <code>telephoneNumber $ internationaliSDNNumber $</code>
+ * <li> <code>facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $</code>
+ * <li> <code>postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )</code>
+ * <li>  ------------------------------------------
+ * </ul>
+ * <p/>
+
+ *
+ * @author Shawn McKinney
+ */
+public class OrganizationalUnit
+{
+    private String name;
+    private String parent;
+    private String description;
+    private String contextId = "";
+
+
+    public String getContextId()
+    {
+        return this.contextId;
+    }
+
+
+    public void setContextId( String contextId )
+    {
+        this.contextId = contextId;
+    }
+
+
+    /**
+     * Generate instance of organizational unit object to be loaded as container node.
+     *
+     * @param name        required attribute must be unique for rDn level and maps to 'ou' attribute in 'organizationalUnit' object class.
+     * @param description maps optional attribute maps to name in 'organizationalUnit' object class.
+     */
+    public OrganizationalUnit( String name, String description )
+    {
+        this.name = name;
+        this.description = description;
+    }
+
+
+    /**
+     * Default constructor generates instance of organizational unit object to be loaded as container node.
+     * The object cannot be used until 'name' value is set.
+     */
+    public OrganizationalUnit()
+    {
+    }
+
+
+    /**
+     * Get the required name attribute from the entity.  This attribute must be unique for the level of tree it is
+     * set.
+     *
+     * @return required attribute must be unique for rDn level and maps to 'ou' attribute in 'organizationalUnit' object class.
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+
+    /**
+     * Set the required name attribute in the entity.  This attribute must be unique for the level of tree it is
+     * set.
+     *
+     * @param name is required attribute and must be unique for rDn level and maps to 'ou' attribute in 'organizationalUnit' object class.
+     */
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
+
+    /**
+     * Get the description for the organizational unit object.  This value is not required or constrained
+     * but is validated on reasonability.
+     *
+     * @return field maps to same name attribute on 'organizationalUnit'.
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+
+    /**
+     * Set the description for the organizational unit object.  This value is not required or constrained
+     * but is validated on reasonability.
+     *
+     * @param description field maps to same name attribute on 'organizationalUnit'.
+     */
+    public void setDescription( String description )
+    {
+        this.description = description;
+    }
+
+
+    /**
+     * Get the optional parent attribute allows nesting of container nodes two levels below suffix.  For example, if parent
+     * node is created it may be used to subdivide collections of related nodes, dn=ou=Roles, ou=RBAC, dc=companyName, dc=com.
+     *
+     * @return attribute that contains name of parent node that is used to construct the dn.
+     */
+    public String getParent()
+    {
+        return parent;
+    }
+
+
+    /**
+     * Set the optional parent attribute allows nesting of container nodes two levels below suffix.  For example, if parent
+     * node is created it may be used to subdivide collections of related nodes, dn=ou=Roles, ou=RBAC, dc=companyName, dc=com.
+     *
+     * @param parent attribute that contains name of parent node that is used to construct the dn.  This maps to 'ou'
+     *               attribute in parent node's 'organizationalUnit' object class.
+     */
+    public void setParent( String parent )
+    {
+        this.parent = parent;
+    }
+
+
+    /**
+     * @see Object#toString()
+     */
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append( "OrganizationalUnit[" );
+        sb.append( name ).append( ", " );
+        sb.append( description ).append( ", " );
+        sb.append( parent ).append( ", " );
+        sb.append( contextId ).append( ']' );
+
+        return sb.toString();
+    }
+}