/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, 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.ejb3;

import org.jboss.annotation.ejb.LocalBinding;
import org.jboss.annotation.ejb.RemoteBinding;
import org.jboss.annotation.ejb.RemoteBindings;
import org.jboss.aop.Advisor;
import org.jboss.ejb.LocalImpl;
import org.jboss.ejb.RemoteImpl;
import org.jboss.logging.Logger;
import org.jboss.ejb3.remoting.RemoteProxyFactory;

import javax.ejb.Local;
import javax.ejb.LocalHome;
import javax.ejb.Remote;
import javax.ejb.RemoteHome;
import javax.jws.WebService;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Comment
 *
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @version $Revision: 57207 $
 */
public class ProxyFactoryHelper
{
   private static final Logger log = Logger.getLogger(ProxyFactoryHelper.class);

   public static Context getProxyFactoryContext(Context ctx)
           throws NamingException
   {

      try
      {
         return (Context) ctx.lookup("proxyFactory");
      }
      catch (NameNotFoundException e)
      {
         return ctx.createSubcontext("proxyFactory");
      }
   }

   public static String getEndpointInterface(Container container)
   {
      WebService ws = (javax.jws.WebService) ((EJBContainer) container).resolveAnnotation(javax.jws.WebService.class);
      if (ws != null)
      {
         return ws.endpointInterface();
      }
      return null;

   }

   public static Class[] getLocalInterfaces(Container container)
   {
      Local li = (javax.ejb.Local) ((EJBContainer) container).resolveAnnotation(javax.ejb.Local.class);

      if (li != null)
      {
         if (li.value().length > 0) return li.value();

         // We have an emtpy @Local annotated bean class

         ArrayList list = getBusinessInterfaces(container.getBeanClass());
         if (list.size() == 0)
            throw new RuntimeException("Use of empty @Local on bean class and there are no valid business interfaces");
         if (list.size() > 1)
            throw new RuntimeException("Use of empty @Local on bean class and there are more than one default interface");
         Class[] rtn = {(Class) list.get(0)};
         li = new LocalImpl(rtn);
         ((EJBContainer) container).getAnnotations().addClassAnnotation(javax.ejb.Local.class, li);
         return rtn;
      }

      Class beanClass = container.getBeanClass();
      String endpoint = getEndpointInterface(container);
      Class[] ri = getRemoteInterfaces(container);

      if (li == null && ri == null && endpoint == null && (beanClass.getInterfaces() == null || beanClass.getInterfaces().length == 0))
         throw new RuntimeException("bean class has no local, webservice, or remote interfaces defined and does not implement at least one business interface");

      // introspect implemented interfaces.
      if (li == null)
      {
         Class[] intfs = beanClass.getInterfaces();
         ArrayList<Class> locals = new ArrayList<Class>();
         for (Class clazz : intfs)
         {
            if (clazz.isAnnotationPresent(javax.ejb.Local.class))
            {
               locals.add(clazz);
            }
         }
         if (locals.size() > 0)
         {
            intfs = locals.toArray(new Class[locals.size()]);
            li = new LocalImpl(intfs);
            ((Advisor) container).getAnnotations().addClassAnnotation(javax.ejb.Local.class, li);
            return li.value();
         }
      }
      // no @Local interfaces implemented
      if (li == null)
      {
         // search for default
         ArrayList<Class> interfaces = getBusinessInterfaces(beanClass);
         if (interfaces.size() != 1) return null; // indeterminate

         Class intf = interfaces.get(0);
         if (ri != null)
         {
            for (Class rintf : ri)
            {
               if (intf.getName().equals(rintf.getName()))
               {
                  return null;
               }
            }
         }
         if (intf.getName().equals(endpoint)) return null;

         Class[] rtn = {intf};
         li = new LocalImpl(rtn);
         ((EJBContainer) container).getAnnotations().addClassAnnotation(javax.ejb.Local.class, li);
         return rtn;
      }
      return li.value();
   }

   public static ArrayList<Class> getBusinessInterfaces(Class beanClass)
   {
      ArrayList<Class> interfaces = new ArrayList<Class>(Arrays.asList(beanClass.getInterfaces()));
      interfaces.remove(java.io.Serializable.class);
      interfaces.remove(java.io.Externalizable.class);
      interfaces.remove(javax.ejb.SessionSynchronization.class);
      interfaces.remove(javax.ejb.TimedObject.class);
      Iterator<Class> it = interfaces.iterator();
      while (it.hasNext())
      {
         if (it.next().getName().startsWith("javax.ejb")) it.remove();
      }
      return interfaces;
   }

   public static Class getLocalHomeInterface(Container container)
   {
      Class beanClass = container.getBeanClass();
      LocalHome li = (javax.ejb.LocalHome) ((EJBContainer) container).resolveAnnotation(javax.ejb.LocalHome.class);
      if (li != null) return li.value();
      return null;
   }

   public static Class getRemoteHomeInterface(Container container)
   {
      Class beanClass = container.getBeanClass();
      RemoteHome li = (javax.ejb.RemoteHome) ((EJBContainer) container).resolveAnnotation(javax.ejb.RemoteHome.class);
      if (li != null) return li.value();
      return null;
   }

   public static boolean publishesInterface(Container container, Class businessInterface)
   {
      if (!(container instanceof SessionContainer)) return false;
      Class[] remotes = getRemoteInterfaces(container);
      if (remotes != null)
      {
         for (Class intf : remotes)
         {
            if (intf.getName().equals(businessInterface.getName())) return true;
         }
      }

      Class remoteHome = getRemoteHomeInterface(container);
      if (remoteHome != null)
      {
         if (businessInterface.getName().equals(remoteHome.getName()))
         {
            return true;
         }
      }
      Class[] locals = getLocalInterfaces(container);
      if (locals != null)
      {
         for (Class clazz : locals)
         {
            if (clazz.getName().equals(businessInterface.getName()))
            {
               return true;
            }
         }
      }
      Class localHome = getLocalHomeInterface(container);
      if (localHome != null)
      {
         if (businessInterface.getName().equals(localHome.getName()))
         {
            return true;
         }
      }

      return false;
   }

   public static String getJndiName(Container container, Class businessInterface)
   {
      if (!(container instanceof SessionContainer)) return null;
      Advisor advisor = (Advisor) container;
      Class[] remotes = getRemoteInterfaces(container);
      if (remotes != null)
      {
         for (Class clazz : remotes)
         {
            if (clazz.getName().equals(businessInterface.getName()))
            {
               RemoteBindings bindings = (RemoteBindings) advisor.resolveAnnotation(RemoteBindings.class);
               if (bindings == null)
               {
                  RemoteBinding binding = (RemoteBinding) advisor.resolveAnnotation(RemoteBinding.class);
                  if (binding == null)
                     throw new RuntimeException("RemoteBindings should not be null: " + container.getEjbName());

                  return getRemoteJndiName(container, binding);
               }
               return getRemoteJndiName(container, bindings.value()[0]);
            }
         }
      }
      Class remoteHome = getRemoteHomeInterface(container);
      if (remoteHome != null)
      {
         if (businessInterface.getName().equals(remoteHome.getName()))
         {
            return getRemoteJndiName(container) + "Home";
         }
      }
      Class[] locals = getLocalInterfaces(container);
      if (locals != null)
      {
         for (Class clazz : locals)
         {
            if (clazz.getName().equals(businessInterface.getName()))
            {
               return getLocalJndiName(container);
            }
         }
      }
      Class localHome = getLocalHomeInterface(container);
      if (localHome != null)
      {
         if (businessInterface.getName().equals(localHome.getName()))
         {
            return getLocalJndiName(container) + "Home";
         }
      }

      return null;
   }

   public static String getLocalJndiName(Container container)
   {
      return getLocalJndiName(container, true);
   }

   public static String getLocalJndiName(Container container, boolean conflictCheck)
   {
      Advisor advisor = (Advisor) container;
      LocalBinding localBinding = (LocalBinding) advisor
              .resolveAnnotation(LocalBinding.class);
      if (localBinding == null)
      {
         String name = container.getEjbName() + "/local";
         EAR ear = ((EJBContainer) container).getDeployment().getEar();
         if (ear != null) return ear.getBaseName() + "/" + name;

         if (conflictCheck)
            checkForRemoteJndiConflict(container);

         return name;
      }
      else
      {
         return localBinding.jndiBinding();
      }
   }

   private static void checkForRemoteJndiConflict(Container container)
   {
      if (((Advisor) container).resolveAnnotation(Remote.class) != null)
      {
         String remoteJndiName = getRemoteJndiName(container, false);
         String ejbName = container.getEjbName();
         if ((remoteJndiName.equals(ejbName) || remoteJndiName.startsWith(ejbName + "/")) && (!remoteJndiName.equals(ejbName + "/remote")))
            throw new javax.ejb.EJBException("Conflict between default local jndi name " + ejbName + "/local and remote jndi name " + remoteJndiName + " for ejb-name:" + ejbName + ", bean class=" + container.getBeanClass());
      }
   }

   public static Class[] getRemoteInterfaces(Container container)
   {
      Remote ri = (Remote) ((Advisor) container).resolveAnnotation(Remote.class);
      if (ri == null)
      {
         Class beanClass = container.getBeanClass();
         Class[] intfs = beanClass.getInterfaces();
         ArrayList<Class> remotes = new ArrayList<Class>();
         for (Class clazz : intfs)
         {
            if (clazz.isAnnotationPresent(Remote.class))
            {
               remotes.add(clazz);
            }
         }
         if (remotes.size() > 0)
         {
            intfs = remotes.toArray(new Class[remotes.size()]);
            ri = new RemoteImpl(intfs);
            ((Advisor) container).getAnnotations().addClassAnnotation(Remote.class, ri);
            return ri.value();
         }

         return null;
      }

      if (ri.value().length > 0) return ri.value();

      // We have an emtpy @Remote annotated bean class

      ArrayList list = getBusinessInterfaces(container.getBeanClass());
      if (list.size() == 0)
         throw new RuntimeException("Use of empty @Remote on bean class and there are no valid business interfaces");
      if (list.size() > 1)
         throw new RuntimeException("Use of empty @Remote on bean class and there are more than one default interface");
      Class[] rtn = {(Class) list.get(0)};
      ri = new RemoteImpl(rtn);
      ((EJBContainer) container).getAnnotations().addClassAnnotation(javax.ejb.Remote.class, ri);
      return rtn;
   }

   public static String getRemoteJndiName(Container container)
   {
      return getRemoteJndiName(container, true);
   }

   public static String getRemoteJndiName(Container container, boolean check)
   {
      Advisor advisor = (Advisor) container;
      RemoteBinding binding = (RemoteBinding) advisor
              .resolveAnnotation(RemoteBinding.class);

      return getRemoteJndiName(container, binding);
   }

   private static void checkForLocalJndiConflict(Container container)
   {
      if (((Advisor) container).resolveAnnotation(Local.class) != null)
      {
         String localJndiName = getLocalJndiName(container, false);
         String ejbName = container.getEjbName();
         if ((localJndiName.equals(ejbName) || localJndiName.startsWith(ejbName + "/")) && (!localJndiName.equals(ejbName + "/local")))
            throw new javax.ejb.EJBException("Conflict between default remote jndi name " + ejbName + "/remote and local jndi name " + localJndiName + " for ejb-name:" + ejbName + ", bean class=" + container.getBeanClass());

      }
   }

   public static String getRemoteJndiName(Container container, RemoteBinding binding)
   {
      return getRemoteJndiName(container, binding, true);
   }

   public static String getRemoteJndiName(Container container, RemoteBinding binding, boolean conflictCheck)
   {
      String jndiName = null;
      if (binding == null || binding.jndiBinding() == null || binding.jndiBinding().equals(""))
      {
         jndiName = getDefaultRemoteJndiName(container);

         if (conflictCheck)
            checkForLocalJndiConflict(container);
      }
      else
      {
         jndiName = binding.jndiBinding();
      }

      return jndiName;
   }

   public static String getDefaultRemoteJndiName(Container container)
   {
      String name = container.getEjbName() + "/remote";
      EAR ear = ((EJBContainer) container).getDeployment().getEar();
      if (ear != null) return ear.getBaseName() + "/" + name;
      return name;
   }
   
   public static String getClientBindUrl(RemoteBinding binding) throws Exception
   {
      String clientBindUrl = binding.clientBindUrl();
      if (clientBindUrl.trim().length() == 0)
      {
         ObjectName connectionON = new ObjectName("jboss.remoting:type=Connector,name=DefaultEjb3Connector,handler=ejb3");
         KernelAbstraction kernelAbstraction = KernelAbstractionFactory.getInstance();
         try
         {
            clientBindUrl = (String)kernelAbstraction.getAttribute(connectionON, "InvokerLocator");
         }
         catch (Exception e)
         {
            clientBindUrl = RemoteProxyFactory.DEFAULT_CLIENT_BINDING;
         }
      }
      
      return clientBindUrl;
   }
}
