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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.EJBHome;
import javax.ejb.EJBMetaData;
import javax.ejb.EJBObject;
import javax.ejb.EnterpriseBean;

import org.jfox.ejb.BucketMetaData;
import org.jfox.ejb.BucketMetaDataImpl;
import org.jfox.ejb.Container;
import org.jfox.ejb.ContainerImpl;
import org.jfox.ejb.EJBMetaDataImpl;
import org.jfox.ejb.meta.EJBBundle;
import org.jfox.ejb.meta.EJBDescriptor;
import org.jfox.ejb.meta.EJBMeta;
import org.jfox.ejb.meta.EJBMetaSupport;
import org.jfox.ejb.meta.SessionBeanDescriptor;
import org.jfox.framework.deploy.DeployException;
import org.jfox.framework.deploy.Deployable;
import org.jfox.framework.deploy.Deployer;
import org.jfox.framework.logger.Logger;
import org.jfox.system.AbstractComponent;

/**
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public class EJBDeployer extends AbstractComponent implements Deployer {
    private Container container;
    private Logger logger = Logger.getLogger(getClass().getName());

    // ejbModule => ejbDescriptor List
    private Map deployedEjbs = new HashMap();

//  private static EJBDeployer instance = new EJBDeployer(ContainerImpl.getInstance());

    public EJBDeployer() {
        this.container = ContainerImpl.getInstance();
    }

    public void deploy(Deployable comp) throws DeployException {
        if(!(comp instanceof EJBModule)) {
            throw new DeployException("can not deploy non ejb module");
        }
        EJBModule ejbModule = (EJBModule) comp;
        // redeploy, do undeploy first
        if(deployedEjbs.containsKey(ejbModule)) {
            undeploy(ejbModule);
        }

        ClassLoader loader = ejbModule.getClassLoader();
        EJBBundle ejbBundle = ejbModule.getEJBBundle();
        EJBDescriptor[] ejbDescriptors = ejbBundle.getEjbDescriptors();
        for(int i = 0; i < ejbDescriptors.length; i++) {
            EJBDescriptor ejbDescriptor = ejbDescriptors[i];
            try {
                // load EJBMeta as addition description
                loadEjbMeta(loader, ejbDescriptor);
                // try to deploy ejb
                deploy(loader, ejbDescriptor);
                addDeployedEjb(ejbModule, ejbDescriptor);
            }
            catch(Exception e) {
                logger.warn(e);
            }
        }
    }

    protected void doInit() throws Exception {
    }

    protected void doDestroy() throws Exception {
    }

    private void addDeployedEjb(EJBModule ejbModule, EJBDescriptor ejbDescriptor) {
        if(!deployedEjbs.containsKey(ejbModule)) {
            List ejbDesriptors = new ArrayList();
            ejbDesriptors.add(ejbDescriptor);
            deployedEjbs.put(ejbModule, ejbDesriptors);
        }
        else {
            List ejbDescriptors = (List) deployedEjbs.get(ejbModule);
            ejbDescriptors.add(ejbDescriptor);
        }
    }

    private void deploy(ClassLoader loader, EJBDescriptor ejbDescriptor) throws DeployException {

        try {
            Class homeClass = loader.loadClass(ejbDescriptor.getHomeClassName());
            loader.loadClass("javax.ejb.EJBHome");
            if(!testHomeClass(homeClass)) {
                logger.warn("Home Class: " + homeClass + " not extends " + EJBHome.class.getName());
            }

            Class remoteClass = loader.loadClass(ejbDescriptor.getRemoteClassName());
            if(!testRemoteClass(remoteClass)) {
                logger.warn("Bean Interface Class: " + remoteClass + " not extends " + EJBObject.class.getName());
            }

            Class beanClass = loader.loadClass(ejbDescriptor.getEjbClassName());
            if(!testBeanClass(beanClass)) {
                logger.warn("Bean Class: " + beanClass + " not extends " + EnterpriseBean.class.getName());
            }

            EJBMetaData ejbMetaData = null;
            if(ejbDescriptor.getType().equals(EJBDescriptor.SESSION_BEAN)) { // is SessionBean
                ejbMetaData = new EJBMetaDataImpl(homeClass, remoteClass, true, ((SessionBeanDescriptor) ejbDescriptor).isStateless());
            }
            else { // Maybe EntityBean, but not support now
                ejbMetaData = new EJBMetaDataImpl(homeClass, remoteClass, true, false);
            }
            // will create a ejb bucket and bind it's home to jndi
            BucketMetaData bucketMeta = new BucketMetaDataImpl(ejbMetaData, ejbDescriptor, beanClass);
            container.createBucket(bucketMeta);
            logger.info("deploy bean [ " + beanClass.getName() + " ] successful");
        }
        catch(Exception e) {
            throw new DeployException("deploy ejb " + ejbDescriptor.getEjbName() + " failed.", e);
        }
    }

    public void undeploy(Deployable comp) throws DeployException {
        EJBModule ejbModule = (EJBModule) comp;
        if(deployedEjbs.containsKey(ejbModule)) {
            List ejbDescriptors = (List) deployedEjbs.get(ejbModule);
            for(int i = 0; i < ejbDescriptors.size(); i++) {
                String ejbName = ((EJBDescriptor) ejbDescriptors.get(i)).getEjbName();
                try {
                    container.dropBucket(ejbName);
                    logger.debug("undeploy " + ejbName + " successful.");
                }
                catch(Exception e) {
                    logger.warn("undeploy " + ejbName + " failed.", e);
                }
            }
        }
        deployedEjbs.remove(ejbModule);
    }

    private boolean testHomeClass(Class homeClass) {
        return EJBHome.class.isAssignableFrom(homeClass);
    }

    private boolean testRemoteClass(Class remoteClass) {
        return EJBObject.class.isAssignableFrom(remoteClass);
    }

    private boolean testBeanClass(Class beanClass) {
        return EnterpriseBean.class.isAssignableFrom(beanClass);
    }

    /**
     * load ejb meta from Meta class
     */
    private void loadEjbMeta(ClassLoader loader, EJBDescriptor ejbDescriptor) {
        EJBMeta ejbMeta = null;
        try {
            Class metaClass = loader.loadClass(ejbDescriptor.getRemoteClassName() + "Meta");
            logger.debug("load EJBMeta: " + metaClass.getName());
            if(testMetaClass(metaClass)) {
                ejbMeta = (EJBMeta) metaClass.getConstructor(new Class[]{EJBDescriptor.class}).newInstance(new Object[]{ejbDescriptor.clone()});
            }
            else {
                logger.warn("Meta Interface Class: " + metaClass + " not extends " + EJBMeta.class.getName() + ", will use default EJBMetaSupport");
                ejbMeta = new EJBMetaSupport(ejbDescriptor);
            }
        }
        catch(Exception ignore) {
            // not define EJBMeta, use the default EJBMetaSupport
            logger.debug("use default EJBMetaSupport in ejb " + ejbDescriptor.getEjbName() + " because " + ignore.toString());
            ejbMeta = new EJBMetaSupport(ejbDescriptor);
        }
        ejbDescriptor.setJndiName(ejbMeta.getJndiName());
        ejbDescriptor.setRemoteProtocol(ejbMeta.getRemoteProtocol());
        ejbDescriptor.setLocalProtocol(ejbMeta.getLocalProtocol());

    }

    private boolean testMetaClass(Class metaClass) {
        return EJBMeta.class.isAssignableFrom(metaClass);
    }


    public static void main(String[] args) {

    }
}

