/*
 * Copyright (c) 1998-2012 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;

import com.caucho.amber.manager.AmberContainer;
import com.caucho.config.ConfigException;
import com.caucho.config.program.ContainerProgram;
import com.caucho.config.program.PropertyValueProgram;
import com.caucho.config.types.FileSetType;
import com.caucho.config.types.JndiBuilder;
import com.caucho.config.types.PathPatternType;
import com.caucho.config.types.Period;
import com.caucho.ejb.manager.EjbManager;
import com.caucho.ejb.manager.EjbEnvironmentListener;
import com.caucho.env.jpa.ListenerPersistenceEnvironment;
import com.caucho.env.jpa.PersistenceManager;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentBean;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.EnvironmentLocal;
import com.caucho.util.L10N;
import com.caucho.vfs.JarPath;
import com.caucho.vfs.MergePath;
import com.caucho.vfs.Path;
import com.caucho.vfs.Vfs;

import javax.annotation.PostConstruct;
import javax.jms.ConnectionFactory;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Server containing all the EJBs for a given configuration.
 *
 * <p>Each protocol will extend the container to override Handle creation.
 */
public class EJBServer
  implements EnvironmentBean
{
  static final L10N L = new L10N(EJBServer.class);
  protected static final Logger log
    = Logger.getLogger(EJBServer.class.getName());

  private static EnvironmentLocal<EJBServer> _localServer
    = new EnvironmentLocal<EJBServer>("caucho.ejb-server");

  protected static EnvironmentLocal<String> _localURL =
    new EnvironmentLocal<String>("caucho.url");
  
  private EjbManager _ejbContainer;
  private AmberContainer _amberContainer;

  private String _entityManagerJndiName = "java:comp/EntityManager";
  private ArrayList<Path> _ejbJars = new ArrayList<Path>();

  private MergePath _mergePath;

  private String _urlPrefix;

  private ArrayList<FileSetType> _configFileSetList =
    new ArrayList<FileSetType>();

  private ContainerProgram _jpaProgram = new ContainerProgram();
  
  // private DataSource _dataSource;
  private boolean _validateDatabaseSchema = true;

  private String _resinIsolation;
  private String _jdbcIsolation;

  private ConnectionFactory _jmsConnectionFactory;
  
  private Path _configDirectory;

  private boolean _forbidJVMCall;
  private boolean _autoCompile = true;
  private boolean _isAllowPOJO = false;

  private String _startupMode;

  private long _transactionTimeout = 0;
  private boolean _isScanPersistenceXml = true;

  /**
   * Create a server with the given prefix name.
   */
  public EJBServer()
    throws ConfigException
  {
    _ejbContainer = EjbManager.create();
    // _amberContainer = AmberContainer.create();
    
    _urlPrefix = _localURL.get();

    _mergePath = new MergePath();
    _mergePath.addMergePath(Vfs.lookup());
    _mergePath.addClassPath();
  }

  public void addJarUrls(EnvironmentClassLoader loader, Path root)
    throws java.io.IOException
  {
    Iterator<String> it = root.iterator();

    while (it.hasNext()) {

      String s = it.next();

      Path path = root.lookup(s);

      if (path.isDirectory()) {
        addJarUrls(loader, path);
      }
      else if (s.endsWith(".jar")) {
        JarPath jarPath = JarPath.create(path);

        loader.addURL(jarPath);
      }
    }
  }

  /**
   * Returns the local EJB server.
   */
  /*
    public static EnvServerManager getLocalManager()
    {
    return EnvServerManager.getLocal();
    }
  */

  /**
   * Gets the environment class loader.
   */
  public EnvironmentClassLoader getClassLoader()
  {
    return _ejbContainer.getClassLoader();
  }

  /**
   * Sets the environment class loader.
   */
  public void setEnvironmentClassLoader(EnvironmentClassLoader env)
  {
  }

  /**
   * Sets the JNDI name.
   */
  public void setName(String name)
  {
    setJndiName(name);
  }

  /**
   * Sets the JNDI name.
   */
  public void setJndiName(String name)
  {
    setJndiPrefix(name);
  }

  /**
   * Sets the JNDI name.
   */
  public void setJndiPrefix(String name)
  {
    _ejbContainer.getProtocolManager().setJndiPrefix(name);
  }

  /**
   * Gets the JNDI name.
   */
  public void setJndiLocalPrefix(String name)
  {
    _ejbContainer.getProtocolManager().setLocalJndiPrefix(name);
  }

  /**
   * Gets the remote JNDI name.
   */
  public void setJndiRemotePrefix(String name)
  {
    _ejbContainer.getProtocolManager().setRemoteJndiPrefix(name);
  }

  /**
   * Sets the EntityManager JNDI name.
   */
  public void setEntityManagerJndiName(String name)
  {
    _entityManagerJndiName = name;
  }

  /**
   * Gets the EntityManager JNDI name.
   */
  public String getEntityManagerJndiName()
  {
    return _entityManagerJndiName;
  }

  /**
   * Sets the URL-prefix for all external beans.
   */
  public void setURLPrefix(String urlPrefix)
  {
    _urlPrefix = urlPrefix;
  }

  /**
   * Gets the URL-prefix for all external beans.
   */
  public String getURLPrefix()
  {
    return _urlPrefix;
  }

  public void setConfigDirectory(Path path)
  {
    
  }
  
  /**
   * Adds an ejb jar.
   */
  public void addEJBJar(Path ejbJar)
    throws ConfigException
  {
    if (! ejbJar.canRead() || ! ejbJar.isFile())
      throw new ConfigException(L.l("<ejb-jar> {0} must refer to a valid jar file.",
                                    ejbJar.getURL()));

    // tck: sanity check
    if (_ejbJars.contains(ejbJar)) {
      log.fine("EJBServer.addEJBJar already added: " + ejbJar);
      return;
    }

    _ejbJars.add(ejbJar);
  }

  /**
   * Sets the data-source
   */
  public void setDataSource(DataSource dataSource)
    throws ConfigException
  {
    if (dataSource == null)
      throw new ConfigException(L.l("<ejb-server> data-source must be a valid DataSource."));

    _jpaProgram.addProgram(new PropertyValueProgram("jta-data-source-value", dataSource));

    // _amberContainer.setDataSource(_dataSource);
  }

  /**
   * Sets the data-source
   */
  public void setReadDataSource(DataSource dataSource)
    throws ConfigException
  {
    if (dataSource == null)
      throw new ConfigException(L.l("<ejb-server> data-source must be a valid DataSource."));

    _jpaProgram.addProgram(new PropertyValueProgram("non-jta-data-source-value", dataSource));

    // _amberContainer.setReadDataSource(dataSource);
  }

  /**
   * Sets the xa data-source
   */
  public void setXADataSource(DataSource dataSource)
    throws ConfigException
  {
    setDataSource(dataSource); // _amberContainer.setXADataSource(dataSource);
  }

  /**
   * Sets true if database schema should be generated automatically.
   */
  public void setCreateDatabaseSchema(boolean create)
  {
    // _amberContainer.setCreateDatabaseTables(create);
  }

  /**
   * True if database schema should be generated automatically.
   */
  public boolean getCreateDatabaseSchema()
  {
    //
    // return _amberContainer.getCreateDatabaseTables();
    return false;
  }

  /**
   * Sets true if database schema should be validated automatically.
   */
  public void setValidateDatabaseSchema(boolean validate)
  {
    log.config("validate-database-schema is no longer valid");
  }

  /**
   * True if database schema should be validated automatically.
   */
  public boolean getValidateDatabaseSchema()
  {
    log.config("validate-database-schema is no longer valid");
    
    return true;
  }

  /**
   * Sets true if database schema should be validated automatically.
   */
  public void setLoadLazyOnTransaction(boolean isLazy)
  {
    // _ejbContainer.setEntityLoadLazyOnTransaction(isLazy);
  }

  /**
   * Sets the jndi name of the jmsConnectionFactory
   */
  public void setJMSConnectionFactory(JndiBuilder factory)
    throws ConfigException, NamingException
  {
    Object obj = factory.getObject();

    if (! (obj instanceof ConnectionFactory))
      throw new ConfigException(L.l("'{0}' must be a JMS ConnectionFactory.", obj));

    _ejbContainer.setJmsConnectionFactory((ConnectionFactory) obj);
  }

  /**
   * Gets the jndi name of the jmsQueueConnectionFactory
   */
  public ConnectionFactory getConnectionFactory()
  {
    return _jmsConnectionFactory;
  }

  /**
   * Sets consumer max
   */
  public void setMessageConsumerMax(int consumerMax)
    throws ConfigException, NamingException
  {
    _ejbContainer.setMessageConsumerMax(consumerMax);
  }

  /**
   * Gets transaction timeout.
   */
  public long getTransactionTimeout()
  {
    return _transactionTimeout;
  }

  /**
   * Sets the transaction timeout.
   */
  public void setTransactionTimeout(Period timeout)
  {
    _transactionTimeout = timeout.getPeriod();
  }

  /**
   * Gets the Resin isolation.
   */
  public String getResinIsolation()
  {
    return _resinIsolation;
  }

  /**
   * Sets the Resin isolation.
   */
  public void setResinIsolation(String resinIsolation)
  {
    _resinIsolation = resinIsolation;
  }

  /**
   * Gets the JDBC isolation.
   */
  public String getJdbcIsolation()
  {
    return _jdbcIsolation;
  }

  /**
   * Sets the JDBC isolation.
   */
  public void setJdbcIsolation(String jdbcIsolation)
  {
    _jdbcIsolation = jdbcIsolation;
  }

  /**
   * If true, JVM calls are forbidden.
   */
  public void setForbidJvmCall(boolean forbid)
  {
    _forbidJVMCall = forbid;
  }

  /**
   * If true, automatically compile old EJBs.
   */
  public boolean isAutoCompile()
  {
    return _autoCompile;
  }

  /**
   * Set true to automatically compile old EJBs.
   */
  public void setAutoCompile(boolean autoCompile)
  {
    _autoCompile = autoCompile;
  }

  /**
   * If true, allow POJO beans
   */
  public boolean isAllowPOJO()
  {
    return _isAllowPOJO;
  }

  /**
   * Set true to allow POJO beans
   */
  public void setAllowPOJO(boolean allowPOJO)
  {
    _isAllowPOJO = allowPOJO;
  }

  /**
   * Sets the EJB server startup mode.
   */
  public void setStartupMode(String startupMode)
  {
    _startupMode = startupMode;
  }

  public static EJBServer getLocal()
  {
    return _localServer.get();
  }
  
  private boolean isScanPersistenceXml()
  {
    return _isScanPersistenceXml;
  }
  
  public void setScanPersistenceXml(boolean isScan)
  {
    _isScanPersistenceXml = isScan;
  }

  /**
   * Initialize the container.
   */
  @PostConstruct
  public void init()
  {
    try {
      Environment.addChildLoaderListener(new ListenerPersistenceEnvironment());
    
      // _ejbContainer.start();
      
      PersistenceManager persistenceManager = null;
      
      if (isScanPersistenceXml()) {
        persistenceManager = PersistenceManager.create();
      }
      
      if (persistenceManager != null)
        persistenceManager.addPersistenceUnitDefault(_jpaProgram);

      if ("manual".equals(_startupMode))
        return;

      manualInit();
    } catch (Exception e) {
      throw ConfigException.create(e);
    }
  }

  /**
   * Initialize the container.
   */
  public void manualInit()
    throws Exception
  {
    try {
      log.fine("Initializing ejb-server : local-jndi="
               + _ejbContainer.getProtocolManager().getLocalJndiPrefix()
               + " remote-jndi="
               + _ejbContainer.getProtocolManager().getRemoteJndiPrefix());

      Environment.addChildLoaderListener(new ListenerPersistenceEnvironment());
      Environment.addChildLoaderListener(new EjbEnvironmentListener());
    } catch (Exception e) {
      log.log(Level.WARNING, e.toString(), e);

      throw e;
    }
  }

  /**
   * Initialize all EJBs for any *.ejb or ejb-jar.xml in the WEB-INF or
   * in a META-INF in the classpath.
   */
  public void initEJBs()
    throws Exception
  {
    manualInit();
  }
}
