/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.security.jmac.config;

import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.AuthConfig;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.ClientAuthConfig;
import javax.security.auth.message.config.ClientAuthContext;
import javax.security.auth.message.config.RegistrationListener;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;

import com.sun.enterprise.security.AppservAccessController;
import com.sun.enterprise.security.jmac.AuthMessagePolicy;
import com.sun.enterprise.security.jmac.config.HandlerContext;


/**
 * Helper class for 196 Configuration.
 * This class implements RegistrationListener. 
 */
public class ConfigHelper implements RegistrationListener {
    private static final String DEFAULT_HANDLER_CLASS =
        "com.sun.enterprise.security.jmac.callback.ContainerCallbackHandler"; 

    private static String handlerClassName = null;
    protected static final AuthConfigFactory factory = AuthConfigFactory.getFactory();

    private ReadWriteLock rwLock;
    private Lock rLock;
    private Lock wLock;

    private boolean enabled;
    private ConfigData data;
	
    protected String layer;
    protected String appCtxt;
    protected Map map;
    protected CallbackHandler cbh;

    public ConfigHelper(String layer, String appContext,
			 Map map, CallbackHandler cbh) {
        init(layer, appContext, map, cbh);
    }

    protected ConfigHelper() {
    }

    protected void init(String layer, String appContext,
            Map map, CallbackHandler cbh) {

	this.layer = layer;
	this.appCtxt = appContext;
	this.map = map;
	this.cbh = cbh;
        if (this.cbh == null) {
            this.cbh = getCallbackHandler();
        }

	this.rwLock = new ReentrantReadWriteLock(true);
	this.rLock = rwLock.readLock();
	this.wLock = rwLock.writeLock();

	enabled = (factory != null);
	data = null;

    }

    public void notify(String layer, String appContext) {
	if (this.layer.equals(layer) && 
            ((this.appCtxt == null && appContext == null) ||
                (appContext != null && appContext.equals(this.appCtxt)))) {
	    try {
		wLock.lock();
		data = null;
	    } finally {
		wLock.unlock();
	    }
	}
    }

    public void disable() {
	try {
	    wLock.lock();
	    enabled = false;
	} finally {
	    data = null;
	    wLock.unlock();
	}
        if (factory != null) {
            factory.detachListener(this,layer,appCtxt);
	}
    }

    public Object getProperty(String key) {
	return map == null ? null : map.get(key);
    }

    public String getAppContextID() {
	return appCtxt;
    }

    public ClientAuthConfig getClientAuthConfig() throws AuthException {
        return (ClientAuthConfig)getAuthConfig(false);
    }

    public ServerAuthConfig getServerAuthConfig() throws AuthException {
        return (ServerAuthConfig)getAuthConfig(true);
    }

    public ClientAuthContext getClientAuthContext(MessageInfo info, Subject s) 
    throws AuthException {
	ClientAuthConfig c = (ClientAuthConfig)getAuthConfig(false);
	if (c != null) {
	    return c.getAuthContext(c.getAuthContextID(info),s,map);
	}
	return null;
    }

    public ServerAuthContext getServerAuthContext(MessageInfo info, Subject s) 
    throws AuthException {
	ServerAuthConfig c = (ServerAuthConfig)getAuthConfig(true);
	if (c != null) {
	    return c.getAuthContext(c.getAuthContextID(info),s,map);
	}
	return null;
    }

    protected AuthConfig getAuthConfig(AuthConfigProvider p, boolean isServer) 
    throws AuthException {
	AuthConfig c = null; 
	if (p != null) {
	    if (isServer) { 
		c = p.getServerAuthConfig(layer, appCtxt, cbh);
	    } else {
		c = p.getClientAuthConfig(layer, appCtxt, cbh);
	    }
	}
	return c;
    }

    protected AuthConfig getAuthConfig(boolean isServer) throws AuthException {

	ConfigData d = null;
	AuthConfig c = null;
	boolean disabled = false;
	AuthConfigProvider lastP = null;

	try {
	    rLock.lock();
	    disabled = (!enabled);
	    if (!disabled) {
		d = data;
		if (d != null) {
		    c = (isServer ? d.sConfig : d.cConfig);
		    lastP = d.provider;
		}
	    }
	    
	} finally {
	    rLock.unlock();
	    if (disabled || c != null || (d != null && lastP == null)) {
		return c;
	    }
	} 


	// d == null || (d != null && lastP != null && c == null)
	if (d == null) {
	    try {
		wLock.lock();
                if (data == null) {
                    AuthConfigProvider nextP =
                        factory.getConfigProvider(layer,appCtxt,this);

                    if (nextP != null) {
                        data = new ConfigData(nextP,getAuthConfig(nextP,isServer));
                    } else {
                        data = new ConfigData();
                    }
                }
                d = data;
	    } finally {
		wLock.unlock();
	    }
	} 

        return ((isServer)? d.sConfig : d.cConfig);
    }

    /**
     * Get the callback default handler
     */
    private CallbackHandler getCallbackHandler() {

        CallbackHandler rvalue = AuthMessagePolicy.getDefaultCallbackHandler();
        if (rvalue instanceof CallbackHandlerConfig) {
            ((CallbackHandlerConfig)rvalue).setHandlerContext
                    (getHandlerContext(map));
        }

        return rvalue;
    }

    /**
     * This method is invoked by the constructor and should be
     * overrided by subclass.
     */
    protected HandlerContext getHandlerContext(Map map) {
        return null;
    }

    private class ConfigData {

	private AuthConfigProvider provider; 
	private AuthConfig sConfig; 
	private AuthConfig cConfig; 

	ConfigData() {
	    provider = null;
	    sConfig = null;
	    cConfig = null;
	}

	ConfigData(AuthConfigProvider p, AuthConfig a) {
	    provider = p;
	    if (a == null) {
		sConfig = null;
		cConfig = null;
	    } else if (a instanceof ServerAuthConfig) {
		sConfig = a;
		cConfig = null;
	    } else if (a instanceof ClientAuthConfig) {
		sConfig = null;
		cConfig = a;
	    } else {
		throw new IllegalArgumentException();
	    }
	}
    }
}
