/*
 * 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.naming;

import java.util.*;
import java.io.*;
import javax.naming.*;
import org.omg.CORBA.ORB;
import com.sun.enterprise.util.ORBManager;
import com.sun.enterprise.Switch;
import com.sun.enterprise.naming.java.javaURLContext;
import javax.rmi.PortableRemoteObject;
import java.rmi.RemoteException;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextHelper;
import com.sun.enterprise.server.ondemand.entry.*;

import java.util.logging.*;
import com.sun.logging.*;


/**
 * This is a JNDI Context implementation for storing serializable objects.
 * This is the default Context for the J2EE RI. Lookups of unqualified
 * names (i.e. names not starting with "java:", "corbaname:" etc)
 * are serviced by SerialContext. The namespace is implemented in
 * the J2EE server in the SerialContextProviderImpl object, which is
 * accessed over RMI-IIOP from clients.
 */
public class SerialContext implements Context {

    static Logger _logger=LogDomains.getLogger(LogDomains.JNDI_LOGGER);

    static private NameParser myParser = new SerialNameParser();

    private Hashtable myEnv;
    private SerialContextProvider provider;
    private static Hashtable providerCache = new Hashtable();
    private String myName;
    private javaURLContext javaUrlContext = null;

    private final static String JAVA_URL = "java:";
    private InitialContext cosContext;   
    private static Boolean threadlock = new Boolean(true);

    private static ThreadLocal stickyContext = new ThreadLocal();

    /**
     * set and get methods for preserving stickiness.
     * This is a temporary solution
     * to store the sticky IC as a thread local variable.
     * This sticky IC will be used by all classes that require a context
     * object to do lookup (if LB is enabled)
     * SerialContext.lookup() sets a value for the thread local variable 
     * (stickyContext) before performing th lookup in case LB is enabled.
     * If not, the thread local variable is null. 
     * At the end of the SerialContext.lookup() method, the thread local
     * variable gets set to null.
     * So actually speaking, more than being a global variable for the 
     * entire thread, its global only during the execution of the 
     * SerialContext.lookup() method. bug 5050591
     * This will be cleaned for the next release.
     *
     */
    public static void setSticky(ThreadLocalIC var) {
        stickyContext.set(var);
    }

    public static ThreadLocalIC getSticky() {
        return (ThreadLocalIC) stickyContext.get();
    }
  
  /**
   * This method is called from SerialInitContextFactory & S1ASCtxFactory
   * to check if sticky context is set.
   */
    public static Context getStickyContext() {
        return getSticky().getStickyContext();
    }


    /**
     * method to narrow down the provider. 
     * common code that is called from getProvider()
     */
    private SerialContextProvider narrowProvider(org.omg.CORBA.Object ref) 
                       throws Exception {

        NamingContext nctx = NamingContextHelper.narrow(ref);
	NameComponent[] path = 
	{ new NameComponent("SerialContextProvider", "") };
	synchronized (threadlock) {	
	    provider = (SerialContextProvider)
	      PortableRemoteObject.narrow(nctx.resolve(path),
					  SerialContextProvider.class);
	}
	return provider;
    }


    private SerialContextProvider getProvider() 
	throws NamingException {

	try {
	    if (provider == null) {
		if (Switch.getSwitch().getContainerType() ==
		    Switch.EJBWEB_CONTAINER) {
		    if (_logger.isLoggable(Level.FINE)) {
			_logger.fine("lookup call inside the server VM...");
		    }
		    provider = Switch.getSwitch().getProviderManager().getLocalProvider();		   
		    
		} else {
		    if (_logger.isLoggable(Level.FINE)) {
			_logger.fine("lookup call outside the server VM...");
		    }
		    
		    // get SerialContextProvider from naming service
		    
		    // Get orb to use for connecting to NameService
		    ORB orb = (ORB) myEnv.get(ORBManager.JNDI_CORBA_ORB_PROPERTY);
		    
		    if ( orb == null ) {
			orb = ORBManager.getORB();
		    }
		    
		    if (_logger.isLoggable(Level.FINE))
			print(orb);
		    
		    org.omg.CORBA.Object ref = null;
		    //if the following is not null, then we are in EE world
		    //else PE
		    if (myEnv.get("com.sun.appserv.ee.iiop.endpointslist") != null) {
			
			ref = orb.string_to_object((String)(myEnv.get(
					     "com.sun.appserv.ee.iiop.endpointslist")));
			provider = narrowProvider(ref);		  
			
		    } else {
			provider = (SerialContextProvider) providerCache.get(orb);
			if (provider == null) {
			    ref = orb.resolve_initial_references("NameService");
			    provider = narrowProvider(ref);
			    providerCache.put(orb, provider);
			}		   
		    }
		}				
	    }
	} catch (Exception ex) {
	    setSticky(null);
	    CommunicationException ce = new CommunicationException
		("Can't find SerialContextProvider");
	    ce.initCause(ex);
	    throw ce;
	}		  
	
	return provider;
    }
    
    //internal API for logging
    public void print(ORB orb) {
        _logger.fine("SerialContext ==> SerialContext instance created : " + this);

	if (orb != null) {	
	    _logger.fine("SerialContext ==> ORB instance : " + orb);

	    String host = ((com.sun.corba.ee.impl.orb.ORBImpl)orb).
	      getORBData().getORBInitialHost();
	    int port = ((com.sun.corba.ee.impl.orb.ORBImpl)orb).
	      getORBData().getORBInitialPort();                    	
	    
	    _logger.fine("SerialContext ==> ORB HOST : " + host +
			       ", ORB PORT : " + port);
	} else _logger.fine("orb is null");
    }

    /**
     * Constructor for the context. Initializes the object reference
     * to the remote provider object.
     */
    public SerialContext(Hashtable environment) throws NamingException {
     
        myEnv = (environment != null)
	    ? (Hashtable)(environment.clone()) 
	    : null;
      
	// Dont initialize provider now, this throws an exception
	// if J2EEServer is not yet started. Get it lazily when needed.
	//provider = SerialContext.getProvider(myEnv);
	
	this.myName = "";
	if (_logger.isLoggable(Level.FINE))
	    _logger.fine("SerialContext ==> SerialContext instance created : " + this);	
	javaUrlContext = new javaURLContext(myEnv, this);
    }
    
    /**
     * This constructor takes the component id as an argument. All 
     * name arguments to operations are prepended by the component id.
     */
    public SerialContext(String name, Hashtable env) throws NamingException {
	this(env);
	this.myName = name;
    }


    /**
     * The getNameInNamespace API is not supported in this context.
     * @exception NamingException if there is a naming exception.
     */
    public String getNameInNamespace() throws NamingException {
	return myName;
    }

    /**
     * method to check if the name to look up starts with "java:"
     */
    private boolean isjavaURL(String name) {
     
        if (name.startsWith(JAVA_URL)) {	    	    
	    return true;
	} else return false;
    }

    /**
     * Generates an entry context and give it to ondemand initialization framework.
     */
    public void generateEntryContext(Object context) {
        ServerEntryHelper.generateJndiEntryContext((String) context);
    }

    /**
     * method for checking the count and decrementing it
     * also resets the sticky context to null if count is 0
     */
    private void resetSticky() {
        if (getSticky() != null) {
	    getSticky().decrementCount();
	
	    if (getSticky().getStickyCount() == 0) {
	        setSticky(null);
	    }
	}
    }
    
    /**
     * Lookup the specified name in the context.
     * Returns the resolved object. 
     * @return the resolved object.
     * @exception NamingException if there is a naming exception.
     */
    public Object lookup(String name) throws NamingException {

        //Before enything call ondemand initialization framework.
        generateEntryContext(name);

        /**
	 * In case a user is creating an IC with env passed
	 * in constructor; env specifies endpoints in some form
	 * in that case, the sticky IC should be stores as a thread local 
	 * variable. 
	 *
	 */
        if (myEnv.get("com.sun.appserv.ee.iiop.endpointslist") != null) {
	    if (getSticky() == null) {
	        ThreadLocalIC threadLocal = new ThreadLocalIC(this,1);	
		setSticky(threadLocal);
	    } else getSticky().incrementCount();
	}

        if (_logger.isLoggable(Level.FINE))
	    _logger.fine("SerialContext ==> doing lookup with " + this);
        if (name.equals("")) {
	    resetSticky();
            // Asking to look up this context itself.  Create and return
            // a new instance with its own independent environment.
            return (new SerialContext(myName, myEnv));
        } 
	name = getRelativeName(name);
        if (_logger.isLoggable(Level.FINE))
	    _logger.fine("SerialContext ==> looking up : " + name);

        try {
	    if (isjavaURL(name)) {
	        resetSticky();
		return javaUrlContext.lookup(name);
	    } else {	        
		Object obj = getProvider().lookup(name);
		if(obj instanceof Context) {
		    resetSticky();
		    return new SerialContext(name, myEnv);
		}
		Object retObj = 
		    javax.naming.spi.NamingManager.getObjectInstance(obj, 
								     new CompositeName(name),
								     null, myEnv);
		resetSticky();

		return retObj;
	    }
	} catch (NamingException nnfe) {
	    setSticky(null);
	    throw nnfe;
	} catch (Exception ex) {
	    setSticky(null);
            _logger.log(Level.SEVERE,
                        "enterprise_naming.serialctx_communication_exception",
                        ex);
	    //temp fix for 6320008
	    //this should be removed once we change the transient NS implementation to persistent
	    if (ex instanceof java.rmi.MarshalException && 
		ex.getCause() instanceof org.omg.CORBA.COMM_FAILURE) {
	        provider = null; 
		_logger.fine("Resetting provider to NULL. Will get new obj ref for provider since previous obj ref was stale...");
		return lookup(name);		
	    } else {
	        CommunicationException ce = 
		  new CommunicationException("serial context communication ex");
		ce.initCause(ex);
		throw ce;
	    }
	}
	
    }

    /**

     * Lookup the specifed name in the context.
     * Returns the resolved object.
     * @return the resolved object.
     * @exception NamingException if there is a naming exception.
     */
    public Object lookup(Name name) throws NamingException {
        // Flat namespace; no federation; just call string version
        return lookup(name.toString()); 
    }

    /**
     * Bind the object to the specified name.
     * @param the name that the object is being bound to.
     * @param the object that is being bound.
     * @exception NamingException if there is a naming exception.
     */
    public void bind(String name, Object obj) throws NamingException {

	name = getRelativeName(name);
	if (isjavaURL(name)) {	    
	    javaUrlContext.bind(name, obj);
	} else {
	    try {
	        getProvider().bind(name, obj);
	    } catch (RemoteException ex) {
	        throw new CommunicationException(ex.toString());
	    }
	}
    }

    /**
     * Bind the object to the specified name.
     * @param the name that the object is being bound to.
     * @param the object that is being bound.
     * @exception NamingException if there is a naming exception.
     */
    public void bind(Name name, Object obj) throws NamingException {
        // Flat namespace; no federation; just call string version
        bind(name.toString(), obj);
    }

    /**
     * Rebind the object to the specified name.
     * @param the name that the object is being bound to.
     * @param the object that is being bound.
     * @exception NamingException if there is a naming exception.
     */
    public void rebind(String name, Object obj) throws NamingException {
	
	name = getRelativeName(name);
	if (isjavaURL(name)) {	    
	    javaUrlContext.rebind(name, obj);
	} else {
	    try {
	        getProvider().rebind(name, obj);
	    } catch (RemoteException ex) {
	      throw new CommunicationException(ex.toString());
	    }
	}
    }

    /**
     * Rebind the object to the specified name.
     * @param the name that the object is being bound to.
     * @param the object that is being bound.
     * @exception NamingException if there is a naming exception.
     */
    public void rebind(Name name, Object obj) throws NamingException {
        // Flat namespace; no federation; just call string version
        rebind(name.toString(), obj);
    }

    /**
     * Unbind the object with the specified name.
     * @param the name that is being unbound.
     * @exception NamingException if there is a naming exception.
     */
    public void unbind(String name) throws NamingException {
	name = getRelativeName(name);
	if (isjavaURL(name)) {	    
	    javaUrlContext.unbind(name);
	} else {
	    try {
	        getProvider().unbind(name);
	    } catch (RemoteException ex) {
	        throw new CommunicationException(ex.toString());
	    }
	}
    }	

    /**
     * Unbind the object with the specified name.
     * @param the name that is being unbound.
     * @exception NamingException if there is a naming exception.
     */
    public void unbind(Name name) throws NamingException {
        // Flat namespace; no federation; just call string version
        unbind(name.toString());
    }

    /**
     * Rename the bound object.
     * @param the old name that the object is bound as.
     * @param the new name that the object will be bound as.
     * @exception NamingException if there is a naming exception.
     */
    public void rename(String oldname, String newname) throws NamingException {
	oldname = getRelativeName(oldname);
	newname = getRelativeName(newname);
	if (isjavaURL(oldname)) {	    
	    javaUrlContext.rename(oldname, newname);
	} else {
	    try {
	        getProvider().rename(oldname, newname);
	    } catch (RemoteException ex) {
	        throw new CommunicationException(ex.toString());
	    }
	}
    }

    /**
     * Rename the bound object.
     * @param the old name that the object is bound as.
     * @param the new name that the object will be bound as.
     * @exception NamingException if there is a naming exception.
     */
    public void rename(Name oldname, Name newname)
            throws NamingException {
        // Flat namespace; no federation; just call string version
        rename(oldname.toString(), newname.toString());
    }

    /**
     * List the contents of the specified context.
     * @return an enumeration of the contents.
     * @param the context name.
     * @exception NamingException if there is a naming exception.
     */
    public NamingEnumeration list(String name) throws NamingException {
        if (name.equals("")) {
            // listing this context
            try {
                Hashtable bindings = getProvider().list(myName);
                return new RepNames(bindings);
            } catch (RemoteException ex) {
                throw new CommunicationException(ex.toString());
            }
        } 

	name = getRelativeName(name);
	if (isjavaURL(name)) {	    
	    return javaUrlContext.list(name);
	} else {
	    // Perhaps 'name' names a context
	    Object target = lookup(name);
	    if (target instanceof Context) {
	        return ((Context)target).list("");
	    }
	    throw new NotContextException(name + " cannot be listed");
	}
    }

    /**
     * List the contents of the specified context.
     * @return an enumeration of the contents.
     * @param the context name.
     * @exception NamingException if there is a naming exception.
     */
    public NamingEnumeration list(Name name)
            throws NamingException {
        // Flat namespace; no federation; just call string version
        return list(name.toString());
    }

    /**
     * List the bindings in the specified context.
     * @return an enumeration of the bindings.
     * @param the context name.
     * @exception NamingException if there is a naming exception.
     */
    public NamingEnumeration listBindings(String name)
            throws NamingException {
        if (name.equals("")) {
            // listing this context
            try {
                Hashtable bindings = getProvider().list(myName);
                return new RepBindings(bindings);
            } catch (RemoteException ex) {
                CommunicationException ce = 
                    new CommunicationException(ex.toString());
                ce.initCause(ex);
                throw ce;
            }
        } 

	name = getRelativeName(name);
	if (isjavaURL(name)) {	    
	    return javaUrlContext.listBindings(name);
	} else {
	    // Perhaps 'name' names a context
	    Object target = lookup(name);
	    if (target instanceof Context) {
	        return ((Context)target).listBindings("");
	    }
	    throw new NotContextException(name + " cannot be listed");
	}
    }

    /**
     * List the bindings in the specified context.
     * @return an enumeration of the bindings.
     * @param the context name.
     * @exception NamingException if there is a naming exception.
     */
    public NamingEnumeration listBindings(Name name)
            throws NamingException {
        // Flat namespace; no federation; just call string version
        return listBindings(name.toString());
    }

    /**
     * Destroy the specified subcontext.
     * @param the name of the subcontext.
     * @exception NamingException if there is a naming exception.
     */
    public void destroySubcontext(String name) throws NamingException {
	name = getRelativeName (name);
	if (isjavaURL(name)) {	    
	    javaUrlContext.destroySubcontext(name);
	} else {
	    try {
	        getProvider().destroySubcontext(name);
	    } catch(RemoteException e) {
	        CommunicationException ce = 
		  new CommunicationException(e.toString());
		ce.initCause(e);
		throw ce;
	    }
	}
    }

    /**
     * Destroy the specified subcontext.
     * @param the name of the subcontext.
     * @exception NamingException if there is a naming exception.
     */
    public void destroySubcontext(Name name) throws NamingException {
        // Flat namespace; no federation; just call string version
        destroySubcontext(name.toString());
    }

    /**
     * Create the specified subcontext.
     * @return the created subcontext.
     * @param the name of the subcontext.
     * @exception NamingException if there is a naming exception.
     */
    public Context createSubcontext(String name)
            throws NamingException {
	Context c = null;
	name = getRelativeName (name);
	if (isjavaURL(name)) {	    
	    return javaUrlContext.createSubcontext(name);
	} else {
	    try {
	        c = getProvider().createSubcontext(name);
		/* this simulates the transient context structure on the
		 * client side. Have to do this - as reference to
		 * Transient Context is not resolved properly due to rmi
		 */
		if (c instanceof Context){
		    c = new SerialContext (name, myEnv);
		}
	    } catch(RemoteException e) {
	        CommunicationException ce = 
		  new CommunicationException(e.toString());
		ce.initCause(e);
		throw ce;
	    }
	    return c;
	}
    }

    /**
     * Create the specified subcontext.
     * @return the created subcontext.
     * @param the name of the subcontext.
     * @exception NamingException if there is a naming exception.
     */
    public Context createSubcontext(Name name) throws NamingException {
        // Flat namespace; no federation; just call string version
        return createSubcontext(name.toString());
    }

    /**
     * Links are not treated specially.
     * @param the name of the link.
     * @return the resolved object.
     * @exception NamingException if there is a naming exception.
     */
    public Object lookupLink(String name) throws NamingException {
	name = getRelativeName(name);
	if (isjavaURL(name)) {	    
	    return javaUrlContext.lookupLink(name);
	} else {
            // This flat context does not treat links specially
	    return lookup(name);
	}
    }

    /**
     * Links are not treated specially.
     * @param the name of the link.
     * @return the resolved object.
     * @exception NamingException if there is a naming exception.
     */
    public Object lookupLink(Name name) throws NamingException {
        // Flat namespace; no federation; just call string version
        return lookupLink(name.toString());
    }

    /**
     * Allow access to the name parser object.
     * @param String JNDI name, is ignored since there is only one Name
     * Parser object.
     * @exception NamingException
     * @return NameParser object
     */
    public NameParser getNameParser(String name)
            throws NamingException {
        return myParser;
    }

    /**
     * Allow access to the name parser object.
     * @param String JNDI name, is ignored since there is only one Name
     * Parser object.
     * @exception NamingException
     * @return NameParser object
     */
    public NameParser getNameParser(Name name) throws NamingException {
        // Flat namespace; no federation; just call string version
        return getNameParser(name.toString());
    }

    public String composeName(String name, String prefix)
            throws NamingException {
        Name result = composeName(new CompositeName(name),
                                  new CompositeName(prefix));
        return result.toString();
    }

    public Name composeName(Name name, Name prefix)
            throws NamingException {
        Name result = (Name)(prefix.clone());
        result.addAll(name);
        return result;
    }

    /**
     * Add to the environment for the current context.
     * @exception NamingException if there is a naming exception.
     */
    public Object addToEnvironment(String propName, Object propVal)
            throws NamingException {
        if (myEnv == null) {
            myEnv = new Hashtable(5, 0.75f);
        } 
        return myEnv.put(propName, propVal);
    }

    /**
     * Remove from the environment for the current context.
     * @exception NamingException if there is a naming exception.
     */
    public Object removeFromEnvironment(String propName) 
		throws NamingException {
        if (myEnv == null) {
            return null;
        }
        return myEnv.remove(propName);
    }

    /**
     * Return the environment for the current context.
     * @exception NamingException if there is a naming exception.
     */
    public Hashtable getEnvironment() throws NamingException {
        if (myEnv == null) {
            // Must return non-null
            myEnv = new Hashtable(3, 0.75f);
        }
        return myEnv;
    }

    /**
     * Set the environment for the current context to null when close is
     * called.
     * @exception NamingException if there is a naming exception.
     */
    public void close() throws NamingException {
        myEnv = null;
    }

    private String getRelativeName(String name) {
	if(!myName.equals("")) {
	    name = myName + "/" + name;
	} 
	return name;
    }

    // Class for enumerating name/class pairs
    class RepNames implements NamingEnumeration {
        Hashtable bindings;
        Enumeration names;

        RepNames (Hashtable bindings) {
            this.bindings = bindings;
            this.names = bindings.keys();
        }

        public boolean hasMoreElements() {
            return names.hasMoreElements();
        }

        public boolean hasMore() throws NamingException {
            return hasMoreElements();
        }

        public Object nextElement() {
            if(names.hasMoreElements())
            {
                String name = (String)names.nextElement();
                String className = bindings.get(name).getClass().getName();
                return new NameClassPair(name, className);
            }
            else
                return null;
        }

        public Object next() throws NamingException {
            return nextElement();
        }

        // New API for JNDI 1.2
        public void close() throws NamingException {
            throw new OperationNotSupportedException("close() not implemented");
        }
    }

    // Class for enumerating bindings
    class RepBindings implements NamingEnumeration {
        Enumeration names;
        Hashtable bindings;

        RepBindings (Hashtable bindings) {
            this.bindings = bindings;
            this.names = bindings.keys();
        }

        public boolean hasMoreElements() {
            return names.hasMoreElements();
        }

        public boolean hasMore() throws NamingException {
            return hasMoreElements();
        }

        public Object nextElement() {
            if(hasMoreElements())
            {
                String name = (String)names.nextElement();
                return new Binding(name, bindings.get(name));
            }
            else
                return null;
        }

        public Object next() throws NamingException {
            return nextElement();
        }

        // New API for JNDI 1.2
        public void close() throws NamingException {
            throw new OperationNotSupportedException("close() not implemented");
        }
    }


  /**
   * This is a temporary solution to store 
   * the sticky context as a threadlocal variable. bug 5050591
   * context is the sticky context
   * count is needed to know how many times the lookup method is being called
   * from within the user code's ic.lookup(). e.g. JMS resource lookups (via ConnectorObjectFactory)
   */
   class  ThreadLocalIC {

        Context ctx;
        int count = 0;

        public ThreadLocalIC(Context ctx, int count) {
	    this.ctx = ctx;
	    this.count = count;
	}

        public void setStickyContext(Context ctx) {
	    ctx = ctx; 
	}
    
        public Context getStickyContext() {
	    return ctx;
	}
    
        public void setStickyCount(int count) {
	    count = count;
      
	}

        public int getStickyCount() {
	  return count;      
	}

        public void incrementCount() {
	    count++;
	}

        public void decrementCount() {
	    count--;
	}
    }
};


