/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * 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.lib.security.context;

import org.ow2.jonas.lib.security.mapping.JPolicyUserRoleMapping;

import java.io.Serializable;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


/**
 * Implementation of the JOnAS Security Context CAUTION: Don't forget to modify
 * the Marshalling class of this package used for IIOP protocol
 * @author Jeff Mesnil (initial developer)
 * @author Florent Benoit
 */

public class SecurityContext implements Serializable {

    /**
     * Name of the principal
     */
    private String principalName = null;

    /**
     * Anonymous principal name.
     */
    private static final String PRINCIPAL_NAME = "ANONYMOUS";

    /**
     * Default Principal (non runAs case).
     */
    private Principal defaultPrincipal = null;

    /**
     * RunAs Principal.
     */
    private Principal runAsPrincipal = null;

    /**
     * List of roles of the principal
     */
    private final String[] roles;

    /**
     * run-as stack It uses ArrayList instead of java.util.Stack class This is
     * for use ArrayList and not Vector as Stack implementation
     */
    private List runAsRoleStack = null;

    /**
     * This stack is used for defining the principal identity for runAs
     */
    private List runAsPrincipalStack = null;

    /**
     * This stack is used for defining the roles of the principal identity for
     * runAs
     */
    private List runAsPrincipalRolesStack = null;

    /**
     * Signature.
     */
    private byte[] signedData = null;

    /**
     * Constructor SecurityContext use the default principal name
     */
    public SecurityContext() {
        this(PRINCIPAL_NAME);
    }
    /**
     * Constructor SecurityContext
     * @param principalName principal name
     */
    public SecurityContext(String principalName) {
        this(principalName, new String[] {"JOnAS"});
    }

    /**
     * Constructor SecurityContext
     * @param principalName principal name
     * @param roles the roles of the principal
     */
    public SecurityContext(String principalName, String[] roles) {
        this(principalName, Arrays.asList(roles));
    }

    /**
     * Constructor SecurityContext
     * @param principalName principal name
     * @param arrayRoles the list of the roles of the principal
     */
    public SecurityContext(String principalName, List arrayRoles) {
        this(principalName, arrayRoles, null, null, null);
    }

    /**
     * Constructor SecurityContext
     * @param principalName principal name
     * @param arrayRoles the list of the roles of the principal
     * @param arrayRunas the RunAs stack
     * @param arrayRunasPrincipal the RunAs stack for principal of runAs
     * @param arrayRunasPrincipalRoles the RunAs stack for roles of the principal
     *
     */
    protected SecurityContext(String principalName, List arrayRoles, List arrayRunas, List arrayRunasPrincipal, List arrayRunasPrincipalRoles) {
        this.principalName = principalName;
        String[] overridedRoles = JPolicyUserRoleMapping.getGlobalMappingForPrincipal(principalName);
        if (overridedRoles != null) {
            this.roles = overridedRoles;
        } else {
            if (arrayRoles != null) {
                //Convert list into array
                String[] r = new String[arrayRoles.size()];
                r = (String[]) arrayRoles.toArray(r);
                this.roles = r;
            } else {
                this.roles = null;
            }
        }

        this.runAsRoleStack = arrayRunas;
        this.runAsPrincipalStack = arrayRunasPrincipal;
        this.runAsPrincipalRolesStack = arrayRunasPrincipalRoles;
        this.defaultPrincipal = new InternalPrincipal(false);
        this.runAsPrincipal = new InternalPrincipal(true);
    }



    /**
     * Method getCallerPrincipal
     * @param inRunAs is the caller is in a runAs case in RunAs mode this
     *        function must return the caller of the bean and not the run as
     *        identity (EJB 2.1 chapter21.2.5.1)
     * @return the Principal in the Security Context
     */
    public Principal getCallerPrincipal(boolean inRunAs) {
        if (inRunAs) {
            return runAsPrincipal;
        }
        return defaultPrincipal;
    }

    /**
     * Gets the signature.
     * @return signature
     */
    public byte[] getSignature() {
        return signedData;
    }

    /**
     * Sets the signature.
     * @param signedData the given data of the signature.
     */
    public void setSignature(final byte[] signedData) {
        this.signedData = signedData;
    }

    /**
     * Return the roles of the principal
     * @param inRunAs caller is in run-as bean ?
     * @return roles of this principal
     */
    public String[] getCallerPrincipalRoles(boolean inRunAs) {
        String[] runAsRoles = null;
        if (inRunAs) {
            runAsRoles = peekLastRunAsPrincipalRoles();
        } else {
            runAsRoles = peekRunAsPrincipalRoles();
        }
        if (runAsRoles != null) {
            return runAsRoles;
        } else {
            return roles;
        }
    }

    /**
     * Push : Pushes run-as items at the top of this stack.
     * @param role the role to add on top of the stack
     * @param principalName the name of the principal to add on top of the
     *        stack.
     * @param roles list of roles of this principal.
     */
    public synchronized void pushRunAs(String role, String principalName, String[] roles) {
        getRunAsRoleStack().add(role);
        getRunAsPrincipalStack().add(principalName);
        getRunAsPrincipalRolesStack().add(roles);
    }

    /**
     * Pop : Removes the object at the top of the run-as stack
     */
    public synchronized void popRunAs() {
        if (!getRunAsRoleStack().isEmpty()) {
            getRunAsRoleStack().remove(getRunAsRoleStack().size() - 1);
        }
        if (!getRunAsPrincipalStack().isEmpty()) {
            getRunAsPrincipalStack().remove(getRunAsPrincipalStack().size() - 1);
        }
        if (!getRunAsPrincipalRolesStack().isEmpty()) {
            getRunAsPrincipalRolesStack().remove(getRunAsPrincipalRolesStack().size() - 1);
        }
    }

    /**
     * Peek : Looks at the object at the top of this stack without removing it
     * from the stack.
     * @return the role at the top of the stack
     */
    public synchronized String peekRunAsRole() {
        if (getRunAsRoleStack().isEmpty()) {
            return null;
        } else {
            return (String) getRunAsRoleStack().get(getRunAsRoleStack().size() - 1);
        }
    }

    /**
     * Peek : Looks at the object at the top of this stack without removing it
     * from the stack.
     * @return the principal at the top of the stack
     */
    public synchronized String peekRunAsPrincipal() {
        if (getRunAsPrincipalStack().isEmpty()) {
            return null;
        } else {
            return (String) getRunAsPrincipalStack().get(getRunAsPrincipalStack().size() - 1);
        }
    }

    /**
     * Peek : Looks at the object at the top of this stack without removing it
     * from the stack.
     * @return the principal at the top of the stack
     */
    public synchronized String peekLastRunAsPrincipal() {
        if (getRunAsPrincipalStack().size() < 2) {
            return null;
        } else {
            return (String) getRunAsPrincipalStack().get(getRunAsPrincipalStack().size() - 2);
        }
    }

    /**
     * Peek : Looks at the object at the top of this stack without removing it
     * from the stack.
     * @return the principal at the top of the stack
     */
    public synchronized String[] peekRunAsPrincipalRoles() {
        if (getRunAsPrincipalRolesStack().isEmpty()) {
            return null;
        } else {
            return (String[]) getRunAsPrincipalRolesStack().get(getRunAsPrincipalRolesStack().size() - 1);
        }
    }

    /**
     * Peek : Looks at the object at the top of this stack without removing it
     * from the stack.
     * @return the principal at the top of the stack
     */
    public synchronized String[] peekLastRunAsPrincipalRoles() {
        if (getRunAsPrincipalRolesStack().size() < 2) {
            return null;
        } else {
            return (String[]) getRunAsPrincipalRolesStack().get(getRunAsPrincipalRolesStack().size() - 2);
        }
    }

    /**
     * Gets the stack which manages the run-as
     * @return the stack which manages the run-as
     */
    public synchronized List getRunAsRoleStack() {
        if (runAsRoleStack == null) {
            runAsRoleStack = Collections.synchronizedList(new ArrayList());
        }
        return runAsRoleStack;
    }

    /**
     * Gets the stack which manages the roles of the current run-as principal
     * @return the stack which manages the roles of the current run-as principal
     */
    public synchronized List getRunAsPrincipalRolesStack() {
        if (runAsPrincipalRolesStack == null) {
            runAsPrincipalRolesStack = Collections.synchronizedList(new ArrayList());
        }
        return runAsPrincipalRolesStack;
    }

    /**
     * Gets the stack which manages the run-as principal
     * @return the stack which manages the run-as principal
     */
    public synchronized List getRunAsPrincipalStack() {
        if (runAsPrincipalStack == null) {
            runAsPrincipalStack = Collections.synchronizedList(new ArrayList());
        }
        return runAsPrincipalStack;
    }

    /**
     * Method toString
     * @return String a string representation of the object
     */
    public String toString() {
        String txt = "principal : name = " + principalName + "\n";
        if (roles != null) {
            for (int i = 0; i < roles.length; i++) {
                txt += "role[" + i + "] = " + roles[i] + "\n";
            }
        }
        if (runAsRoleStack != null) {
            Iterator iRunas = runAsRoleStack.iterator();
            int i = 0;
            while (iRunas.hasNext()) {
                txt += "runas[" + i + "] = " + ((String) iRunas.next()) + "\n";
            }
        }
        return txt;
    }

    /**
     * @param runningRunAs bean is currently running with run-as enabled
     * @return the principal name.
     */
    protected String getPrincipalName(boolean runningRunAs) {
        String principal = null;
        if (runningRunAs) {
            principal = peekLastRunAsPrincipal();
        } else {
            principal = peekRunAsPrincipal();
        }
        if (principal != null) {
            return principal;
        } else {
            return principalName;
        }
    }

    /**
     * @return the principal Name.
     */
    public String getPrincipalName() {
        return principalName;
    }

    /**
     * @return the roles.
     */
    public String[] getRoles() {
        return roles;
    }

    /**
     * @author Florent Benoit Defines an implementation of the Principal object
     */
    class InternalPrincipal implements Principal, Serializable {

        /**
         * When building principal name, use right principal name. This is
         * possible by knowing the bean making the call is in a run-as bean or
         * not.
         */
        private boolean inRunAs = false;

        /**
         * Constructor
         * @param inRunAs caller is in run-as bean ?
         */
        public InternalPrincipal(boolean inRunAs) {
            super();
            this.inRunAs = inRunAs;

        }

        /**
         * @return name of this principal
         */
        public String getName() {
            return getPrincipalName(inRunAs);
        }

        /**
         * @param o the object to compare
         * @return true if the given principal has the same name than ours
         */
        public boolean equals(Object o) {
            if (o instanceof Principal) {
                return getPrincipalName(inRunAs).equals(((Principal) o).getName());
            }
            return false;
        }

        /**
         * Hashcode for this principal
         * @return hashcode of the principal name
         */
        public int hashCode() {
            return getPrincipalName(inRunAs).hashCode();
        }

        /**
         * Display this object
         * @return string representing this object
         */
        public String toString() {
            return "name = " + getPrincipalName(inRunAs);
        }
    }

}

