/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/ejb/cfg/EjbSessionBean.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.ejb.cfg;

import static javax.ejb.TransactionAttributeType.REQUIRED;

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;

import javax.annotation.PostConstruct;
import javax.ejb.Local;
import javax.ejb.LocalBean;
import javax.ejb.Remote;
import javax.ejb.SessionSynchronization;
import javax.ejb.Singleton;
import javax.ejb.Stateful;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;

import com.caucho.config.ConfigException;
import com.caucho.config.Configurable;
import com.caucho.config.LineConfigException;
import com.caucho.config.gen.TransactionAttributeLiteral;
import com.caucho.config.inject.InjectManager;
import com.caucho.config.reflect.AnnotatedTypeImpl;
import com.caucho.config.reflect.BaseType;
import com.caucho.ejb.manager.EjbManager;
import com.caucho.ejb.session.AbstractSessionManager;
import com.caucho.ejb.session.SingletonManager;
import com.caucho.ejb.session.StatefulManager;
import com.caucho.ejb.session.StatelessManager;
import com.caucho.util.L10N;

/**
 * Configuration for an ejb entity bean.
 */
public class EjbSessionBean<X> extends EjbBean<X> {
  private static final L10N L = new L10N(EjbSessionBean.class);

  private Class<? extends Annotation> _sessionType;

  /**
   * Creates a new session bean configuration.
   */
  public EjbSessionBean(EjbConfig ejbConfig, 
                        AnnotatedType<X> rawAnnType,
                        AnnotatedType<X> annType,
                        String ejbModuleName)
  {
    super(ejbConfig, rawAnnType, annType, ejbModuleName);
  }

  /**
   * Returns the kind of bean.
   */
  @Override
  public String getEJBKind()
  {
    return "session";
  }

  /**
   * Sets the ejb implementation class.
   */
  @Override
  public void setEJBClass(Class<X> type) throws ConfigException
  {
    super.setEJBClass(type);

    AnnotatedType<X> ejbClass = getAnnotatedType();
    Class<X> ejbJavaClass = ejbClass.getJavaClass();
    
    if (Modifier.isAbstract(ejbJavaClass.getModifiers()))
      throw error(L.l(
             "'{0}' must not be abstract.  Session bean implementations must be fully implemented.",
             ejbJavaClass.getName()));

    if (ejbClass.isAnnotationPresent(Stateless.class)) {
      Stateless stateless = ejbClass.getAnnotation(Stateless.class);

      if (getEJBName() == null && !"".equals(stateless.name()))
        setEJBName(stateless.name());

      _sessionType = Stateless.class;
    } else if (ejbClass.isAnnotationPresent(Stateful.class)) {
      Stateful stateful = ejbClass.getAnnotation(Stateful.class);

      if (getEJBName() == null && !"".equals(stateful.name()))
        setEJBName(stateful.name());

      _sessionType = Stateful.class;
    }

    if (getEJBName() == null)
      setEJBName(ejbJavaClass.getSimpleName());

    /*
     * if (! ejbClass.isAssignableTo(SessionBean.class) && !
     * ejbClass.isAnnotationPresent(Stateless.class) && !
     * ejbClass.isAnnotationPresent(Stateful.class)) throwerror(L.l(
     * "'{0}' must implement SessionBean or @Stateless or @Stateful.  Session beans must implement javax.ejb.SessionBean."
     * , ejbClass.getName()));
     */

    // introspectSession();
  }

  /**
   * Gets the session bean type.
   */
  public Class<? extends Annotation> getSessionType()
  {
    return _sessionType;
  }

  /**
   * Returns true if the container handles transactions.
   */
  @Override
  public boolean isContainerTransaction()
  {
    return _isContainerTransaction;
  }

  /**
   * Set true if the container handles transactions.
   */
  public void setTransactionType(String type)
    throws ConfigException
  {
    if (type.equals("Container"))
      _isContainerTransaction = true;
    else if (type.equals("Bean"))
      _isContainerTransaction = false;
    else
      throw new ConfigException(
          L.l("'{0}' is an unknown transaction-type.  transaction-type must be 'Container' or 'Bean'.",
              type));
  }

  /**
   * Configure initialization.
   */
  @PostConstruct
  public void init() throws ConfigException
  {
    super.init();

    try {
      for (AnnotatedType<?> remoteApi : getRemoteList())
        validateRemote(remoteApi);

      for (AnnotatedType<?> localApi : getLocalList())
        validateLocal(localApi);

      if (getEJBClass() == null) {
        throw error(L.l(
               "'{0}' does not have a defined ejb-class.  Session beans must have an ejb-class.",
               getEJBName()));
      }
      
      Class<X> ejbClass = getAnnotatedType().getJavaClass();

      if (! SessionSynchronization.class.isAssignableFrom(ejbClass)) {
      } else if (! Stateful.class.equals(getSessionType())) {
        throw error(L.l(
               "'{0}' must not implement SessionSynchronization.  Stateless session beans must not implement SessionSynchronization.",
               getEJBClass().getName()));
      } else if (! _isContainerTransaction) {
        throw error(L.l(
               "'{0}' must not implement SessionSynchronization.  Session beans with Bean-managed transactions may not use SessionSynchronization.",
               getEJBClass().getName()));
      }
      
      fillClassDefaults();
    } catch (LineConfigException e) {
      throw e;
    } catch (ConfigException e) {
      throw new LineConfigException(getLocation() + e.getMessage(), e);
    }
  }

  protected void fillClassDefaults()
  {
    AnnotatedTypeImpl<X> ejbClass = getAnnotatedType();
    
    TransactionManagement tm = ejbClass.getAnnotation(TransactionManagement.class);
    
    /*
    if (! ejbClassImpl.isAnnotationPresent(TransactionManagement.class)) {
      ejbClassImpl.addAnnotation(XaAnnotation.createBeanManaged());
    }
    */

    if (tm == null || tm.value() == TransactionManagementType.CONTAINER) {
      TransactionAttribute ann
        = ejbClass.getAnnotation(TransactionAttribute.class);

      if (ann == null) {
        // ejb/1100
        ejbClass.addAnnotation(new TransactionAttributeLiteral(REQUIRED));
      }
    }
  }

  /**
   * Obtain and apply initialization from annotations.
   */
  @Override
  public void initIntrospect() throws ConfigException
  {
    super.initIntrospect();

    AnnotatedType<X> type = getAnnotatedType();

    // XXX: ejb/0f78
    if (type == null)
      return;

    // ejb/0j20
    if (!type.isAnnotationPresent(Stateful.class)
        && !type.isAnnotationPresent(Stateless.class) 
        && !type.isAnnotationPresent(Singleton.class) 
        && !isAllowPOJO())
      return;

    /*
     * TCK: ejb/0f6d: bean with local and remote interfaces if (_localHome !=
     * null || _localList.size() != 0 || _remoteHome != null ||
     * _remoteList.size() != 0) return;
     */
    
    Class<?> ejbClass = type.getJavaClass();

    ArrayList<BaseType> interfaceList = new ArrayList<BaseType>();
    
    addInterfaces(interfaceList, ejbClass, true);

    Local local = type.getAnnotation(Local.class);
    if (local != null && local.value() != null) {
      _localList.clear();

      for (Class<?> api : local.value()) {
        // XXX: grab from type?
        addLocal((Class) api);
      }
    }

    Remote remote = type.getAnnotation(Remote.class);
    if (remote != null && remote.value() != null) {
      _remoteList.clear();

      for (Class<?> api : remote.value()) {
        // XXX: grab from type?
        addRemote(api);
      }
    }

    // if (getLocalList().size() != 0 || getRemoteList().size() != 0) {
    if (_localList.size() != 0 || _remoteList.size() != 0) {
    } else if (interfaceList.size() == 0) {
      // Session bean no-interface view.
    } else if (interfaceList.size() != 1)
      throw new ConfigException(
          L.l("'{0}' has multiple interfaces, but none are marked as @Local or @Remote.\n{1}",
              type.getJavaClass().getName(), interfaceList.toString()));
    else {
      addLocalType(interfaceList.get(0));
    }

    // ioc/0312
    if (type.isAnnotationPresent(LocalBean.class)) {
      setLocalBean(true);
    }
    else if (_localList.size() == 0) {
      setLocalBean(true);
    }
  }
  
  private void addInterfaces(ArrayList<BaseType> interfaceList,
                             Class<?> ejbClass,
                             boolean isTop)
  {
    if (ejbClass == null)
      return;
    
    InjectManager cdiManager = InjectManager.getCurrent();

    for (Type localApi : ejbClass.getGenericInterfaces()) {
      BaseType type = cdiManager.createTargetBaseType(localApi);
      
      Class<?> rawClass = type.getRawClass();
      
      Local local = rawClass.getAnnotation(Local.class);

      if (local != null) {
        addLocalType(type);
        continue;
      }

      javax.ejb.Remote remote = rawClass.getAnnotation(javax.ejb.Remote.class);

      if (remote != null || java.rmi.Remote.class.isAssignableFrom(rawClass)) {
        addRemoteType(type);
        continue;
      }

      if (rawClass.getName().equals("java.io.Serializable"))
        continue;

      if (rawClass.getName().equals("java.io.Externalizable"))
        continue;

      if (rawClass.getName().startsWith("javax.ejb"))
        continue;

      if (rawClass.getName().equals("java.rmi.Remote"))
        continue;

      if (isTop)
        addInterface(interfaceList, type);
    }
    
    // ejb/6040
    // addInterfaces(interfaceList, ejbClass.getSuperclass(), false);
  }
  
  private void addInterface(ArrayList<BaseType> interfaceList,
                            BaseType cl)
  {
    for (int i = interfaceList.size() - 1; i >= 0; i--) {
      BaseType oldClass = interfaceList.get(i);
      
      if (oldClass.isAssignableFrom(cl)) {
        interfaceList.set(i, cl);
        return;
      }
      else if (cl.isAssignableFrom(oldClass)) {
        return;
      }
    }
    
    interfaceList.add(cl);
  }

  /**
   * Deploys the bean.
   */
  @Override
  public AbstractSessionManager<X> deployServer(EjbManager ejbContainer,
                                                EjbLazyGenerator<X> lazyGenerator)
      throws ClassNotFoundException, ConfigException
  {
    AbstractSessionManager<X> manager;
    
    if (Stateless.class.equals(getSessionType())) {
      manager = new StatelessManager<X>(ejbContainer,
                                        getEJBName(),
                                        getModuleName(),
                                        getRawAnnotatedType(),
                                        getAnnotatedType(),
                                        lazyGenerator);
    }
    else if (Stateful.class.equals(getSessionType())) {
      manager = new StatefulManager<X>(ejbContainer,
                                       getEJBName(),
                                       getModuleName(),
                                       getRawAnnotatedType(),
                                       getAnnotatedType(),
                                       lazyGenerator);
    }
    else if (Singleton.class.equals(getSessionType())) {
      manager = new SingletonManager<X>(ejbContainer,
                                        getEJBName(),
                                        getModuleName(),
                                        getRawAnnotatedType(),
                                        getAnnotatedType(),
                                        lazyGenerator);
    }
    else
      throw new IllegalStateException(String.valueOf(getSessionType()));

    manager.setMappedName(getMappedName());
    manager.setId(getEJBModuleName() + "#" + getEJBName());
    manager.setContainerTransaction(_isContainerTransaction);
    manager.setResourceList(getResourceList());

    Thread thread = Thread.currentThread();
    ClassLoader oldLoader = thread.getContextClassLoader();

    try {
      thread.setContextClassLoader(manager.getClassLoader());

      // manager.setInjectionTarget(getInjectionTarget());

      // manager.setInitProgram(getInitProgram());

      try {
        if (getServerProgram() != null)
          getServerProgram().configure(manager);
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw ConfigException.create(e);
      }

    } finally {
      thread.setContextClassLoader(oldLoader);
    }

    return manager;
  }

  /**
   * @return Type of bean (Stateful, Stateless, etc.)
   */
  @Override
  protected String getBeanType()
  {
    return getSessionType().getSimpleName();
  }
}
