/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.proxy.ejb.handle;

import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import java.security.AccessControlException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Hashtable;

import javax.ejb.Handle;
import javax.ejb.EJBObject;
import javax.naming.InitialContext;

import org.jboss.invocation.Invocation;
import org.jboss.invocation.InvocationContext;
import org.jboss.invocation.InvocationKey;
import org.jboss.invocation.InvocationType;
import org.jboss.invocation.Invoker;
import org.jboss.invocation.InvokerInterceptor;
import org.jboss.invocation.PayloadKey;
import org.jboss.logging.Logger;
import org.jboss.naming.NamingContextFactory;
import org.jboss.security.SecurityAssociation;

/**
 * An EJB stateful session bean handle.
 *
 * @author  <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
 * @author  <a href="mailto:jason@planet57.com">Jason Dillon</a>
 * @author <a href="bill@burkecentral.com">Bill Burke</a>
 * @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a>
 * @version $Revision: 84397 $
 */
public class StatefulHandleImpl
   implements Handle
{
   private static final Logger log = Logger.getLogger(StatefulHandleImpl.class);
   
   /** Serial Version Identifier. */
   static final long serialVersionUID = -6324520755180597156L;

   /** A reference to {@link Handle#getEJBObject}. */
   protected static final Method GET_EJB_OBJECT;

   /** The value of our local Invoker.ID to detect when we are local. */
   private Object invokerID = null;

   /**
    * Initialize <tt>Handle</tt> method references.
    */
   static
   {
      try
      {
         GET_EJB_OBJECT = Handle.class.getMethod("getEJBObject", new Class[0]);
      }
      catch(Exception e)
      {
         e.printStackTrace();
         throw new ExceptionInInitializerError(e);
      }
   }

   /** The identity of the bean. */
   public int objectName;
   public String jndiName;
   public String invokerProxyBinding;
   public Invoker invoker;
   public Object id;

   /** The JNDI env in effect when the home handle was created */
   protected Hashtable jndiEnv;
   
   public StatefulHandleImpl()
   {
      
   }

   /** Create an ejb handle for a stateful session bean.
    * @param objectName - the session container jmx name
    * @param jndiName - the session home ejb name
    * @param invoker - the invoker to request the EJBObject from
    * @param invokerProxyBinding - the type of invoker binding
    * @param id - the session id
    */ 
   public StatefulHandleImpl(
      int objectName,
      String jndiName,
      Invoker invoker,
      String invokerProxyBinding,
      Object id,
      Object invokerID)
   {
      this.jndiName = jndiName;
      this.id = id;
      this.jndiEnv = (Hashtable) NamingContextFactory.lastInitialContextEnv.get();
      try
      {
         String property = System.getProperty("org.jboss.ejb.sfsb.handle.V327");
         if (property != null)
         {
            this.invokerProxyBinding = invokerProxyBinding;
            this.invokerID = invokerID;
            this.objectName = objectName;
            this.invoker = invoker;
         }
      }
      catch (AccessControlException ignored)
      {
      }

   }

   /**
    * @return the internal session identifier
    */
   public Object getID()
   {
      return id;
   }

   /**
    * @return the jndi name
    */
   public String getJNDIName()
   {
      return jndiName;
   }

   /**
    * Handle implementation.
    *
    * This differs from Stateless and Entity handles which just invoke
    * standard methods (<tt>create</tt> and <tt>findByPrimaryKey</tt>
    * respectively) on the Home interface (proxy).
    * There is no equivalent option for stateful SBs, so a direct invocation
    * on the container has to be made to locate the bean by its id (the
    * stateful SB container provides an implementation of
    * <tt>getEJBObject</tt>).
    *
    * This means the security context has to be set here just as it would
    * be in the Proxy.
    *
    * @return  <tt>EJBObject</tt> reference.
    *
    * @throws ServerException    Could not get EJBObject.
    */
   public EJBObject getEJBObject() throws RemoteException
   {
      if (invokerProxyBinding != null)
      {
         try
         {
            return getEjbObjectViaInvoker(); 
         }
         catch(Exception e)
         {
            log.debug("Exception reported, try JNDI method to recover EJB object instead", e);
            return getEjbObjectViaJndi();
         }
      }               
      
      return getEjbObjectViaJndi();
   }

   /**
    * Returns wether we are local to the originating container or not. 
    */
   protected boolean isLocal()
   {
      return invokerID != null && invokerID.equals(Invoker.ID);
   }
   
   protected EJBObject getEjbObjectViaInvoker() throws Exception
   {
      if (log.isTraceEnabled())
      {
         log.trace("Using legacy invoker method for getEJBObject() invocation.");
      }
      SecurityActions sa = SecurityActions.UTIL.getSecurityActions();         
      Invocation invocation = new Invocation(
            null,
            GET_EJB_OBJECT,
            new Object[]{id},
            //No transaction set up in here? it will get picked up in the proxy
            null,
            // fix for bug 474134 from Luke Taylor
            sa.getPrincipal(),
            sa.getCredential());

      invocation.setObjectName(new Integer(objectName));
      invocation.setValue(InvocationKey.INVOKER_PROXY_BINDING,
         invokerProxyBinding, PayloadKey.AS_IS);

      // It is a home invocation
      invocation.setType(InvocationType.HOME);

      // Create an invocation context for the invocation
      InvocationContext ctx = new InvocationContext();
      invocation.setInvocationContext(ctx);
      
      // Get the invoker to the target server (cluster or node)

      // Ship it
      if (isLocal())
         return (EJBObject) InvokerInterceptor.getLocal().invoke(invocation);
      else
         return (EJBObject) invoker.invoke(invocation);
   }
   
   protected EJBObject getEjbObjectViaJndi() throws RemoteException
   {
      try
      {
         if (log.isTraceEnabled())
         {
            log.trace("Using JNDI method for getEJBObject() invocation.");
         }
         InitialContext ic = null;
         if( jndiEnv != null )
            ic = new InitialContext(jndiEnv);
         else
            ic = new InitialContext();

         Proxy proxy = (Proxy) ic.lookup(jndiName);

         // call findByPrimary on the target
         InvocationHandler ih = Proxy.getInvocationHandler(proxy);
         return (EJBObject) ih.invoke(proxy, GET_EJB_OBJECT, new Object[] {id});
      }
      catch (RemoteException e)
      {
         throw e;
      }
      catch (Throwable t)
      {
         t.printStackTrace();
         throw new RemoteException("Error during getEJBObject", t);
      }
   }

   interface SecurityActions
   {
      class UTIL
      {
         static SecurityActions getSecurityActions()
         {
            return System.getSecurityManager() == null ? NON_PRIVILEGED : PRIVILEGED;
         }
      }

      SecurityActions NON_PRIVILEGED = new SecurityActions()
      {
         public Principal getPrincipal()
         {
            return SecurityAssociation.getPrincipal();
         }

         public Object getCredential()
         {
            return SecurityAssociation.getCredential();
         }
      };

      SecurityActions PRIVILEGED = new SecurityActions()
      {
         private final PrivilegedAction getPrincipalAction = new PrivilegedAction()
         {
            public Object run()
            {
               return SecurityAssociation.getPrincipal();
            }
         };

         private final PrivilegedAction getCredentialAction = new PrivilegedAction()
         {
            public Object run()
            {
               return SecurityAssociation.getCredential();
            }
         };

         public Principal getPrincipal()
         {
            return (Principal)AccessController.doPrivileged(getPrincipalAction);
         }

         public Object getCredential()
         {
            return AccessController.doPrivileged(getCredentialAction);
         }
      };

      Principal getPrincipal();

      Object getCredential();
   }
}

