/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 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
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas.security;

import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.NamingException;
import java.rmi.RemoteException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import java.security.Principal;
import java.security.Guard;
import org.objectweb.jonas.common.Log;
import org.objectweb.jonas_ejb.deployment.api.BeanDesc;
import org.objectweb.jonas_ejb.deployment.api.MethodDesc;
import org.objectweb.jonas_ejb.lib.BeanNaming;
import org.objectweb.jonas.jmx.JmxService;
import org.objectweb.jonas.jmx.JonasObjectName;
import org.objectweb.jonas.service.AbsServiceImpl;
import org.objectweb.jonas.service.ServiceException;
import org.objectweb.jonas.service.ServiceManager;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * Security Service implementation
 *
 * @author Philippe Coq
 * Contributor(s): 
 *
 *        JONAS 2.1  Jeff Mesnil first implementation
 *
 *        JOnAS 2.2  John Ellis add the possibility to plug other implementations
 *                   of the security service.
 *
 *        JOnAS 2.2  Joe Gittings coding for method signature into MethodGuard.add.
 */

public class JonasSecurityServiceImpl extends AbsServiceImpl implements SecurityService, JonasSecurityServiceImplMBean {

    static private Logger logger = null;

    private Hashtable methodGuards = new Hashtable();

    private Hashtable roleGuards = new Hashtable();

    private Properties prop = new Properties(); // for jonas-users.properties

    // -------------------------------------------------------------------
    // JOnAS Service Implementation
    // -------------------------------------------------------------------

    /**
     * Init the Service.
     * Configuration information is passed thru a Context object.
     */
    public void doInit(Context ctx) throws ServiceException {
        if( logger == null)
            logger = Log.getLogger(Log.JONAS_SECURITY_PREFIX);
	try {
	    prop.load(ClassLoader.getSystemResourceAsStream("jonas-users.properties"));
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "Cannot get the 'jonas-users.properties' properties");
	    throw new ServiceException("Cannot initialize the JonasSecurityService", e);
	}

	logger.log(BasicLevel.DEBUG,  "JonasSecurityService initialized");
    }

    /**
     * Start the Service
     * Initialization of the service is already done.
     */
    public void doStart() throws ServiceException {
	setPrincipalRoleMapping();

	try {
	    // register security service mbean 
	    ((JmxService)ServiceManager.getJmxService()).getJmxServer().registerMBean(this,JonasObjectName.securityService() );
	} catch (ServiceException se) {
	    // Jmx Service not available, do nothing
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "SecurityService: Cannot start the Security service:\n"+e);
	    throw new ServiceException("SecurityService: Cannot start the Security service",
				       e);
	}

    }

    /**
     * Stop the Service
     */
    public void doStop() {
	// unregister MBean	
	try {
	    // unregister Security Service MBean : SecurityServiceImplMBean 
	    ((JmxService)ServiceManager.getJmxService()).getJmxServer().unregisterMBean(JonasObjectName.securityService());
	} catch (ServiceException se) {
	    // Jmx Service not available, do nothing
	} catch (Exception e) {
	    logger.log(BasicLevel.ERROR, "EJBService: Cannot stop the EJB service:\n"+e);
	    throw new ServiceException("EJBService: Cannot stop the EJB service",e);
	}
	logger.log(BasicLevel.DEBUG,  "JonasSecurityService stopped");
    }
 
    
    // -------------------------------------------------------------------
    // SecurityService Implementation
    // -------------------------------------------------------------------

    /**
     * Returns the MethodGuard for the given Container
     * @params containerName The container name
     */
    public Guard getMethodGuard(String containerName) {
	MethodGuard mg = (MethodGuard) methodGuards.get(containerName);
	if (mg == null) {
	    mg = new MethodGuard(containerName);
	    methodGuards.put(containerName, mg);
	}
	logger.log(BasicLevel.DEBUG,  "entering for "+ mg);
	return mg;
    }
    
    /**
     * Returns the RoleGuard for the given Container
     * @params containerName The container name
     */
    public Guard getRoleGuard(String containerName) {
	RoleGuard rg = (RoleGuard) roleGuards.get(containerName);
	if (rg == null) {
	    rg = new RoleGuard(containerName);
	    roleGuards.put(containerName, rg);
	}
	logger.log(BasicLevel.DEBUG,  "entering for "+rg);
	return rg;
    }

    /**
     * Set the security attributes for the bean being deployed.
     * @params containerName The container the bean is deployed in
     * @params bd The bean descriptor
     */
    public void deployBean(String containerName, BeanDesc bd) {
	logger.log(BasicLevel.DEBUG,  "deployed for "+bd.getEjbName());
	setMethodRoleMapping(containerName, bd);
	setRoleRefMapping(containerName, bd);
    }
    
    // -------------------------------------------------------------------
    // Other methods
    // -------------------------------------------------------------------

    private void setMethodRoleMapping(String containerName, BeanDesc bd) {
	logger.log(BasicLevel.DEBUG,  "entering");
	// deployment of the method guards
	//System.out.println("methodGuard : "+mg);
	Hashtable ht = bd.getMethodDescs();
	//System.out.println("enumeration : "+e);
	Enumeration e = ht.keys();
 	MethodGuard methodGuard = (MethodGuard) methodGuards.get(containerName);
	while (e.hasMoreElements()) {
	    Method meth = (Method) e.nextElement();
	    MethodDesc md = (MethodDesc) ht.get(meth);
	    //System.out.println("MethodDesc : "+md);
	    methodGuard.add(BeanNaming.getSignature(bd.getEjbName(), meth),md.getRoleName());
	}
	methodGuards.put(containerName, methodGuard);
    }

    private  void setRoleRefMapping(String containerName, BeanDesc bd) {
	logger.log(BasicLevel.DEBUG,  "entering");
    	// deployment of the security-role-ref  mapping
	//RoleMechanism rm = new RoleMechanism();
 	RoleGuard roleGuard = (RoleGuard)roleGuards.get(containerName);
	String[] roles = bd.getSecurityRoleNames();
	for (int i = 0; i < roles.length; i++) 
	    roleGuard.add(roles[i], bd.getRoleLink(roles[i]));
	roleGuards.put(containerName, roleGuard);
    }   

    private void setPrincipalRoleMapping() {
 	logger.log(BasicLevel.DEBUG,  "entering");
	
	Enumeration e = prop.propertyNames();
	String[] roles;
	RoleMechanism rm = RoleMechanism.getInstance();
	while (e.hasMoreElements()) {
	    String principalName = (String) e.nextElement();
	    String roleList = prop.getProperty(principalName);
	    StringTokenizer str = new StringTokenizer(roleList, ",");
	    int roleNumbers = str.countTokens();
	    roles = new String [roleNumbers];
	    int i = 0;
	    while (str.hasMoreElements()) {
		String role = (String) str.nextElement();
		roles[i] = role.trim();
		i ++;
	    }
	    rm.addMapping(principalName, roles);
	}
    }

    /**
     * MBean Method
     * @return Hashtable of correpondences between pricipal and declarative roles
     */
    public Hashtable getPrincipalDeclarativeRoleMapping(){
	return RoleMechanism.getInstance().getPrincipalDeclarativeRoleMapping();
    } 
}
