/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.security.internal.realm.factory;

import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.ow2.jonas.security.internal.realm.lib.HashHelper;
import org.ow2.jonas.security.internal.realm.principal.LDAPUser;
import org.ow2.jonas.security.realm.factory.JResourceException;
import org.ow2.jonas.security.realm.principal.JUser;

import org.objectweb.util.monolog.api.BasicLevel;

/**
 * This class extends the JResource class for the LDAP implementation.
 * @author Florent Benoit
 * @author Emmanuel Rias (12/03 some improvements)
 */
public class JResourceLDAP extends AbstractJResource {

    /**
     * Type of the factory
     */
    private static final String FACTORY_TYPE = "org.ow2.jonas.security.realm.factory.JResourceLDAP";

    /**
     * Name of the factory
     */
    private static final String FACTORY_NAME = "org.ow2.jonas.security.realm.factory.JResourceLDAPFactory";

    /**
     * Bind authentication mode. Bind to the ldap server with the l/p given by
     * the user by the servlet
     */
    private static final String BIND_AUTHENTICATION_MODE = "bind";

    /**
     * Compare authentication mode. Retrieve password from the ldap server with
     * the given login and compare the password retrived with the password given
     * by the user
     */
    private static final String COMPARE_AUTHENTICATION_MODE = "compare";

    /**
     * Initial context factory for the LDAp server
     */
    private String initialContextFactory = null;

    /**
     * Url of the ldap server
     */
    private String providerUrl = null;

    /**
     * Type of the authentication used during the authentication to the LDAP
     * server
     */
    private String securityAuthentication = null;

    /**
     * DN of the Principal(username). He can retrieve the information from the
     * user
     */
    private String securityPrincipal = null;

    /**
     * Credential(password) of the principal
     */
    private String securityCredentials = null;

    /**
     * Constant that holds the name of the environment property for specifying
     * the security protocol to use.
     */
    private String securityProtocol = null;

    /**
     * Constant that holds the name of the environment property for specifying
     * the preferred language to use with the service.
     */
    private String language = null;

    /**
     * Constant that holds the name of the environment property for specifying
     * how referrals encountered by the service provider are to be processed.
     */
    private String referral = null;

    /**
     * Constant that holds the name of the environment property for specifying
     * the list of state factories to use.
     */
    private String stateFactories = null;

    /**
     * Mode for validate the authentication (BIND_AUTHENTICATION_MODE or
     * COMPARE_AUTHENTICATION_MODE)
     */
    private String authenticationMode = null;

    /**
     * Attribute in order to get the password from the ldap server
     */
    private String userPasswordAttribute = null;

    /**
     * Attribute in order to get the user role from the ldap server
     */
    private String userRolesAttribute = null;

    /**
     * Attribute for the role name when performing a lookup on a role
     */
    private String roleNameAttribute = null;

    /**
     * DN used for the lookup
     */
    private String baseDN = null;

    /**
     * DN used when searching the user DN. Override the baseDN if it is defined
     */
    private String userDN = null;

    /**
     * Filter used when searching the user
     */
    private String userSearchFilter = null;

    /**
     * DN used when searching the role DN. Override the baseDN if it is defined
     */
    private String roleDN = null;

    /**
     * Filter used when searching the role
     */
    private String roleSearchFilter = null;

    /**
     * Leave role DN empty ?
     */
    private static final String ROLEDN_EMPTY = "EMPTY";

    /**
     * Default algorithm. If specified, the default is not 'clear' password
     */
    private String algorithm = null;

    /**
     * Constructor : use the super constructor
     * @throws Exception if the super constructor fail
     */
    public JResourceLDAP() throws Exception {
        super();
    }

    /**
     * Check if a user is found and return it
     * @param username the wanted user name
     * @return the user found or null
     * @throws JResourceException if there is a NamingException
     */
    public JUser findUser(String username) throws JResourceException {

        // No username. failed
        if (username == null) {
            return null;
        }

        // User to return
        LDAPUser user = new LDAPUser();
        user.setName(username);
        try {

            // Attributes for performing the search
            DirContext dirContext = getDirContext();
            SearchControls sctls = new SearchControls();

            String[] attributes = null;
            if (authenticationMode.equals(COMPARE_AUTHENTICATION_MODE)) {
                attributes = new String[] {userPasswordAttribute, userRolesAttribute};
            } else {
                attributes = new String[0];
            }
            sctls.setReturningAttributes(attributes);
            sctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "userDN = '" + userDN + "'");
                getLogger().log(BasicLevel.DEBUG, "baseDN = '" + baseDN + "'");
            }

            String lookupUserDN = null;
            // DN of the user. If no specific user DN, use the baseDN
            if (userDN != null && !userDN.equals("")) {
                lookupUserDN = userDN.concat(",").concat(baseDN);
            } else {
                lookupUserDN = baseDN;
            }
            Object[] arguments = {username};
            lookupUserDN = MessageFormat.format(lookupUserDN, arguments);
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "lookupUserDN = '" + lookupUserDN + "'");
            }

            // Search the DN with the user filter with the specific argument and
            // specific search controls
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "search : lookupUserDN = '" + lookupUserDN + "', searchFilter = '"
                        + userSearchFilter + "', username = '" + username + "'");
            }
            NamingEnumeration answer = dirContext
                    .search(lookupUserDN, userSearchFilter, new Object[] {username}, sctls);
            // No answer
            if (answer == null || !answer.hasMore()) {
                if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                    if (answer == null) {
                        getLogger().log(BasicLevel.DEBUG, "answer is null");
                    } else {
                        getLogger().log(BasicLevel.DEBUG, "no anwser");
                    }
                }
                return null;
            }

            // Get first answer
            SearchResult firstAnswer = (SearchResult) answer.next();
            // More than one answer. Not so good.
            if (answer.hasMore()) {
                return null;
            }

            // Build full User DN
            NameParser nameParser = dirContext.getNameParser("");
            Name contextName = nameParser.parse(dirContext.getNameInNamespace());

            // Add the lookupUserDN to the context name -> baseDN
            Name baseDNName = contextName.addAll(nameParser.parse(lookupUserDN));

            // Add the uid retrieve from the search to the baseDN --> user DN
            Name userFullDN = baseDNName.addAll(nameParser.parse(firstAnswer.getName()));
            user.setDN(userFullDN.toString());

            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "DN found : '" + userFullDN + "'");
            }

            // Get the user attributes
            Attributes userAttributes = firstAnswer.getAttributes();
            if (userAttributes == null) {
                if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                    getLogger().log(BasicLevel.DEBUG, "No user attributes found");
                }
                return null;
            }

            String password = null;
            // Retrieve password if in compare mode
            if (authenticationMode.equals(COMPARE_AUTHENTICATION_MODE)) {
                password = readValueFromAttribute(userPasswordAttribute, userAttributes);
                if (password != null) {
                    user.setPassword(password);
                }
            }

            // Add the roles which are found from the user
            String roles = readValuesFromAttribute(userRolesAttribute, userAttributes);
            user.setRoles(roles);

        } catch (javax.naming.NamingException ne) {
            throw new JResourceException("Could not find user :" + ne.getMessage());
        }
        return user;
    }

    /**
     * Check if the given user with the given credential is a valid user
     * @param user the given user
     * @param credentials credential of the user
     * @return true if the user is valid
     */
    public boolean isValidUser(JUser user, String credentials) {

        // null password ... can't authenticate
        if (credentials == null || user == null) {
            return false;
        }

        // Choose the right method
        if (authenticationMode.equals(COMPARE_AUTHENTICATION_MODE)) {
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "Compare mode");
            }
            return isValidUserCompare(user, credentials);
        } else if (authenticationMode.equals(BIND_AUTHENTICATION_MODE)) {
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "Bind mode");
            }
            return isValidUserBind(user, credentials);
        } else {
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "No authentication mode found, return false");
            }
            return false;
        }

    }

    /**
     * Check if the given user with the given credential is a valid user. Check
     * is done by binding to the LDAP server with the specific
     * principal/credential.
     * @param user the given user
     * @param credentials credential of the user
     * @return true if the user is valid
     */
    public boolean isValidUserBind(JUser user, String credentials) {

        // Get the DN of the ldap user
        if (!(user instanceof LDAPUser)) {
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "Not instance of LDAPUser");
            }
            return false;
        }

        String dn = ((LDAPUser) user).getDN();
        if (dn == null) {
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "No DN found in User");
            }
            return false;
        }

        //Make our env
        Hashtable env = getEnvInitialDirContext();
        env.put(Context.SECURITY_PRINCIPAL, dn);
        env.put(Context.SECURITY_CREDENTIALS, credentials);

        boolean validated = false;
        try {
            DirContext dirContext = new InitialDirContext(env);
            validated = true;
            dirContext.close();
        } catch (AuthenticationException e) {
            // not validated
            getLogger().log(BasicLevel.ERROR, "Can't make an initial dir context : " + e.getMessage());
        } catch (NamingException ne) {
            // not validated
            getLogger().log(BasicLevel.ERROR, "Naming exception " + ne.getMessage());
        }
        return validated;
    }

    /**
     * Check if the given user with the given credential is a valid user. Check
     * is done by comparing the given credential with the credential which is
     * retrieved from the LDAP server.
     * @param user the given user
     * @param credentials credential of the user
     * @return true if the user is valid
     */
    public boolean isValidUserCompare(JUser user, String credentials) {

        boolean validated = false;

        if (user != null && user.getHashPassword() == null) {
            // Don't use stringbuffer, error case
            String errMsg = "No password for the user so it cannot perform a check.";
            errMsg += " Check that you are using the correct mode ('compare' or 'bind').";
            errMsg += " By using compare mode, the anonymous user cannot retrieved password in many cases.";
            getLogger().log(BasicLevel.ERROR,  errMsg);
            return validated;
        }

        //Get algorithm and hashpassword
        String pass = user.getHashPassword().getPassword();
        String algo = user.getHashPassword().getAlgorithm();

        // Crypt password ?
        if (algo != null && pass != null) {
            try {
                validated = HashHelper.hashPassword(credentials, algo).equalsIgnoreCase(pass);
            } catch (NoSuchAlgorithmException nsae) {
                getLogger().log(BasicLevel.ERROR, "Can't make a password with the algorithm " + algo + ". "
                        + nsae.getMessage());
            }
        } else if ((algorithm != null) && (!algorithm.equals(""))) {
            // Encode password with the specified algorithm (no clear)
            try {
                validated = HashHelper.hashPassword(credentials, algorithm).equalsIgnoreCase(pass);
            } catch (NoSuchAlgorithmException nsae) {
                getLogger().log(BasicLevel.ERROR, "Can't make a password with the algorithm " + algorithm + ". "
                        + nsae.getMessage());
            }
        } else {
            // clear
            validated = credentials.equals(pass);
        }
        return validated;
    }

    /**
     * Get all the roles (from the roles and from the groups) of the given user
     * @param user the given user
     * @return the array list of all the roles for a given user
     * @throws JResourceException if there is a naming exception
     */
    public ArrayList getArrayListCombinedRoles(JUser user) throws JResourceException {

        ArrayList allCombinedRoles = new ArrayList();
        // Return empty array if user null
        if (user == null) {
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "User is empty, return empty array of roles");
            }
            return allCombinedRoles;
        }

        // Add all user roles found with the user ldap entry
        String[] userRoles = user.getArrayRoles();
        for (int r = 0; r < userRoles.length; r++) {
            String roleName = userRoles[r];
            if (!allCombinedRoles.contains(roleName)) {
                allCombinedRoles.add(roleName);
            }
        }

        // Add roles found in ldap
        if (!(user instanceof LDAPUser)) {
            return allCombinedRoles;
        }
        String dn = ((LDAPUser) user).getDN();

        // No valid info
        if (dn == null) {
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "DN of user is empty, return empty array of roles");
            }
            return allCombinedRoles;
        }

        try {
            // Attributes for performing the search
            DirContext dirContext = getDirContext();
            SearchControls sctls = new SearchControls();

            String[] attributes = new String[] {roleNameAttribute};
            sctls.setReturningAttributes(attributes);
            sctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "roleDN = '" + roleDN + "'");
                getLogger().log(BasicLevel.DEBUG, "baseDN = '" + baseDN + "'");
            }

            // DN of the role. If no specific role DN, use the baseDN
            String lookupRoleDN = null;
            if ((roleDN != null) && (!roleDN.equals(""))) {
                if (ROLEDN_EMPTY.equals(roleDN)) {
                    // No role DN for this case
                    lookupRoleDN = "";
                } else {
                    lookupRoleDN = roleDN.concat(",").concat(baseDN);
                }
            } else {
                lookupRoleDN = baseDN;
            }

            // Search the DN with the role filter with the specific argument and
            // specific search controls
            if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                getLogger().log(BasicLevel.DEBUG, "search with lookupRoleDN = '" + lookupRoleDN + "', rolesearchFilter = '"
                        + roleSearchFilter + "', dn = '" + dn + "'");
            }

            NamingEnumeration answers = dirContext.search(lookupRoleDN, roleSearchFilter, new Object[] {dn}, sctls);

            if (answers == null) {
                if (getLogger().isLoggable(BasicLevel.DEBUG)) {
                    getLogger().log(BasicLevel.DEBUG, "answer is null");
                }
                return allCombinedRoles;
            }

            Vector vRoles = new Vector();
            while (answers.hasMore()) {
                SearchResult answer = (SearchResult) answers.next();
                Attributes roleAttributes = answer.getAttributes();
                if (roleAttributes == null) {
                    continue;
                }
                addValueFromAttributeToVector(roleNameAttribute, roleAttributes, vRoles);
            }

            for (Enumeration e = vRoles.elements(); e.hasMoreElements();) {
                String roleName = (String) e.nextElement();
                if (!allCombinedRoles.contains(roleName)) {
                    allCombinedRoles.add(roleName);
                }
            }
        } catch (javax.naming.NamingException ne) {
            throw new JResourceException("Could not find roles from the user :" + ne.getMessage());
        }
        user.setCombinedRoles(allCombinedRoles);
        if (getLogger().isLoggable(BasicLevel.DEBUG)) {
            StringBuffer rolesStr = new StringBuffer();
            for (Iterator it = allCombinedRoles.iterator(); it.hasNext();) {
                rolesStr.append((String) it.next());
                rolesStr.append(",");
            }
            getLogger().log(BasicLevel.DEBUG, "Roles are : " + rolesStr + " for user '" + user.getName() + "'");
        }

        return allCombinedRoles;
    }

    /**
     * String representation of the LDAP realm
     * @return the xml representation of the LDAP realm
     */
    public String toXML() {

        // Required values

        StringBuffer xml = new StringBuffer("    <ldaprealm name=\"");
        xml.append(getName());

        xml.append("\"\n               baseDN=\"");
        if (baseDN != null) {
            xml.append(baseDN);
        }

        if ((initialContextFactory != null) && (!initialContextFactory.equals(""))) {
            xml.append("\"\n               initialContextFactory=\"");
            xml.append(initialContextFactory);
        }

        if ((providerUrl != null) && (!providerUrl.equals(""))) {
            xml.append("\"\n               providerUrl=\"");
            xml.append(providerUrl);
        }

        if ((securityAuthentication != null) && (!securityAuthentication.equals(""))) {
            xml.append("\"\n               securityAuthentication=\"");
            xml.append(securityAuthentication);
        }

        if ((securityPrincipal != null) && (!securityPrincipal.equals(""))) {
            xml.append("\"\n               securityPrincipal=\"");
            xml.append(securityPrincipal);
        }

        if ((securityCredentials != null) && (!securityCredentials.equals(""))) {
            xml.append("\"\n               securityCredentials=\"");
            xml.append(securityCredentials);
        }

        if ((authenticationMode != null) && (!authenticationMode.equals(""))) {
            xml.append("\"\n               authenticationMode=\"");
            xml.append(authenticationMode);
        }

        if ((userPasswordAttribute != null) && (!userPasswordAttribute.equals(""))) {
            xml.append("\"\n               userPasswordAttribute=\"");
            xml.append(userPasswordAttribute);
        }

        if ((userRolesAttribute != null) && (!userRolesAttribute.equals(""))) {
            xml.append("\"\n               userRolesAttribute=\"");
            xml.append(userRolesAttribute);
        }

        if ((roleNameAttribute != null) && (!roleNameAttribute.equals(""))) {
            xml.append("\"\n               roleNameAttribute=\"");
            xml.append(roleNameAttribute);
        }

        if ((userDN != null) && (!userDN.equals(""))) {
            xml.append("\"\n               userDN=\"");
            xml.append(userDN);
        }

        if ((userSearchFilter != null) && (!userSearchFilter.equals(""))) {
            xml.append("\"\n               userSearchFilter=\"");
            xml.append(userSearchFilter);
        }

        if ((roleDN != null) && (!roleDN.equals(""))) {
            xml.append("\"\n               roleDN=\"");
            xml.append(roleDN);
        }

        if ((roleSearchFilter != null) && (!roleSearchFilter.equals(""))) {
            xml.append("\"\n               roleSearchFilter=\"");
            xml.append(roleSearchFilter);
        }

        // Optional values
        if ((securityProtocol != null) && (!securityProtocol.equals(""))) {
            xml.append("\"\n               securityProtocol=\"");
            xml.append(securityProtocol);
        }

        if ((language != null) && (!language.equals(""))) {
            xml.append("\"\n               language=\"");
            xml.append(language);
        }

        if ((referral != null) && (!referral.equals(""))) {
            xml.append("\"\n               referral=\"");
            xml.append(referral);
        }

        if ((stateFactories != null) && (!stateFactories.equals(""))) {
            xml.append("\"\n               stateFactories=\"");
            xml.append(stateFactories);
        }

        if ((algorithm != null) && (!algorithm.equals(""))) {
            xml.append("\"\n               algorithm=\"");
            xml.append(algorithm);
        }

        xml.append("\" />");

        return xml.toString();
    }

    /**
     * The string representation of this object is an XML value
     * @return the xml representation of this object
     */
    public String toString() {
        return this.toXML();
    }

    /**
     * Retrieves the Reference of the object. The Reference contains the factory
     * used to create this object and the optional parameters used to configure
     * the factory.
     * @return the non-null Reference of the object.
     * @throws NamingException if a naming exception was encountered while
     *         retrieving the reference.
     */
    public Reference getReference() throws NamingException {

        // Build the reference to the factory FACTORY_TYPE
        Reference reference = new Reference(FACTORY_TYPE, FACTORY_NAME, null);

        // Add ref addr
        reference.add(new StringRefAddr("name", getName()));
        reference.add(new StringRefAddr("initialContextFactory", getInitialContextFactory()));
        reference.add(new StringRefAddr("providerUrl", getProviderUrl()));
        reference.add(new StringRefAddr("securityAuthentication", getSecurityAuthentication()));
        reference.add(new StringRefAddr("securityPrincipal", getSecurityPrincipal()));
        reference.add(new StringRefAddr("securityCredentials", getSecurityCredentials()));
        reference.add(new StringRefAddr("securityProtocol", getSecurityProtocol()));
        reference.add(new StringRefAddr("language", getLanguage()));
        reference.add(new StringRefAddr("referral", getReferral()));
        reference.add(new StringRefAddr("stateFactories", getStateFactories()));
        reference.add(new StringRefAddr("authenticationMode", getAuthenticationMode()));
        reference.add(new StringRefAddr("userPasswordAttribute", getUserPasswordAttribute()));
        reference.add(new StringRefAddr("userRolesAttribute", getUserRolesAttribute()));
        reference.add(new StringRefAddr("roleNameAttribute", getRoleNameAttribute()));
        reference.add(new StringRefAddr("baseDN", getBaseDN()));
        reference.add(new StringRefAddr("userDN", getUserDN()));
        reference.add(new StringRefAddr("userSearchFilter", getUserSearchFilter()));
        reference.add(new StringRefAddr("roleDN", getRoleDN()));
        reference.add(new StringRefAddr("roleSearchFilter", getRoleSearchFilter()));
        reference.add(new StringRefAddr("algorithm", algorithm));

        return reference;
    }


    /**
     * Set the initial context factory of this LDAP realm
     * @param initialContextFactory the initial context factory
     */
    public void setInitialContextFactory(String initialContextFactory) {
        this.initialContextFactory = initialContextFactory;
    }

    /**
     * Set the Url of the ldap server of this LDAP realm
     * @param providerUrl Url of the ldap server
     */
    public void setProviderUrl(String providerUrl) {
        this.providerUrl = providerUrl;
    }

    /**
     * Set the authentication used during the authentication to the LDAP server
     * of this LDAP realm
     * @param securityAuthentication authentication used during the
     *        authentication to the LDAP server
     */
    public void setSecurityAuthentication(String securityAuthentication) {
        this.securityAuthentication = securityAuthentication;
    }

    /**
     * Set the DN of the Principal(username) of this LDAP realm
     * @param securityPrincipal DN of the Principal(username)
     */
    public void setSecurityPrincipal(String securityPrincipal) {
        this.securityPrincipal = securityPrincipal;
    }

    /**
     * Set the Credential(password) of the principal of this LDAP realm
     * @param securityCredentials Credential(password) of the principal
     */
    public void setSecurityCredentials(String securityCredentials) {
        this.securityCredentials = securityCredentials;
    }

    /**
     * Set the security protocol to use of this LDAP realm
     * @param securityProtocol security protocol to use
     */
    public void setSecurityProtocol(String securityProtocol) {
        this.securityProtocol = securityProtocol;
    }

    /**
     * Set the preferred language to use with the service of this LDAP realm
     * @param language preferred language to use with the service
     */
    public void setLanguage(String language) {
        this.language = language;
    }

    /**
     * Set how referrals encountered by the service provider are to be processed
     * @param referral how referrals encountered by the service provider are to
     *        be processed
     */
    public void setReferral(String referral) {
        this.referral = referral;
    }

    /**
     * Set the the list of state factories of this LDAP realm
     * @param stateFactories list of state factories
     */
    public void setStateFactories(String stateFactories) {
        this.stateFactories = stateFactories;
    }

    /**
     * Set the mode for validate the authentication of this LDAP realm
     * @param authenticationMode BIND_AUTHENTICATION_MODE or
     *        COMPARE_AUTHENTICATION_MODE
     */
    public void setAuthenticationMode(String authenticationMode) {
        this.authenticationMode = authenticationMode;
    }

    /**
     * Set the attribute in order to get the password of this LDAP realm
     * @param userPasswordAttribute attribute in order to get the password of
     *        this LDAP realm
     */
    public void setUserPasswordAttribute(String userPasswordAttribute) {
        this.userPasswordAttribute = userPasswordAttribute;
    }

    /**
     * Set the attribute in order to get the user role from the ldap server
     * @param userRolesAttribute attribute in order to get the user role from
     *        the ldap server
     */
    public void setUserRolesAttribute(String userRolesAttribute) {
        this.userRolesAttribute = userRolesAttribute;
    }

    /**
     * Set the role name when performing a lookup on a role
     * @param roleNameAttribute role name when performing a lookup on a role
     */
    public void setRoleNameAttribute(String roleNameAttribute) {
        this.roleNameAttribute = roleNameAttribute;
    }

    /**
     * Set the DN used for the lookup of this LDAP realm
     * @param baseDN DN used for the lookup
     */
    public void setBaseDN(String baseDN) {
        // empty value can't be set
        if ((baseDN != null) && (!baseDN.equals(""))) {
            this.baseDN = baseDN;
        }
    }

    /**
     * Set the DN used when searching the user DN. Override the baseDN if it is
     * defined
     * @param userDN DN used when searching the user DN
     */
    public void setUserDN(String userDN) {
        if ((userDN != null) && (!userDN.equals(""))) {
            this.userDN = userDN;
        }
    }

    /**
     * Set the filter used when searching the user
     * @param userSearchFilter filter used when searching the user
     */
    public void setUserSearchFilter(String userSearchFilter) {
        this.userSearchFilter = userSearchFilter;
    }

    /**
     * Set the DN used when searching the role DN. Override the baseDN if it is
     * defined
     * @param roleDN DN used when searching the role DN. Override the baseDN if
     *        it is defined
     */
    public void setRoleDN(String roleDN) {
        this.roleDN = roleDN;
    }

    /**
     * Set the filter used when searching the role
     * @param roleSearchFilter filter used when searching the role
     */
    public void setRoleSearchFilter(String roleSearchFilter) {
        this.roleSearchFilter = roleSearchFilter;
    }

    /**
     * Set the default algorithm to use
     * @param algorithm algorithm to be used
     */
    public void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    /**
     * Return the initial context factory of this LDAP realm
     * @return the initial context factory
     */
    public String getInitialContextFactory() {
        return initialContextFactory;
    }

    /**
     * Get the Url of the ldap server of this LDAP realm
     * @return Url of the ldap server
     */
    public String getProviderUrl() {
        return providerUrl;
    }

    /**
     * Get the authentication used during the authentication to the LDAP server
     * of this LDAP realm
     * @return authentication used during the authentication to the LDAP server
     */
    public String getSecurityAuthentication() {
        return securityAuthentication;
    }

    /**
     * Get the DN of the Principal(username) of this LDAP realm
     * @return DN of the Principal(username)
     */
    public String getSecurityPrincipal() {
        return securityPrincipal;
    }

    /**
     * Get the Credential(password) of the principal of this LDAP realm
     * @return Credential(password) of the principal
     */
    public String getSecurityCredentials() {
        return securityCredentials;
    }

    /**
     * Get the security protocol to use of this LDAP realm
     * @return security protocol to use
     */
    public String getSecurityProtocol() {
        return securityProtocol;
    }

    /**
     * Get the preferred language to use with the service of this LDAP realm
     * @return language preferred language to use with the service
     */
    public String getLanguage() {
        return language;
    }

    /**
     * Get how referrals encountered by the service provider are to be processed
     * @return how referrals encountered by the service provider are to be
     *         processed
     */
    public String getReferral() {
        return referral;
    }

    /**
     * Get the the list of state factories of this LDAP realm
     * @return list of state factories
     */
    public String getStateFactories() {
        return stateFactories;
    }

    /**
     * Get the mode for validate the authentication of this LDAP realm
     * @return BIND_AUTHENTICATION_MODE or COMPARE_AUTHENTICATION_MODE
     */
    public String getAuthenticationMode() {
        return authenticationMode;
    }

    /**
     * Get the attribute in order to get the password of this LDAP realm
     * @return attribute in order to get the password of this LDAP realm
     */
    public String getUserPasswordAttribute() {
        return userPasswordAttribute;
    }

    /**
     * Get the attribute in order to get the user role from the ldap server
     * @return attribute in order to get the user role from the ldap server
     */
    public String getUserRolesAttribute() {
        return userRolesAttribute;
    }

    /**
     * Get the role name when performing a lookup on a role
     * @return role name when performing a lookup on a role
     */
    public String getRoleNameAttribute() {
        return roleNameAttribute;
    }

    /**
     * Get the DN used for the lookup of this LDAP realm
     * @return baseDN DN used for the lookup
     */
    public String getBaseDN() {
        return baseDN;
    }

    /**
     * Get the DN used when searching the user DN. Override the baseDN if it is
     * defined
     * @return userDN DN used when searching the user DN
     */
    public String getUserDN() {
        return userDN;
    }

    /**
     * Get the filter used when searching the user
     * @return userSearchFilter filter used when searching the user
     */
    public String getUserSearchFilter() {
        return userSearchFilter;
    }

    /**
     * Get the DN used when searching the role DN. Override the baseDN if it is
     * defined
     * @return roleDN DN used when searching the role DN. Override the baseDN if
     *         it is defined
     */
    public String getRoleDN() {
        return roleDN;
    }

    /**
     * Get the filter used when searching the role
     * @return roleSearchFilter filter used when searching the role
     */
    public String getRoleSearchFilter() {
        return roleSearchFilter;
    }

    /**
     * Get the default algorithm
     * @return the default algorithm
     */
    public String getAlgorithm() {
        return algorithm;
    }

    /**
     * Return a dircontext for this LDAP server
     * @return a dircontext for this LDAP server
     * @throws NamingException if we can't retrieve a DirContext
     */
    protected DirContext getDirContext() throws NamingException {
        DirContext dirContext = null;
        // Get the InitialDirContext with our env
        dirContext = new InitialDirContext(getEnvInitialDirContext());

        return dirContext;
    }

    /**
     * Return the environment used to build a DirContext
     * @return the environment used to build a DirContext
     */
    private Hashtable getEnvInitialDirContext() {

        // The environment is a hashtable
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
        env.put(Context.PROVIDER_URL, providerUrl);
        env.put(Context.SECURITY_AUTHENTICATION, securityAuthentication);
        if ((securityPrincipal != null) && (!securityPrincipal.equals(""))) {
            env.put(Context.SECURITY_PRINCIPAL, securityPrincipal);
        }
        if ((securityCredentials != null) && (!securityCredentials.equals(""))) {
            env.put(Context.SECURITY_CREDENTIALS, securityCredentials);
        }
        if ((language != null) && (!language.equals(""))) {
            env.put(Context.LANGUAGE, language);
        }
        if ((referral != null) && (!referral.equals(""))) {
            env.put(Context.REFERRAL, referral);
        }
        if ((securityProtocol != null) && (!securityProtocol.equals(""))) {
            env.put(Context.SECURITY_PROTOCOL, securityProtocol);
        }
        if ((stateFactories != null) && (!stateFactories.equals(""))) {
            env.put(Context.STATE_FACTORIES, stateFactories);
        }
        return env;
    }

    /**
     * Return the string value read into the attributes attributes with the key
     * attrID
     * @param attrID the key that we are looking for
     * @param attributes all the attributes
     * @return the string value read into the attributes attributes with the key
     *         attrID
     * @throws NamingException if the attrID can't be read from the attributes
     */
    private String readValueFromAttribute(String attrID, Attributes attributes) throws NamingException {

        // No attributes or the attrID is null (must be a non null attribute)
        if (attributes == null || attrID == null) {
            return null;
        }

        Attribute attribute = attributes.get(attrID);

        // Attribute not found
        if (attribute == null) {
            return null;
        }

        Object o = attribute.get();
        String value = null;

        if (o instanceof byte[]) {
            value = new String((byte[]) o);
        } else {
            value = o.toString();
        }

        return value;
    }

    /**
     * Return a comma separated string with the values read into the attributes
     * attributes with the key attrID
     * @param attrID the key that we are looking for
     * @param attributes all the attributes
     * @return comma separated string with the values read into the attributes
     *         attributes with the key attrID
     * @throws NamingException if the attrID can't be read from the attributes
     */
    private String readValuesFromAttribute(String attrID, Attributes attributes) throws NamingException {

        // No attributes or the attrID is null (must be a non null attribute)
        if (attributes == null || attrID == null) {
            return null;
        }

        Attribute attribute = attributes.get(attrID);

        // Attribute not found
        if (attribute == null) {
            return null;
        }

        String value = null;
        NamingEnumeration e = attribute.getAll();
        while (e.hasMore()) {
            String s = (String) e.next();
            if (value == null) {
                value = s;
            } else {
                value = value + "," + s;
            }
        }
        return value;
    }

    /**
     * Add to a vector the values retrieved from the attributes with the
     * keyattrID
     * @param attrID the key that we are looking for
     * @param attributes all the attributes
     * @param v the vector on which the data are added
     * @throws NamingException if the attrID can't be read from the attributes
     */
    private void addValueFromAttributeToVector(String attrID, Attributes attributes, Vector v) throws NamingException {

        // No attributes or the attrID is null (must be a non null attribute)
        if (attributes == null || attrID == null) {
            return;
        }

        Attribute attribute = attributes.get(attrID);

        // Attribute not found
        if (attribute == null) {
            return;
        }

        NamingEnumeration e = attribute.getAll();
        while (e.hasMore()) {
            v.add(e.next());
        }

    }

    /**
     * Remove all the Mbeans used by this resource
     * @throws JResourceException if the MBeans can not be removed
     */
    public void removeMBeans() throws JResourceException {
        //no MBeans
    }

}