/* JFox, the OpenSource J2EE Application Server
 *
 * Distributable under GNU LGPL license by gun.org
 * more details please visit http://www.huihoo.org/jfox
 */

package org.jfox.ejb;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.EJBException;
import javax.ejb.EJBHome;
import javax.ejb.EJBMetaData;
import javax.ejb.EJBObject;
import javax.ejb.EnterpriseBean;
import javax.ejb.HomeHandle;
import javax.ejb.TimedObject;
import javax.ejb.TimerService;
import javax.naming.LinkRef;
import javax.naming.NamingException;

import org.jfox.ejb.meta.EJBDescriptor;
import org.jfox.ejb.meta.EJBReferenceDescriptor;
import org.jfox.ejb.meta.EnvEntryDescriptor;
import org.jfox.ejb.meta.ResourceEnvRefDescriptor;
import org.jfox.ejb.meta.ResourceRefDescriptor;
import org.jfox.ejb.timer.EJBTimerService;
import org.jfox.ejb.timer.TimedObjectMethod;
import org.jfox.framework.logger.Logger;
import org.jfox.jndi.enc.EnterpriseContext;
import org.jfox.system.AbstractComponent;

/**
 * 提供 bucket 的公共方法
 *
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public abstract class BucketSupport extends AbstractComponent implements Bucket {
//  protected Logger logger = Logger.getLogger(getClass().getName());
    protected BucketMetaData bucketMeta = null;
//  protected Container container = null;

    // homeClassName 将作为ObjectId 的前缀
    protected String homeClassName = null;
    /**
     * EJBObject 接口中定义的方法
     * methodHashCode => Method Object
     */
    protected Map beanMethods = new HashMap();
    protected Map homeMethods = new HashMap();

    protected EJBObjectIDGenerator idgen = null;

//  protected List interceptors = new ArrayList();

    // enc
    protected EnterpriseContext enc = new EnterpriseContext();

    private TimerService timerService;

    public BucketSupport(BucketMetaData meta) {
        this.bucketMeta = meta;
        logger = Logger.getLogger(this.getClass().getName() + "[" + bucketMeta.getBeanClass().getName() + "]");
        idgen = EJBObjectIDGenerator.newInstance(meta.getEJBDescriptor().getEjbName(),
                meta.getEJBMetaData().getHomeInterfaceClass().getName(),
                meta.getEJBMetaData().getRemoteInterfaceClass().getName());

        this.homeClassName = meta.getEJBMetaData().getHomeInterfaceClass().getName();
        homeMethods = introspectorInterfMethods(meta.getEJBMetaData().getHomeInterfaceClass());
        beanMethods = introspectorBeanMethods(meta.getBeanClass(), meta.getEJBMetaData().getRemoteInterfaceClass());
        beanMethods.putAll(introspectorInterfMethods(EJBObject.class));

        timerService = new EJBTimerService(bucketMeta.getEJBDescriptor().getEjbName());

        // setEJBHome , 以便 doInit 能够将其绑定到 jndi
        ((EJBMetaDataImpl) meta.getEJBMetaData()).setEJBHome(createEJBHome());

    }

    public ClassLoader getEJBClassLoader() {
        return bucketMeta.getBeanClass().getClassLoader();
    }

    public EJBMetaData getEJBMetaData() throws RemoteException {
        return bucketMeta.getEJBMetaData();
    }

    public EJBHome getEJBHome() throws RemoteException {
        return bucketMeta.getEJBMetaData().getEJBHome();
    }

    public HomeHandle getHomeHandle() throws RemoteException {
        return new HomeHandleImpl(bucketMeta.getEJBDescriptor().getJndiName());
    }

    public EnterpriseContext getEnterpriseContext() {
        return enc;
    }

    // call back the ejbCreate method
    public void ejbCreate(EnterpriseBean bean, String createMethod, Object[] args) throws RemoteException {
        char c = createMethod.charAt(0);
        String ejbCreateMethodName = "ejb" + Character.toUpperCase(c) + createMethod.substring(1);

        logger.debug(bean + " " + ejbCreateMethodName);
        Class[] signatures = null;
        if(args != null && args.length != 0) {
            signatures = new Class[args.length];
            for(int i = 0; i < args.length; i++) {
                signatures[i] = args[i].getClass();
            }
        }

        try {
            bean.getClass().getMethod(ejbCreateMethodName, signatures).invoke(bean, args);
        }
        catch(Exception e) {
            throw new RemoteException(e.getMessage(), e);
        }
    }

    /**
     * 生成 EJBHome ，使用 DelegateInvocationHandler, 由客户来决定使用什么协议
     *
     * @return
     */
    private EJBHome createEJBHome() {
        EJBHome home = (EJBHome) Proxy.newProxyInstance(bucketMeta.getEJBMetaData().getHomeInterfaceClass().getClassLoader(),
                new Class[]{bucketMeta.getEJBMetaData().getHomeInterfaceClass(), ExtendedEJBHome.class},
                new DelegateInvocationHandler(this.getHomeObjectId(),
                        bucketMeta.getEJBDescriptor().getRemoteProtocol(),
                        bucketMeta.getEJBDescriptor().getLocalProtocol()));
        logger.debug("createEJBHome " + homeClassName + " Successful");
        return home;
    }

    /**
     * 缓存在 EJBHome,EJBObject 接口中定义的对象
     *
     * @param classObj
     * @return
     */
    protected Map introspectorInterfMethods(Class classObj) {
        Map homeMethods = new HashMap();
        Method[] methods = classObj.getMethods();
        for(int i = 0; i < methods.length; i++) {
            long hashCode = MethodHasher.getMethodHash(methods[i]);
            homeMethods.put(String.valueOf(hashCode), methods[i]);
            logger.debug("Cache method: " + hashCode + " => " + methods[i].getName());
        }
        return homeMethods;
    }

    /**
     * 缓存在 EnterpriseBean 类中定义的对象
     * 这些方法应该都是已经在 EJBObject 接口中已经定义的
     *
     * @param beanClass
     * @return
     */
    protected Map introspectorBeanMethods(Class beanClass, Class remoteInterface) {
        Map methods = new HashMap();
        Method[] remoteMethods = remoteInterface.getDeclaredMethods();
        for(int i = 0; i < remoteMethods.length; i++) {
            Method _method = remoteMethods[i];
            try {
                Method method = beanClass.getMethod(_method.getName(), _method.getParameterTypes());
                long hashCode = MethodHasher.getMethodHash(method);
                methods.put(String.valueOf(hashCode), method);
                logger.debug("Cache business method: " + hashCode + " => " + method.getName());
            }
            catch(Exception e) {
                throw new EJBException(e);
            }
        }

        // TimedObject
        if(TimedObject.class.isAssignableFrom(beanClass)) {
            long hashCode = MethodHasher.getMethodHash(TimedObjectMethod.TimeOut);
            methods.put(String.valueOf(hashCode), TimedObjectMethod.TimeOut);
            logger.debug("Cache business method: " + hashCode + " => " + TimedObjectMethod.TimeOut.getName());
        }

        return methods;
    }


    public Method getHomeMethod(Invocation invocation) throws NoSuchMethodException {
        if(!homeMethods.containsKey(invocation.getMethodHash())) {
            throw new NoSuchMethodException("no such EJBHome method with method hash is " + invocation.getMethodHash());
        }
        return (Method) homeMethods.get(invocation.getMethodHash());
    }

    public Method getBeanMethod(Invocation invocation) throws NoSuchMethodException {
        if(!beanMethods.containsKey(invocation.getMethodHash())) {
            throw new NoSuchMethodException("no such EnterpriseBean method with method hash is " + invocation.getMethodHash());
        }
        return (Method) beanMethods.get(invocation.getMethodHash());
    }

    public EJBDescriptor getEJBDescriptor() {
        return bucketMeta.getEJBDescriptor();
    }

    public TimerService getTimerService() {
        return timerService;
    }

    public EJBObjectId nextObjectId() {
        return idgen.nextBeanObjectId();
    }

    public EJBObjectId getHomeObjectId() {
        return idgen.getHomeObjectId();
    }

    public Class getBeanClass() {
        return bucketMeta.getBeanClass();
    }

    protected void doInit() throws Exception {
        EJBDescriptor ejbDescriptor = bucketMeta.getEJBDescriptor();
        ContainerImpl.INITIAL_CONTEXT.bind(ejbDescriptor.getJndiName(), bucketMeta.getEJBMetaData().getEJBHome());

        // bind refrence to EnterpriseContext
        bindReference();

    }

    protected void doDestroy() throws Exception {
        ContainerImpl.INITIAL_CONTEXT.unbind(bucketMeta.getEJBDescriptor().getJndiName());
    }

    private void bindReference() {
        EJBDescriptor ejbDescriptor = bucketMeta.getEJBDescriptor();
        try {
            List entries = ejbDescriptor.getEnvEntries();
            for(int i = 0; i < entries.size(); i++) {
                EnvEntryDescriptor env = (EnvEntryDescriptor) entries.get(i);
                String name = env.getName();
                Object value = env.getValueObject();
                enc.bind(name, value);
                logger.debug("bind env-entry, name: " + name);
            }

            List ejbRefs = ejbDescriptor.getEjbReferences();
            for(int i = 0; i < ejbRefs.size(); i++) {
                EJBReferenceDescriptor ejbRef = (EJBReferenceDescriptor) ejbRefs.get(i);
//        checkEjbReference(ejbRef);
                LinkRef linkRef = new LinkRef(ejbRef.getRefName());
                enc.bind(ejbRef.getRefName(), linkRef);
                logger.debug("bind ejb-ref, name: " + ejbRef.getRefName());
            }

            List resRefs = ejbDescriptor.getResourceReferences();
            for(int i = 0; i < resRefs.size(); i++) {
                ResourceRefDescriptor resRef = (ResourceRefDescriptor) resRefs.get(i);
//        checkResourceRef(resRef);
                LinkRef linkRef = new LinkRef(resRef.getRefName());
                enc.bind(resRef.getRefName(), linkRef);
                logger.debug("bind resource-ref, name: " + resRef.getRefName());
            }

            List resourceEnvRefs = ejbDescriptor.getResourceEnvReferences();
            for(int i = 0; i < resourceEnvRefs.size(); i++) {
                ResourceEnvRefDescriptor resourceEnvRef = (ResourceEnvRefDescriptor) resourceEnvRefs.get(i);
//        checkResourceEnvRef(resourceEnvRef);
                LinkRef linkRef = new LinkRef(resourceEnvRef.getRefName());
                enc.bind(resourceEnvRef.getRefName(), linkRef);
                logger.debug("bind resource-env-ref, name: " + resourceEnvRef.getRefName());
            }

        }
        catch(NamingException e) {
            logger.warn("bind EnterpriseContext error", e);
        }
    }


}

