/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.ejb3.injection;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import javax.annotation.Resources;
import javax.ejb.EJBContext;
import javax.ejb.TimerService;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.LinkRef;
import javax.naming.NamingException;
import javax.transaction.UserTransaction;
import org.jboss.aop.Advisor;
import org.jboss.ejb3.Container;
import org.jboss.logging.Logger;
import org.jboss.naming.Util;

/**
 * Searches bean class for all @Inject and create Injectors
 *
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision: 1.5.2.5 $
 */
public class ResourceHandler
{
   private static final Logger log = Logger.getLogger(ResourceHandler.class);
   
   private static void loadResourcesAnnotation(Container container, Context resourceCtx) throws Exception
   {
      Resources resources = (Resources) ((Advisor) container).resolveAnnotation(Resources.class);
      if (resources == null) return;
      for (Resource ref : resources.value())
      {
         String encName = ref.name();
         if (encName == null || encName.equals(""))
         {
            throw new RuntimeException("JBoss requires name() for class level @Resource");
         }
         String jndiName = ref.name();
         InitialContext ctx = container.getInitialContext();
         try
         {
            ctx.lookup(jndiName);
         }
         catch (NamingException e)
         {
            jndiName = "java:/" + jndiName;
            ctx.lookup(jndiName);
         }
         Util.bind(resourceCtx,
                   encName,
                   new LinkRef(jndiName));
      }
   }

   public static List loadInjectors(Container container) throws Exception
   {
      Class clazz = container.getBeanClass();
      Method[] methods = clazz.getMethods();
      ArrayList list = new ArrayList();
      InitialContext ctx = container.getInitialContext();
      Context resourceCtx = Util.createSubcontext(container.getInitialContext(), Container.ENC_CTX_NAME + "/env/resource");
      for (int i = 0; i < methods.length; i++)
      {
         Resource ref = (Resource) ((Advisor) container).resolveAnnotation(methods[i], Resource.class);
         if (ref != null)
         {
            if (!methods[i].getName().startsWith("set")) throw new RuntimeException("@Resource can only be used with a set method: " + methods[i]);
            if (methods[i].getParameterTypes().length != 1) throw new RuntimeException("@Resource can only be used with a set method of one parameter: " + methods[i]);
            Class type = methods[i].getParameterTypes()[0];
            if (!ref.type().equals(Object.class))
            {
               type = ref.type();
            }
            String encName = ref.name();
            if (encName == null || encName.equals(""))
            {
               encName = methods[i].getName().substring(3);
            }
            if (type.equals(UserTransaction.class))
            {
               list.add(new UserTransactionMethodInjector(methods[i], container));
            }
            else if (type.equals(TimerService.class))
            {
               list.add(new TimerServiceMethodInjector(methods[i], container));
            }
            else if (EJBContext.class.isAssignableFrom(type))
            {
               list.add(new EJBContextMethodInjector(methods[i]));
            }
            else
            {
               String jndiName = ref.name();
               if (jndiName == null || jndiName.equals(""))
               {
                  jndiName = methods[i].getName().substring(3);
               }
               try
               {
                  ctx.lookup(jndiName);
               }
               catch (NamingException e)
               {
                  jndiName = "java:/" + jndiName;
                  ctx.lookup(jndiName);
               }
               Util.bind(resourceCtx,
                         encName,
                         new LinkRef(jndiName));
               list.add(new JndiMethodInjector(methods[i], jndiName, ctx));
            }
         }
      }
      loadResourcesAnnotation(container, resourceCtx);
      loadFieldInjectors(clazz, container, list, resourceCtx);
      return list;
   }

   private static void loadFieldInjectors(Class clazz, Container container, ArrayList list, Context resourceCtx) throws Exception
   {
      if (clazz == null || clazz.equals(java.lang.Object.class)) return;
      loadFieldInjectors(clazz.getSuperclass(), container, list, resourceCtx);
      Field[] fields = clazz.getDeclaredFields();
      InitialContext ctx = container.getInitialContext();
      for (int i = 0; i < fields.length; i++)
      {
         Resource ref = (Resource) ((Advisor) container).resolveAnnotation(fields[i], Resource.class);
         if (ref != null)
         {
            fields[i].setAccessible(true);
            Class type = fields[i].getType();
            if (!ref.type().equals(Object.class))
            {
               type = ref.type();
            }
            String encName = ref.name();
            if (encName == null || encName.equals(""))
            {
               encName = fields[i].getName();
            }
            if (type.equals(UserTransaction.class))
            {
               list.add(new UserTransactionFieldInjector(fields[i], container));
            }
            else if (type.equals(TimerService.class))
            {
               list.add(new TimerServiceFieldInjector(fields[i], container));
            }
            else if (EJBContext.class.isAssignableFrom(type))
            {
               list.add(new EJBContextFieldInjector(fields[i]));
            }
            else if (javax.jms.ConnectionFactory.class.isAssignableFrom(type))
            {
               String jndiName = type.getSimpleName();
               
               ctx.lookup(jndiName);
               Util.bind(resourceCtx,
                     encName,
                     new LinkRef(jndiName));
               list.add(new JndiFieldInjector(fields[i], jndiName, ctx));
            }
            else if (javax.jms.Destination.class.isAssignableFrom(type))
            {
               String jndiName;
               
               if (javax.jms.Queue.class.isAssignableFrom(type))
                  jndiName = "queue/" + fields[i].getName();
               else
                  jndiName = "topic/" + fields[i].getName();
                     
               ctx.lookup(jndiName);
               Util.bind(resourceCtx,
                     encName,
                     new LinkRef(jndiName));
               list.add(new JndiFieldInjector(fields[i], jndiName, ctx));
            }
            else
            {
               String jndiName = ref.name();
               if (jndiName == null || jndiName.equals(""))
               {
                  jndiName = fields[i].getName();
               }
               try
               {
                  ctx.lookup(jndiName);
                  Util.bind(resourceCtx,
                        encName,
                        new LinkRef(jndiName));
                  list.add(new JndiFieldInjector(fields[i], jndiName, ctx));
               }
               catch (NamingException e)
               {
                  try 
                  {
                     jndiName = "java:/" + jndiName;
                     ctx.lookup(jndiName);
                     Util.bind(resourceCtx,
                           encName,
                           new LinkRef(jndiName));
                     list.add(new JndiFieldInjector(fields[i], jndiName, ctx));
                  } catch (NamingException e1)
                  {
                     // ignore for support of <env-entry> elements
                     log.warn("Unbound injected resource " + fields[i].getType() + " " +  fields[i].getName());
                  }
               }
               
            }
         }
      }
   }
}
