/*
 * 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.amber.manager;

import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.spi.PersistenceProvider;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.sql.DataSource;

import com.caucho.amber.AmberRuntimeException;
import com.caucho.amber.cfg.EntityMappingsConfig;
import com.caucho.amber.cfg.MappedSuperclassConfig;
import com.caucho.amber.gen.AmberEnhancer;
import com.caucho.amber.gen.AmberGenerator;
import com.caucho.amber.type.EmbeddableType;
import com.caucho.amber.type.EntityType;
import com.caucho.amber.type.ListenerType;
import com.caucho.amber.type.MappedSuperclassType;
import com.caucho.bytecode.JClassWrapper;
import com.caucho.config.Config;
import com.caucho.config.ConfigException;
import com.caucho.config.LineConfigException;
import com.caucho.config.Names;
import com.caucho.config.inject.BeanBuilder;
import com.caucho.config.inject.InjectManager;
import com.caucho.config.inject.CurrentLiteral;
import com.caucho.config.program.ConfigProgram;
import com.caucho.env.jpa.EntityManagerFactoryProxy;
import com.caucho.env.jpa.EntityManagerJtaProxy;
import com.caucho.env.jpa.ConfigPersistence;
import com.caucho.env.jpa.ConfigPersistenceUnit;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.EnvironmentListener;
import com.caucho.loader.EnvironmentLocal;
import com.caucho.loader.enhancer.EnhancerManager;
import com.caucho.loader.enhancer.ScanClass;
import com.caucho.loader.enhancer.ScanClassAllow;
import com.caucho.loader.enhancer.ScanListener;
import com.caucho.loader.enhancer.ScanMatch;
import com.caucho.util.*;
import com.caucho.vfs.*;

/**
 * Environment-based container.
 */
public class AmberContainer implements ScanListener, EnvironmentListener {
  private static final L10N L = new L10N(AmberContainer.class);
  private static final Logger log = Logger.getLogger(AmberContainer.class
      .getName());

  private static final EnvironmentLocal<AmberContainer> _localContainer
    = new EnvironmentLocal<AmberContainer>();

  private EnvironmentClassLoader _parentLoader;
  private ClassLoader _tempLoader;
  // private EnhancingClassLoader _enhancedLoader;
  private AmberContainer _parentAmberContainer;

  private AmberEnhancer _enhancer;

  private DataSource _dataSource;
  private DataSource _readDataSource;
  private DataSource _xaDataSource;

  private boolean _createDatabaseTables;

  private ArrayList<ConfigProgram> _unitDefaultList
    = new ArrayList<ConfigProgram>();

  private HashMap<String, ArrayList<ConfigProgram>> _unitDefaultMap
    = new HashMap<String, ArrayList<ConfigProgram>>();

  private ArrayList<ConfigPersistenceUnit> _unitConfigList
    = new ArrayList<ConfigPersistenceUnit>();

  private HashMap<String, AmberPersistenceUnit> _unitMap
    = new HashMap<String, AmberPersistenceUnit>();

  private HashMap<String, EntityManagerFactory> _factoryMap
    = new HashMap<String, EntityManagerFactory>();

  private HashMap<String, EntityManager> _persistenceContextMap
    = new HashMap<String, EntityManager>();

  private HashMap<String, EmbeddableType> _embeddableMap
    = new HashMap<String, EmbeddableType>();

  private HashMap<String, EntityType> _entityMap
    = new HashMap<String, EntityType>();

  private HashMap<String, MappedSuperclassType> _mappedSuperclassMap
    = new HashMap<String, MappedSuperclassType>();

  private HashMap<String, ListenerType> _defaultListenerMap
    = new HashMap<String, ListenerType>();

  private HashMap<String, ArrayList<ListenerType>> _entityListenerMap
    = new HashMap<String, ArrayList<ListenerType>>();

  private Throwable _exception;

  private HashMap<String, Throwable> _embeddableExceptionMap
    = new HashMap<String, Throwable>();

  private HashMap<String, Throwable> _entityExceptionMap
    = new HashMap<String, Throwable>();

  private HashMap<String, Throwable> _listenerExceptionMap
    = new HashMap<String, Throwable>();

  private HashMap<Path, RootContext> _persistenceRootMap
    = new HashMap<Path, RootContext>();

  private ArrayList<RootContext> _pendingRootList
    = new ArrayList<RootContext>();

  private ArrayList<AmberPersistenceUnit> _pendingUnitList
    = new ArrayList<AmberPersistenceUnit>();

  private ArrayList<LazyEntityManagerFactory> _pendingFactoryList
    = new ArrayList<LazyEntityManagerFactory>();

  private HashSet<URL> _persistenceURLSet = new HashSet<URL>();

  private ArrayList<String> _pendingClasses = new ArrayList<String>();

  private AmberContainer(ClassLoader loader)
  {
    _parentAmberContainer = _localContainer.get(loader);
    _parentLoader = Environment.getEnvironmentClassLoader(loader);
    _localContainer.set(this, _parentLoader);

    _tempLoader = _parentLoader.getNewTempClassLoader();

    _enhancer = new AmberEnhancer(this);

    EnhancerManager.create(_parentLoader).addClassEnhancer(_enhancer);

    if (_parentAmberContainer != null)
      copyContainerDefaults(_parentAmberContainer);

    _parentLoader.addScanListener(this);

    Environment.addEnvironmentListener(this, _parentLoader);

    try {
      if (_parentLoader instanceof DynamicClassLoader)
        ((DynamicClassLoader) _parentLoader).make();
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Returns the local container.
   */
  public static AmberContainer create()
  {
    return create(Thread.currentThread().getContextClassLoader());
  }

  /**
   * Returns the local container.
   */
  public static AmberContainer create(ClassLoader loader)
  {
    synchronized (_localContainer) {
      AmberContainer container = _localContainer.getLevel(loader);

      if (container == null) {
        container = new AmberContainer(loader);

        _localContainer.set(container, loader);
      }

      return container;
    }
  }

  /**
   * Returns the local container.
   */
  public static AmberContainer getCurrent()
  {
    return getCurrent(Thread.currentThread().getContextClassLoader());
  }

  /**
   * Returns the current environment container.
   */
  public static AmberContainer getCurrent(ClassLoader loader)
  {
    synchronized (_localContainer) {
      return _localContainer.get(loader);
    }
  }

  /**
   * Sets the primary data source.
   */
  public void setDataSource(DataSource dataSource)
  {
    _dataSource = dataSource;
  }

  /**
   * Gets the primary data source.
   */
  public DataSource getDataSource()
  {
    return _dataSource;
  }

  /**
   * Sets the read data source.
   */
  public void setReadDataSource(DataSource dataSource)
  {
    _readDataSource = dataSource;
  }

  /**
   * Gets the read data source.
   */
  public DataSource getReadDataSource()
  {
    return _readDataSource;
  }

  /**
   * Sets the xa data source.
   */
  public void setXADataSource(DataSource dataSource)
  {
    _xaDataSource = dataSource;
  }

  /**
   * Gets the XA data source.
   */
  public DataSource getXADataSource()
  {
    return _xaDataSource;
  }

  /**
   * True if database tables should be created automatically.
   */
  public boolean getCreateDatabaseTables()
  {
    return _createDatabaseTables;
  }

  /**
   * True if database tables should be created automatically.
   */
  public void setCreateDatabaseTables(boolean isCreate)
  {
    _createDatabaseTables = isCreate;
  }

  /**
   * Returns the parent loader
   */
  public ClassLoader getParentClassLoader()
  {
    return _parentLoader;
  }

  /**
   * Returns the parent loader
   */
  public ClassLoader getEnhancedLoader()
  {
    return _parentLoader;
  }

  /**
   * Returns the enhancer.
   */
  public AmberGenerator getGenerator()
  {
    return _enhancer;
  }

  /**
   * Returns the persistence unit JNDI context.
   */
  public static String getPersistenceUnitJndiPrefix()
  {
    return "java:comp/env/persistence/_amber_PersistenceUnit/";
  }

  /**
   * Adds a persistence-unit default
   */
  public void addPersistenceUnitDefault(ConfigProgram program)
  {
    _unitDefaultList.add(program);
  }

  /**
   * Returns the persistence-unit default list.
   */
  public ArrayList<ConfigProgram> getPersistenceUnitDefaultList()
  {
    return _unitDefaultList;
  }

  /**
   * Adds a persistence-unit default
   */
  public void addPersistenceUnitProxy(String name,
                                      ArrayList<ConfigProgram> program)
  {
    ArrayList<ConfigProgram> oldProgram = _unitDefaultMap.get(name);

    if (oldProgram == null)
      oldProgram = new ArrayList<ConfigProgram>();

    oldProgram.addAll(program);

    _unitDefaultMap.put(name, oldProgram);
  }

  public ArrayList<ConfigProgram> getProxyProgram(String name)
  {
    return _unitDefaultMap.get(name);
  }

  /**
   * Returns the persistence unit JNDI context.
   */
  public static String getPersistenceContextJndiPrefix()
  {
    // return "java:comp/env/persistence/PersistenceContext/";
    return "java:comp/env/persistence/";
  }

  /**
   * Returns the JClassLoader.
   */
  public ClassLoader getTempClassLoader()
  {
    return _tempLoader;
  }

  public Class loadTempClass(String name) throws ClassNotFoundException
  {
    return Class.forName(name, false, getTempClassLoader());
  }

  private void copyContainerDefaults(AmberContainer parent)
  {
    _dataSource = parent._dataSource;
    _xaDataSource = parent._xaDataSource;
    _readDataSource = parent._readDataSource;
    _createDatabaseTables = parent._createDatabaseTables;
  }

  public void init()
  {
  }

  /**
   * Returns the EmbeddableType for an introspected class.
   */
  public EmbeddableType getEmbeddable(String className)
  {
    Throwable e = _embeddableExceptionMap.get(className);

    if (e != null)
      throw new AmberRuntimeException(e);
    else if (_exception != null) {
      throw new AmberRuntimeException(_exception);
    }

    return _embeddableMap.get(className);
  }

  /**
   * Returns the EntityType for an introspected class.
   */
  public EntityType getEntity(String className)
  {
    Throwable e = _entityExceptionMap.get(className);

    if (e != null)
      throw new AmberRuntimeException(e);
    else if (_exception != null) {
      throw new AmberRuntimeException(_exception);
    }

    return _entityMap.get(className);
  }

  /**
   * Returns the MappedSuperclassType for an introspected class.
   */
  public MappedSuperclassType getMappedSuperclass(String className)
  {
    Throwable e = _entityExceptionMap.get(className);

    if (e != null)
      throw new AmberRuntimeException(e);
    else if (_exception != null) {
      throw new AmberRuntimeException(_exception);
    }

    MappedSuperclassType type = _mappedSuperclassMap.get(className);

    return type;
  }

  /**
   * Returns the default ListenerType for an introspected class.
   */
  public ListenerType getDefaultListener(String className)
  {
    if (true)
      return null;

    Throwable e = _listenerExceptionMap.get(className);

    if (e != null)
      throw new AmberRuntimeException(e);
    else if (_exception != null) {
      throw new AmberRuntimeException(_exception);
    }

    return _defaultListenerMap.get(className);
  }

  /**
   * Returns the entity ListenerType for an introspected class.
   */
  public ListenerType getEntityListener(String className)
  {
    if (true)
      return null;

    Throwable e = _listenerExceptionMap.get(className);

    if (e != null)
      throw new AmberRuntimeException(e);
    else if (_exception != null) {
      throw new AmberRuntimeException(_exception);
    }

    ArrayList<ListenerType> listenerList;

    for (Map.Entry<String, ArrayList<ListenerType>> entry : _entityListenerMap
        .entrySet()) {

      listenerList = entry.getValue();

      if (listenerList == null)
        continue;

      for (ListenerType listener : listenerList) {
        if (className.equals(listener.getBeanClass().getName()))
          return listener;
      }
    }

    return null;
  }

  /**
   * Returns the listener for an introspected class.
   */
  public ListenerType getListener(String className)
  {
    if (true)
      return null;

    ListenerType listener = getDefaultListener(className);

    if (listener == null)
      listener = getEntityListener(className);

    return listener;
  }

  /**
   * Returns the entity listeners for an entity.
   */
  public ArrayList<ListenerType> getEntityListeners(String entityClassName)
  {
    return null;

    // return _entityListenerMap.get(entityClassName);
  }

  /**
   * Adds an entity for an introspected class.
   */
  public void addEntityException(String className, Throwable e)
  {
    _entityExceptionMap.put(className, e);
  }

  /**
   * Adds an entity for an introspected class.
   */
  public void addException(Throwable e)
  {
    if (_exception == null) {
      _exception = e;

      Environment.setConfigException(e);
    }
  }

  public Throwable getConfigException()
  {
    return _exception;
  }

  /**
   * Adds an embeddable for an introspected class.
   */
  public void addEmbeddable(String className, EmbeddableType type)
  {
    _embeddableMap.put(className, type);
  }

  /**
   * Adds an entity for an introspected class.
   */
  public void addEntity(String className, EntityType type)
  {
    _entityMap.put(className, type);
  }

  /**
   * Adds a mapped superclass for an introspected class.
   */
  public void addMappedSuperclass(String className, MappedSuperclassType type)
  {
    _mappedSuperclassMap.put(className, type);
  }

  /**
   * Adds a default listener.
   */
  public void addDefaultListener(String className, ListenerType type)
  {
    _defaultListenerMap.put(className, type);
  }

  /**
   * Adds an entity listener.
   */
  public void addEntityListener(String entityClassName,
                                ListenerType listenerType)
  {
    ArrayList<ListenerType> listenerList
      = _entityListenerMap.get(entityClassName);

    if (listenerList == null) {
      listenerList = new ArrayList<ListenerType>();
      _entityListenerMap.put(entityClassName, listenerList);
    }

    listenerList.add(listenerType);
  }

  /**
   * Initialize the entity homes.
   */
  public void initEntityHomes()
  {
    throw new UnsupportedOperationException();
  }

  public EntityManagerFactory createEntityManagerFactory(PersistenceUnitInfo info)
  {
    Path path = Vfs.lookup(info.getPersistenceUnitRootUrl());

    addPersistenceUnit(path);

    String name = info.getPersistenceUnitName();

    configurePersistenceRoot(info);

    AmberPersistenceUnit pUnit = createPersistenceUnit(name);

    return getEntityManagerFactory(name);
  }

  public AmberPersistenceUnit createPersistenceUnit(String name)
  {
    AmberPersistenceUnit unit = _unitMap.get(name);
    
    if (unit == null) {
      unit = new AmberPersistenceUnit(this, name);

      _unitMap.put(unit.getName(), unit);
    }

    return unit;
  }

  public void start()
  {
    // configurePersistenceRoots();
    startPersistenceUnits();
  }

  public AmberPersistenceUnit getPersistenceUnit(String name)
  {
    if (_exception != null)
      throw new AmberRuntimeException(_exception);

    return _unitMap.get(name);
  }

  public EntityManagerFactory getEntityManagerFactory(String name)
  {
    if (_exception != null)
      throw new AmberRuntimeException(_exception);

    EntityManagerFactory factory = _factoryMap.get(name);
    if (factory != null)
      return factory;

    /*
    if (_pendingRootList.size() > 0)
      configurePersistenceRoots();
      */

    factory = _factoryMap.get(name);
    if (factory != null)
      return factory;

    AmberPersistenceUnit amberUnit = _unitMap.get(name);
    if (amberUnit != null) {
      factory = new AmberEntityManagerFactory(amberUnit);
      _factoryMap.put(name, factory);
      return factory;
    }

    if ("".equals(name) && _factoryMap.size() == 1)
      return _factoryMap.values().iterator().next();

    if ("".equals(name) && _unitMap.size() == 1) {
      amberUnit = _unitMap.values().iterator().next();

      factory = new AmberEntityManagerFactory(amberUnit);
      _factoryMap.put(name, factory);
      return factory;
    }

    if (_parentAmberContainer != null)
      return _parentAmberContainer.getEntityManagerFactory(name);
    else
      return null;
  }

  public EntityManager getPersistenceContext(String name)
  {
    if (_exception != null)
      throw new AmberRuntimeException(_exception);

    if ("".equals(name) && _unitConfigList.size() > 0)
      name = _unitConfigList.get(0).getName();

    EntityManager context = _persistenceContextMap.get(name);
    if (context != null)
      return context;

    /*
    if (_pendingRootList.size() > 0)
      configurePersistenceRoots();
      */

    if ("".equals(name) && _unitConfigList.size() > 0)
      name = _unitConfigList.get(0).getName();

    context = _persistenceContextMap.get(name);
    if (context != null)
      return context;

    AmberPersistenceUnit amberUnit = _unitMap.get(name);
    if (amberUnit != null) {
      context = new EntityManagerProxy(amberUnit);
      _persistenceContextMap.put(name, context);

      return context;
    }

    return null;
  }

  public EntityManager getExtendedPersistenceContext(String name)
  {
    if (_exception != null)
      throw new AmberRuntimeException(_exception);

    if ("".equals(name) && _unitConfigList.size() > 0)
      name = _unitConfigList.get(0).getName();

    /*
    if (_pendingRootList.size() > 0)
      configurePersistenceRoots();
      */

    if ("".equals(name) && _unitConfigList.size() > 0)
      name = _unitConfigList.get(0).getName();

    AmberPersistenceUnit amberUnit = _unitMap.get(name);
    if (amberUnit != null) {
      return new EntityManagerExtendedProxy(amberUnit);
    }

    return null;
  }

  /**
   * Adds a persistence root.
   */
  public void addPersistenceUnit(Path root)
  {
    if (_persistenceRootMap.get(root) != null)
      return;

    RootContext context = new RootContext(root);
    _persistenceRootMap.put(root, context);
    _pendingRootList.add(context);
  }

  private Class loadProvider(URL url)
  {
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    InputStream is = null;

    try {
      is = url.openStream();

      ReadStream in = Vfs.openRead(is);
      String line;
      while ((line = in.readLine()) != null) {
        line = line.trim();

        if (! "".equals(line) && ! line.startsWith("#")) {
          return Class.forName(line, false, loader);
        }
      }
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);
    } finally {
      IoUtil.close(is);
    }

    return null;
  }

  private void configurePersistenceRoot(PersistenceUnitInfo info)
  {
    URL rootUrl = info.getPersistenceUnitRootUrl();
    String pUnitName = info.getPersistenceUnitName();

    Path root = Vfs.lookup(rootUrl);

    try {
      Path ormXml = root.lookup("META-INF/orm.xml");

      EntityMappingsConfig entityMappings
        = configureMappingFile(root, ormXml);

      // HashMap<String, JClass> classMap = new HashMap<String, JClass>();

      try {
        if (log.isLoggable(Level.CONFIG))
          log.config("Amber PersistenceUnit[" + pUnitName + "] configuring " + rootUrl);
        
        ArrayList<String> classes = new ArrayList<String>();

        if (! info.excludeUnlistedClasses()) {
          /*
          for (String className : rootContext.getClassNameList())
            lookupClass(className, classMap, entityMappings);
            */

          // unitConfig.addAllClasses(classMap);
        }

        ArrayList<EntityMappingsConfig> entityMappingsList
        = new ArrayList<EntityMappingsConfig>();

        if (entityMappings != null)
          entityMappingsList.add(entityMappings);

        /*
        // jpa/0s2n: <jar-file>
        for (String fileName : unitConfig.getJarFiles()) {
          JarPath jarFile;

          Path parent = root;

          if (root instanceof JarPath) {
            parent = ((JarPath) root).getContainer().getParent();
          }

          jarFile = JarPath.create(parent.lookup(fileName));

          classMap.clear();

          unitConfig.addAllClasses(classMap);
        }
        */

        // jpa/0s2l: custom mapping-file.
        /*
        for (String fileName : unitConfig.getMappingFiles()) {
          Path mappingFile = root.lookup(fileName);

          EntityMappingsConfig mappingFileConfig
          = configureMappingFile(root, mappingFile);

          if (mappingFileConfig != null) {
            entityMappingsList.add(mappingFileConfig);

            classMap.clear();

            unitConfig.addAllClasses(classMap);
          }
        }
        */

        AmberPersistenceUnit unit = init(info);

        _pendingUnitList.add(unit);

        _unitMap.put(unit.getName(), unit);
      } catch (Exception e) {
        addException(e);

        log.log(Level.WARNING, e.toString(), e);
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw ConfigException.create(e);
    }
  }

  private AmberPersistenceUnit init(PersistenceUnitInfo info)
    throws Exception
  {
    String name = info.getPersistenceUnitName();

    AmberPersistenceUnit unit
      = new AmberPersistenceUnit(this, name);

    unit.setJPA(true);

    if (info.getJtaDataSource() != null)
      unit.setJtaDataSource(info.getJtaDataSource());

    if (info.getNonJtaDataSource() != null)
      unit.setNonJtaDataSource(info.getNonJtaDataSource());

    // unit.setEntityMappingsList(entityMappings);

    unit.init();
    
    ClassLoader tempLoader = info.getNewTempClassLoader();
    
    for (String className : info.getManagedClassNames()) {
      Class type = Class.forName(className, false, tempLoader);

      unit.addEntityClass(className, type);//JClassWrapper.create(type));
    }

    unit.generate();

    return unit;
  }

  /**
   * Adds the URLs for the classpath.
   */
  public void startPersistenceUnits()
  {
    Thread thread = Thread.currentThread();
    ClassLoader oldLoader = thread.getContextClassLoader();

    try {
      // jpa/1630
      // thread.setContextClassLoader(_tempLoader);
      thread.setContextClassLoader(_parentLoader);

      ArrayList<AmberPersistenceUnit> unitList
        = new ArrayList<AmberPersistenceUnit>(_pendingUnitList);
      _pendingUnitList.clear();

      ArrayList<LazyEntityManagerFactory> lazyEmfList = new ArrayList<LazyEntityManagerFactory>(
          _pendingFactoryList);
      _pendingFactoryList.clear();

      for (LazyEntityManagerFactory lazyEmf : lazyEmfList) {
        lazyEmf.init();
      }

      for (AmberPersistenceUnit unit : unitList) {
        unit.initEntityHomes();
      }
    } finally {
      thread.setContextClassLoader(oldLoader);
    }
  }

  //
  // private

  //
  // Configures the default orm.xml or mapping files specified with
  // mapping-file tags within a persistence-unit.
  //
  private EntityMappingsConfig configureMappingFile(Path root, Path xmlFile)
      throws Exception
  {
    EntityMappingsConfig entityMappings = null;

    if (xmlFile.exists()) {
      InputStream is = xmlFile.openRead();

      entityMappings = new EntityMappingsConfig();
      entityMappings.setRoot(root);

      new Config().configure(entityMappings, is,
          "com/caucho/amber/cfg/mapping-30.rnc");
    }

    return entityMappings;
  }

  private void lookupClass(String className, HashMap<String, Class> classMap,
                           EntityMappingsConfig entityMappings) throws Exception
  {
    Class type = loadTempClass(className);

    if (type != null) {
      boolean isEntity = type.getAnnotation(javax.persistence.Entity.class) != null;
      boolean isEmbeddable
        = type.getAnnotation(javax.persistence.Embeddable.class) != null;
      boolean isMappedSuperclass
        = type.getAnnotation(javax.persistence.MappedSuperclass.class) != null;

      MappedSuperclassConfig mappedSuperclassOrEntityConfig = null;

      if (entityMappings != null) {
        mappedSuperclassOrEntityConfig =
          entityMappings.getEntityConfig(className);

        if (mappedSuperclassOrEntityConfig == null)
          mappedSuperclassOrEntityConfig
            = entityMappings.getMappedSuperclass(className);
      }

      if (isEntity || isEmbeddable || isMappedSuperclass
          || (mappedSuperclassOrEntityConfig != null)) {
        classMap.put(className, type);
      }
    }
  }

  //
  // ScanListener
  //

  /**
   * Since Amber enhances it's priority 0
   */
  public int getScanPriority()
  {
    return 0;
  }

  /**
   * Returns true if the root is a valid scannable root.
   */
  @Override
  public boolean isRootScannable(Path root, String packageRoot)
  {
    if (! root.lookup("META-INF/persistence.xml").canRead())
      return false;

    RootContext context = _persistenceRootMap.get(root);

    if (context == null) {
      context = new RootContext(root);
      _pendingRootList.add(context);
      _persistenceRootMap.put(root, context);
    }

    if (context.isScanComplete())
      return false;
    else {
      if (log.isLoggable(Level.FINER))
        log.finer(this + " scanning " + root);

      context.setScanComplete(true);

      return true;
    }
  }

  @Override
  public ScanClass scanClass(Path root, String packageRoot,
                             String className, int modifiers)
  {
    if (Modifier.isInterface(modifiers))
      return null;
    else if (Modifier.isAbstract(modifiers))
      return null;
    else if (Modifier.isFinal(modifiers))
      return null;
    else if (!Modifier.isPublic(modifiers))
      return null;
    else
      return ScanClassAllow.ALLOW;
  }

  @Override
  public boolean isScanMatchAnnotation(CharBuffer annotationName)
  {
    if (annotationName.matches("javax.persistence.Entity"))
      return true;
    else if (annotationName.matches("javax.persistence.Embeddable"))
      return true;
    else if (annotationName.matches("javax.persistence.MappedSuperclass"))
      return true;
    else
      return false;
  }

  /**
   * Callback to note the class matches
   */
  public void classMatchEvent(EnvironmentClassLoader loader, Path root,
      String className)
  {
    RootContext context = _persistenceRootMap.get(root);

    if (context == null) {
      context = new RootContext(root);
      _persistenceRootMap.put(root, context);
      _pendingRootList.add(context);
    }

    context.addClassName(className);
  }

  //
  // EnvironmentListener
  //

  /**
   * Handles the environment config phase
   */
  public void environmentConfigure(EnvironmentClassLoader loader)
  {
    // configurePersistenceRoots();
  }

  /**
   * Handles the environment config phase
   */
  public void environmentBind(EnvironmentClassLoader loader)
  {
    // configurePersistenceRoots();
  }

  /**
   * Handles the case where the environment is starting (after init).
   */
  public void environmentStart(EnvironmentClassLoader loader)
  {
    start();
  }

  /**
   * Handles the case where the environment is stopping
   */
  public void environmentStop(EnvironmentClassLoader loader)
  {
  }

  public String toString()
  {
    return "AmberContainer[" + _parentLoader.getId() + "]";
  }

  class LazyEntityManagerFactory {
    private final ConfigPersistenceUnit _unit;
    private final PersistenceProvider _provider;
    private final Map _props;

    LazyEntityManagerFactory(ConfigPersistenceUnit unit,
                             PersistenceProvider provider, Map props)
    {
      _unit = unit;
      _provider = provider;
      _props = props;
    }

    void init()
    {
      /*
      synchronized (AmberContainer.this) {
        String unitName = _unit.getName();

        EntityManagerFactory factory = _factoryMap.get(unitName);

        if (factory == null) {
          factory = _provider.createContainerEntityManagerFactory(_unit, _props);

          if (factory == null)
            throw new ConfigException(L.l(
                "'{0}' must return an EntityManagerFactory",
                _provider.getClass().getName()));

          if (log.isLoggable(Level.FINE)) {
            log.fine(L.l("Amber creating persistence unit '{0}' created with provider '{1}'",
                         unitName, _provider.getClass().getName()));
          }

          _factoryMap.put(unitName, factory);
        }
      }
      */
    }
  }

}
