/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@objectweb.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
 *
 * Initial developer(s): Greg Wilkins for the HashUserRealm
 * Copyright (c) 1996 Mort Bay Consulting Pty. Ltd. All rights reserved.
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.security.realm.web.jetty50;

import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.mortbay.http.HttpRequest;
import org.mortbay.http.UserRealm;

import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.security.SecurityService;
import org.objectweb.jonas.security.realm.factory.JResource;
import org.objectweb.jonas.security.realm.principals.User;
import org.objectweb.jonas.service.ServiceManager;

import org.objectweb.security.context.SecurityContext;
import org.objectweb.security.context.SecurityCurrent;

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

/**
 * <p>
 * Implementation of a Realm. Use any JOnAS realm by specifying the resource
 * name
 * @author Greg Wilkins for the HashUserRealm
 * @author Florent Benoit : Jetty 4.2.x / JOnAS 3.1
 */
public class Standard implements UserRealm {

    /**
     * The logger used in JOnAS
     */
    private static Logger logger = null;

    /**
     * Name of this realm
     */
    private String name;

    /**
     * The resource we will use to authenticate users and identify associated
     * roles.
     */
    private JResource jResource = null;

    /**
     * Reference to the JOnAS security service
     */
    private SecurityService securityService = null;

    /**
     * List of authenticated users
     */
    private Map users = null;

    /**
     * Default Constructor
     */
    protected Standard() {
        users = new HashMap();

        if (logger == null) {
            logger = Log.getLogger(Log.JONAS_SECURITY_PREFIX);
        }
    }

    /**
     * Constructor
     * @param resourceName name of the resource to use
     * @throws Exception if the resource can't be retrieved
     */
    public Standard(String resourceName) throws Exception {
        this();

        // Get the Security Service
        try {
            securityService = (SecurityService) ServiceManager.getInstance().getSecurityService();
        } catch (Exception e) {
            // Can't retrieve Security service
            throw new Exception("can't retrieve Security service", e);
        }

        // Get the resource from the security service
        jResource = securityService.getJResource(resourceName);
        if (jResource == null) {
            throw new Exception("Can't retrieve resource " + resourceName + "from the security service");
        }
    }

    /**
     * Constructor
     * @param name name of the realm
     * @param resourceName name of the resource to use
     * @throws Exception if the resource can't be retrieved
     */
    public Standard(String name, String resourceName) throws Exception {
        this(resourceName);
        this.name = name;
    }

    /**
     * @return The realm name.
     */
    public String getName() {
        return name;
    }

    /**
     * Authenticate a user with a specific username and credentials
     * @param username name of the user
     * @param credentials credential of the user
     * @param request httprequest
     * @return a Jetty principal
     */
    public Principal authenticate(String username, Object credentials, HttpRequest request) {

        // No authentication can be made with a null username
        if (username == null) {
            return null;
        }

        Principal jettyPrincipal = (Principal) users.get(username);
        // User previously authenticated --> remove from the cache
        if (jettyPrincipal != null) {
            users.remove(username);
        }

        // Does a user with this username exist?
        User user = null;
        try {
            user = jResource.findUser(username);
        } catch (Exception jre) {
            // could not retrieve user
            logger.log(BasicLevel.INFO, jre.getMessage());
            return null;
        }

        // User was not found
        if (user == null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "User " + username + " not found.");
            }
            return null;
        }

        if (!(credentials instanceof String)) {
            logger.log(BasicLevel.ERROR, "Allow only string type as credentials");
            return null;
        }

        boolean validated = jResource.isValidUser(user, (String) credentials);

        if (!validated) {
            logger.log(BasicLevel.INFO, "The password for the user " + username + " is not valid");
            return null;
        }

        ArrayList combinedRoles = null;
        try {
            combinedRoles = jResource.getArrayListCombinedRoles(user);
        } catch (Exception jre) {
            logger.log(BasicLevel.ERROR, jre.getMessage());
            return null;
        }

        Principal principal = new JettyPrincipal(user.getName(), combinedRoles);
        SecurityContext ctx = new SecurityContext(principal.getName(), combinedRoles);
        SecurityCurrent current = SecurityCurrent.getCurrent();
        current.setSecurityContext(ctx);

        // Add to cache
        users.put(username, principal);

        return principal;
    }

    /**
     * Check if a user is in a role.
     * @param user The user, which must be from this realm
     * @param roleName the role to test for the given user
     * @return True if the user can act in the role.
     */
    public synchronized boolean isUserInRole(Principal user, String roleName) {
        if (user == null) {
            return false;
        }

        if (user instanceof JettyPrincipal) {
            return ((JettyPrincipal) user).isUserInRole(roleName);
        } else {
            logger.log(BasicLevel.ERROR, "The user '" + user + "' is not instance of JettyPrincipal");
            return false;
        }
    }

    /**
     * Check if a user is authenticated
     * @param user The user, which must be from this realm
     * @return True if the user is authenticated
     */
    public boolean isAuthenticated(Principal user) {
        if (user == null) {
            return false;
        }

        if (user instanceof JettyPrincipal) {
            return ((JettyPrincipal) user).isAuthenticated();
        } else {
            logger.log(BasicLevel.ERROR, "The user '" + user + "' is not instance of JettyPrincipal");
            return false;
        }
    }

    /**
     * Gets the principal with the given username
     * @param username the given username
     * @return the principal with the given username
     */
    public Principal getPrincipal(String username) {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Get principal with username '" + username + "'.");
        }

        JettyPrincipal principal = (JettyPrincipal) users.get(username);
        SecurityContext ctx = new SecurityContext(principal.getName(), principal.getRoles());
        SecurityCurrent current = SecurityCurrent.getCurrent();
        current.setSecurityContext(ctx);
        return principal;
    }

    /**
     * Disassociate a user Not implemented
     * @param user the given user
     */
    public void disassociate(Principal user) {
    }

    /**
     * Push a role to a user Not implemented
     * @param user the given user
     * @param role the role to push
     * @return the new principal
     */
    public Principal pushRole(Principal user, String role) {
        return user;
    }

    /**
     * Pop a role to a user Not implemented
     * @param user the given user
     * @return the new principal
     */
    public Principal popRole(Principal user) {
        return user;
    }

    /**
     * Log out a specific user
     * @param user the user to logout
     */
    public void logout(Principal user) {
    }

    /**
     * Check if the specific user is authenticated
     * @param user the user to reauthenticate
     * @return true if the user is authenthicated
     */
    public boolean reauthenticate(Principal user) {
        if (user instanceof JettyPrincipal) {
            return ((JettyPrincipal) user).isAuthenticated();
        } else {
            return false;
        }
    }

    /**
     * @return the logger.
     */
    protected static Logger getLogger() {
        return logger;
    }

    /**
     * @return the users.
     */
    protected Map getUsers() {
        return users;
    }

    /**
     * Remove a specific user
     * @param username user to remove
     */
    protected void removeUser(String username) {
        users.remove(username);
    }

    /**
     * Add a user to the current map
     * @param username name of the user
     * @param principal object
     */
    protected void addUser(String username, Principal principal) {
        users.put(username, principal);
    }

    /**
     * Sets the name of the realm
     * @param name The name to set.
     */
    protected void setName(String name) {
        this.name = name;
    }
}