/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.container;

import org.exoplatform.commons.utils.ClassLoading;
import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.component.ComponentLifecyclePlugin;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.context.ContextManager;
import org.exoplatform.container.security.ContainerPermissions;
import org.exoplatform.container.spi.ComponentAdapter;
import org.exoplatform.container.spi.Container;
import org.exoplatform.container.spi.ContainerException;
import org.exoplatform.container.spi.DefinitionByName;
import org.exoplatform.container.spi.DefinitionByQualifier;
import org.exoplatform.container.spi.DefinitionByType;
import org.exoplatform.container.spi.InterceptorChainFactoryProvider;
import org.exoplatform.container.util.ContainerUtil;
import org.exoplatform.container.xml.Configuration;
import org.exoplatform.container.xml.ExternalComponentPlugins;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.management.annotations.Managed;
import org.exoplatform.management.annotations.ManagedDescription;
import org.exoplatform.management.annotations.ManagedName;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.inject.Qualifier;

/**
 * Created by The eXo Platform SAS<br>
 * The Exo Container is an object responsible for loading services/components.
 * The eXoContainer class is inherited by all the containers, including RootContainer, PortalContainer,
 * and StandaloneContainer. It itself inherits from a {@link Container} which allows eXo to apply
 * the Inversion of Control (also known as IoC) principles.
 *
 * @author <a href="mailto:tuan08@users.sourceforge.net">Tuan Nguyen</a>
 * @LevelAPI Provisional
 */
public class ExoContainer extends AbstractContainer
{

   /**
    * Serial Version UID
    */
   private static final long serialVersionUID = -8068506531004854036L;

   /**
    * The current list of profiles
    */
   private static volatile String PROFILES;

   /**
    * The current set of profiles
    */
   private static Set<String> SET_PROFILES = Collections.unmodifiableSet(new HashSet<String>());

   protected final AtomicBoolean stopping = new AtomicBoolean();

   private final AtomicBoolean started = new AtomicBoolean();

   private final AtomicBoolean disposed = new AtomicBoolean();

   private final AtomicBoolean initialized = new AtomicBoolean();

   private final AtomicBoolean ctxManagerLoaded = new AtomicBoolean();

   private ContextManager ctxManager;

   /**
    * Returns an unmodifiable set of profiles defined by the value returned by invoking
    * {@link PropertyManager#getProperty(String)} with the {@link org.exoplatform.commons.utils.PropertyManager#RUNTIME_PROFILES}
    * property.
    *
    * @return the set of profiles
    */
   public static Set<String> getProfiles()
   {
      String profiles = PropertyManager.getProperty(PropertyManager.RUNTIME_PROFILES);
      if ((profiles == null && PROFILES != null) || (profiles != null && !profiles.equals(PROFILES)))
      {
         synchronized (ExoContainer.class)
         {
            if ((profiles == null && PROFILES != null) || (profiles != null && !profiles.equals(PROFILES)))
            {
               SET_PROFILES = getProfiles(profiles);
               PROFILES = profiles;
            }
         }
      }
      return SET_PROFILES;
   }

   /**
    * Indicates whether or not a given profile exists
    * @param profileName the name of the profile to check
    * @return <code>true</code> if the profile exists, <code>false</code> otherwise.
    */
   public static boolean hasProfile(String profileName)
   {
      return getProfiles().contains(profileName);
   }

   /**
    * Convert the list of profiles into a Set of String
    */
   private static Set<String> getProfiles(String profileList)
   {
      Set<String> profiles = new HashSet<String>();

      // Obtain profile list by runtime properties
      if (profileList != null)
      {
         for (String profile : profileList.split(","))
         {
            profiles.add(profile.trim());
         }
      }
      return Collections.unmodifiableSet(profiles);
   }

   protected static final Log LOG = ExoLogger.getLogger("exo.kernel.container.ExoContainer");

   private final Map<String, ComponentLifecyclePlugin> componentLifecylePlugin_ =
      new HashMap<String, ComponentLifecyclePlugin>();

   private final List<ContainerLifecyclePlugin> containerLifecyclePlugin_ = new ArrayList<ContainerLifecyclePlugin>();

   protected final ExoContainerContext context;

   protected final ExoContainer parent;

   public ExoContainer()
   {
      this(null);
   }

   public ExoContainer(ExoContainer parent)
   {
      this.context = new ExoContainerContext(this, this.getClass().getSimpleName());
      this.parent = parent;
      this.delegate = InterceptorChainFactoryProvider.getInterceptorChainFactory().getInterceptorChain(this, parent);
      SecurityHelper.doPrivilegedAction(new PrivilegedAction<Void>()
      {
         public Void run()
         {
            registerComponentInstance(context);
            return null;
         }
      });
   }

   public ExoContainerContext getContext()
   {
      return context;
   }

   /**
    * @return the name of the plugin if it is not empty, the FQN of the plugin otherwise
    */
   private static String getPluginName(ContainerLifecyclePlugin plugin)
   {
      String name = plugin.getName();
      if (name == null || name.length() == 0)
      {
         name = plugin.getClass().getName();
      }
      return name;
   }

   protected void initContainerInternal()
   {
      ConfigurationManager manager = getComponentInstanceOfType(ConfigurationManager.class);
      ContainerUtil.addContainerLifecyclePlugin(this, manager);
      ContainerUtil.addComponentLifecyclePlugin(this, manager);
      ContainerUtil.addComponents(this, manager);
      for (ContainerLifecyclePlugin plugin : containerLifecyclePlugin_)
      {
         try
         {
            plugin.initContainer(this);
         }
         catch (Exception e)
         {
            LOG.warn("An error occurs with the ContainerLifecyclePlugin '" + getPluginName(plugin) + "'", e);
         }
      }
      Collection<ExternalComponentPlugins> unusedPlugins = getExternalComponentPluginsUnused();
      if (unusedPlugins != null && PropertyManager.isDevelopping())
      {
         for (ExternalComponentPlugins plugins : unusedPlugins)
         {
            LOG.warn("Some external plugins has for target '{}' which is unknown, please configure it or "
               + "define the annotation 'DefinitionByType' to enable the auto-registration on the target",
               plugins.getTargetComponent());
         }
      }
   }

   public synchronized void dispose()
   {
      SecurityManager security = System.getSecurityManager();
      if (security != null)
         security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION);

      if (canBeDisposed())
      {
         destroyContainerInternal();
         super.dispose();
         disposed.set(true);
      }
   }

   /**
    * Starts the container
    * @param init indicates if the container must be initialized first
    */
   public synchronized void start(boolean init)
   {
      if (init)
      {
         initialize();
      }
      start();
   }

   public synchronized void initialize()
   {
      SecurityManager security = System.getSecurityManager();
      if (security != null)
         security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION);

      if (canBeInitialized())
      {
         // Initialize the successors
         super.initialize();
         // Initialize the container
         initContainerInternal();
         initialized.set(true);
      }
   }

   public synchronized void start()
   {
      SecurityManager security = System.getSecurityManager();
      if (security != null)
         security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION);

      if (canBeStarted())
      {
         super.start();
         startContainerInternal();
         started.set(true);
      }
   }

   public synchronized void stop()
   {
      SecurityManager security = System.getSecurityManager();
      if (security != null)
         security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION);

      if (canBeStopped())
      {
         stopping.set(true);
         stopContainerInternal();
         super.stop();
         started.set(false);
      }
   }

   private void startContainerInternal()
   {
      for (ContainerLifecyclePlugin plugin : containerLifecyclePlugin_)
      {
         try
         {
            plugin.startContainer(this);
         }
         catch (Exception e)
         {
            LOG.warn("An error occurs with the ContainerLifecyclePlugin '" + getPluginName(plugin) + "'", e);
         }
      }
   }

   private void stopContainerInternal()
   {
      for (ContainerLifecyclePlugin plugin : containerLifecyclePlugin_)
      {
         try
         {
            plugin.stopContainer(this);
         }
         catch (Exception e)
         {
            LOG.warn("An error occurs with the ContainerLifecyclePlugin '" + getPluginName(plugin) + "'", e);
         }
      }
   }

   private void destroyContainerInternal()
   {
      for (ContainerLifecyclePlugin plugin : containerLifecyclePlugin_)
      {
         try
         {
            plugin.destroyContainer(this);
         }
         catch (Exception e)
         {
            LOG.warn("An error occurs with the ContainerLifecyclePlugin '" + getPluginName(plugin) + "'", e);
         }
      }
   }

   public void addComponentLifecylePlugin(ComponentLifecyclePlugin plugin)
   {
      SecurityManager security = System.getSecurityManager();
      if (security != null)
         security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION);

      List<String> list = plugin.getManageableComponents();
      for (String component : list)
         componentLifecylePlugin_.put(component, plugin);
   }

   public void addContainerLifecylePlugin(ContainerLifecyclePlugin plugin)
   {
      SecurityManager security = System.getSecurityManager();
      if (security != null)
         security.checkPermission(ContainerPermissions.MANAGE_CONTAINER_PERMISSION);

      containerLifecyclePlugin_.add(plugin);
   }

   /**
    * Gets the {@link ConfigurationManager} from the given {@link ExoContainer} if it exists, 
    * then returns the nested {@link Configuration} otherwise it returns <code>null</code>
    */
   protected Configuration getConfiguration()
   {
      ConfigurationManager cm = getComponentInstanceOfType(ConfigurationManager.class);
      return cm == null ? null : cm.getConfiguration();
   }

   /**
    * Gives all the {@link ExternalComponentPlugins} that have not been used, <code>null</code>
    * if there are all used.
    */
   protected Collection<ExternalComponentPlugins> getExternalComponentPluginsUnused()
   {
      Configuration configuration = getConfiguration();
      if (configuration == null)
         return null;
      Collection<ExternalComponentPlugins> result = null;
      for (Iterator<ExternalComponentPlugins> it = configuration.getExternalComponentPluginsIterator(); it.hasNext();)
      {
         ExternalComponentPlugins plugins = it.next();
         boolean toAdd = false;
         String target = plugins.getTargetComponent();
         if (target == null)
            toAdd = true;
         else if (configuration.getComponent(target) == null)
         {
            try
            {
               Class<?> c = ClassLoading.loadClass(target, ExoContainer.class);
               if (c.isAnnotation())
               {
                  // We assume that the target is a qualifier so we cannot know if it is normal
                  // or not as it could be auto-registered and we don't know the bind type
               }
               else if (getComponentAdapterOfType(c) == null)
               {
                  // There is no ComponentAdapter corresponding to this
                  // particular class even the auto-registration could
                  // not allow to find a candidate so we can consider it
                  // as unused
                  toAdd = true;
               }
            }
            catch (ClassNotFoundException e)
            {
               if (LOG.isTraceEnabled())
                  LOG.trace("The class {} could not be found", target);
               // We assume that the target is meant to be used with
               // the annotation Named so we cannot know if it is normal
               // or not as it could be auto-registered and we don't know 
               // the bind type
            }
         }
         if (toAdd)
         {
            if (result == null)
            {
               result = new ArrayList<ExternalComponentPlugins>();
            }
            result.add(plugins);
         }
      }
      return result;
   }

   /**
    * Unregister all components from container to avoid keeping instances in memory.
    */
   protected void unregisterAllComponents()
   {
      Collection<ComponentAdapter<?>> adapters = getComponentAdapters();
      for (ComponentAdapter<?> adapter : adapters)
      {
         unregisterComponent(adapter.getComponentKey());
      }
   }

   /**
    * Register a component using the componentImplementation as key. Calling this method is equivalent to calling
    * <code>registerComponentImplementation(componentImplementation, componentImplementation)</code>.
    *
    * @param componentImplementation the concrete component class.
    * @return the ComponentAdapter that has been associated with this component. In the majority of cases, this return
    *         value can be safely ignored, as one of the <code>getXXX()</code> methods of the
    *         {@link Container} interface can be used to retrieve a reference to the component later on.
    * @throws ContainerException if registration fails.
    */
   public <T> ComponentAdapter<T> registerComponentImplementation(Class<T> componentImplementation)
      throws ContainerException
   {
      return registerComponentImplementation(componentImplementation, componentImplementation);
   }

   /**
    * Register an arbitrary object. The class of the object will be used as a key. Calling this method is equivalent to
    * calling     * <code>registerComponentImplementation(componentImplementation, componentImplementation)</code>.
    *
    * @param componentInstance the instance of the component to register
    * @return the ComponentAdapter that has been associated with this component. In the majority of cases, this return
    *         value can be safely ignored, as one of the <code>getXXX()</code> methods of the
    *         {@link Container} interface can be used to retrieve a reference to the component later on.
    * @throws ContainerException if registration fails.
    */
   public <T> ComponentAdapter<T> registerComponentInstance(T componentInstance) throws ContainerException
   {
      return registerComponentInstance(componentInstance.getClass(), componentInstance);
   }

   /**
    * Creates a component corresponding to the given {@link Class} with no parameters
    * This is equivalent to call {@link #createComponent(Class, InitParams)} with
    * <code>null</code> as {@link InitParams}
    * @param clazz the Class of the object to create
    * @return an instance of the component
    * @throws Exception if any issue occurs while creating the component.
    */
   public <T> T createComponent(Class<T> clazz) throws Exception
   {
      return createComponent(clazz, null);
   }

   /**
    * Find a component adapter associated with the specified key. If a component adapter cannot be found in this
    * container, the parent container (if one exists) will be searched.
    * 
    * @param componentKey the key that the component was registered with.
    * @return the component adapter associated with this key, or <code>null</code> if no component has been registered
    *         for the specified key.
    */
   public ComponentAdapter<?> getComponentAdapter(Object componentKey)
   {
      return getComponentAdapter(componentKey, Object.class);
   }

   /**
    * Retrieve a component instance registered with a specific key. If a component cannot be found in this container,
    * the parent container (if one exists) will be searched.
    * 
    * @param componentKey the key that the component was registered with.
    * @return an instantiated component, or <code>null</code> if no component has been registered for the specified
    *         key.
    */
   public Object getComponentInstance(Object componentKey)
   {
      return getComponentInstance(componentKey, Object.class);
   }

   @Managed
   @ManagedName("RegisteredComponentNames")
   @ManagedDescription("Return the list of the registered component names")
   public Set<String> getRegisteredComponentNames() throws ContainerException
   {
      Set<String> names = new HashSet<String>();
      Collection<ComponentAdapter<?>> adapters = getComponentAdapters();
      for (ComponentAdapter<?> adapter : adapters)
      {
         Object key = adapter.getComponentKey();
         String name = String.valueOf(key);
         names.add(name);
      }
      return names;
   }

   /**
    * Gives the parent container of this container.
    * 
    * @return a {@link ExoContainer} instance, or <code>null</code> if this container does not have a parent.
    */
   public ExoContainer getParent()
   {
      return parent;
   }

   /**
    * Indicates whether or not the container can be started
    * @return <code>true</code> if it can be started, <code>false</code> otherwise.
    */
   public boolean canBeStarted()
   {
      return !disposed.get() && !started.get();
   }

   /**
    * Indicates whether or not the container can be stopped
    * @return <code>true</code> if it can be stopped, <code>false</code> otherwise.
    */
   public boolean canBeStopped()
   {
      return !disposed.get() && started.get();
   }

   /**
    * Indicates whether or not the container can be disposed
    * @return <code>true</code> if it can be disposed, <code>false</code> otherwise.
    */
   public boolean canBeDisposed()
   {
      return !disposed.get();
   }

   /**
    * Indicates whether or not the container can be initialized
    * @return <code>true</code> if it can be initialized, <code>false</code> otherwise.
    */
   protected boolean canBeInitialized()
   {
      return !initialized.get();
   }

   /**
    * Gives the {@link ContextManager} that has been registered
    * @return the {@link ContextManager} related to this container, <code>null</code> otherwise.
    */
   public ContextManager getContextManager()
   {
      if (ctxManagerLoaded.get())
         return ctxManager;
      synchronized (this)
      {
         if (ctxManagerLoaded.get())
            return ctxManager;
         ctxManager = getComponentInstanceOfType(ContextManager.class, false);
         ctxManagerLoaded.set(true);
      }
      return ctxManager;
   }

   /**
    * Indicates whether or not the {@link ContextManager} has already been loaded
    * @return <code>true</code> if the {@link ContextManager} has been loaded,
    * <code>false</code> otherwise.
    */
   public boolean isContextManagerLoaded()
   {
      return ctxManagerLoaded.get();
   }

   /**
    * This method is equivalent to {@link #getComponentInstanceOfType(Class, boolean)} with <code>true</code> for the parameter
    * <code>autoRegistration</code>
    */
   public <T> T getComponentInstanceOfType(Class<T> componentType)
   {
      return getComponentInstanceOfType(componentType, true);
   }

   /**
    * Find a component instance matching the specified type. If none can be
    * found it will try to auto register the component according to the
    * content of the annotation {@link org.exoplatform.container.spi.DefinitionByType} if the parameter
    * <code>autoRegistration</code> has been set to <code>true</code>.
    *
    * @param componentType the type of the component.
    * @param autoRegistration indicates whether the auto registration should be performed or not
    * @return the adapter matching the class.
    */
   public <T> T getComponentInstanceOfType(Class<T> componentType, boolean autoRegistration)
   {
      T result = super.getComponentInstanceOfType(componentType, autoRegistration);
      if (result != null || !autoRegistration)
      {
         return result;
      }
      if (componentType.isAnnotationPresent(DefinitionByType.class) && autoRegister(DefinitionType.TYPE, null, componentType))
      {
         return super.getComponentInstanceOfType(componentType, false);
      }
      return result;
   }

   /**
    * This method is equivalent to {@link #getComponentAdapterOfType(Class, boolean)} with <code>true</code> for the parameter
    * <code>autoRegistration</code>
    *
    * @param componentType the type of the component.
    * @return the component adapter associated with this class, or <code>null</code> if no component has been
    *         registered for the specified key.
    */
   public <T> ComponentAdapter<T> getComponentAdapterOfType(Class<T> componentType)
   {
      return getComponentAdapterOfType(componentType, true);
   }

   /**
    * Find a component adapter associated with the specified type. If a component adapter cannot be found in this
    * container, the parent container (if one exists) will be searched. If none can be
    * found it will try to auto register the component according to the
    * content of the annotation {@link org.exoplatform.container.spi.DefinitionByType} if the parameter
    * <code>autoRegistration</code> has been set to <code>true</code>.
    *
    * @param componentType the type of the component.
    * @param autoRegistration indicates whether the auto registration should be performed or not
    * @return the component adapter associated with this class, or <code>null</code> if no component has been
    *         registered for the specified key.
    */
   public <T> ComponentAdapter<T> getComponentAdapterOfType(Class<T> componentType, boolean autoRegistration)
   {
      ComponentAdapter<T> result = super.getComponentAdapterOfType(componentType, autoRegistration);
      if (result != null || !autoRegistration)
      {
         return result;
      }
      if (componentType.isAnnotationPresent(DefinitionByType.class) && autoRegister(DefinitionType.TYPE, null, componentType))
      {
         return super.getComponentAdapterOfType(componentType, false);
      }
      return result;
   }

   /**
    * Tries to auto register the component according to what has been defined in the annotations
    * {@link org.exoplatform.container.spi.DefinitionByType}, {@link org.exoplatform.container.spi.DefinitionByName}
    * or {@link org.exoplatform.container.spi.DefinitionByQualifier} according to the definition type.
    * @param definitionType the type of definition expected
    * @param componentKey the key that the component was registered with or <code>null</code> in case it has been
    *                     registered by type.
    * @param componentType the type of the component that we would like to auto register
    * @return <code>true</code> if the component has been registered, <code>false</code> otherwise.
    */
   private <T> boolean autoRegister(final DefinitionType definitionType, final Object componentKey,
      final Class<T> componentType)
   {
      return SecurityHelper.doPrivilegedAction(new PrivilegedAction<Boolean>()
      {
         public Boolean run()
         {
            Class<?> type;
            Class<? extends ExoContainer>[] containers;
            if (definitionType == DefinitionType.TYPE)
            {
               DefinitionByType definition = componentType.getAnnotation(DefinitionByType.class);
               containers = definition.target();
               type = definition.type();
            }
            else if (definitionType == DefinitionType.NAME)
            {
               DefinitionByName definition = componentType.getAnnotation(DefinitionByName.class);
               if (!definition.named().equals(componentKey))
               {
                  return false;
               }
               containers = definition.target();
               type = definition.type();
            }
            else
            {
               DefinitionByQualifier definition = componentType.getAnnotation(DefinitionByQualifier.class);
               if (!definition.qualifier().equals(componentKey))
               {
                  return false;
               }
               containers = definition.target();
               type = definition.type();
            }
            if (!accepts(containers))
            {
               // The class of the current container is not part of the allowed classes.
               return false;
            }
            if (type.equals(void.class))
            {
               // No default implementation has been set
               if (componentType.isInterface() || Modifier.isAbstract(componentType.getModifiers()))
               {
                  throw new IllegalArgumentException("The class " + componentType.getName()
                     + " is an interface or an abstract class so it cannot be automatically registered without a type.");
               }
               if (definitionType == DefinitionType.TYPE)
               {
                  registerComponentImplementation(componentType);
               }
               else
               {
                  registerComponentImplementation(componentKey, componentType);
               }
            }
            else if (!componentType.isAssignableFrom(type))
            {
               throw new IllegalArgumentException("The class " + type.getName() + " must be a sub class of "
                  + componentType.getName() + ".");
            }
            else if (type.isInterface() || Modifier.isAbstract(type.getModifiers()))
            {
               throw new IllegalArgumentException("The class " + type.getName()
                  + " is an interface or an abstract class so it cannot be used as default implementation.");
            }
            else if (definitionType == DefinitionType.TYPE)
            {
               registerComponentImplementation(componentType, type);
            }
            else
            {
               registerComponentImplementation(componentKey, type);
            }
            return true;
         }
      });
   }

   /**
    * Indicates whether or not the class of the current container is part of the allowed classes.
    * @param containers the class of ExoContainer allowed by the definition
    * @return <code>true</code> if the class of the current container is part of the allowed classes. <code>false</code>
    * otherwise
    */
   private boolean accepts(Class<? extends ExoContainer>[] containers)
   {
      for (Class<? extends ExoContainer> clazz : containers)
      {
         if (getClass().equals(clazz))
         {
            return true;
         }
      }
      return false;
   }

   /**
    * This method is equivalent to {@link #getComponentInstance(Object, Class, boolean)} with <code>true</code> for the parameter
    * <code>autoRegistration</code>
    *
    * @param componentKey the key that the component was registered with.
    * @param bindType the expected type of the instance if one can be found.
    * @return an instantiated component, or <code>null</code> if no component has been registered for the specified
    *         key.
    */
   public <T> T getComponentInstance(Object componentKey, Class<T> bindType)
   {
      return getComponentInstance(componentKey, bindType, true);
   }

   /**
    * Retrieve a component instance registered with a specific key. If a component cannot be found in this container,
    * the parent container (if one exists) will be searched. If none can be found and the parameter
    * <code>autoRegistration</code> has been set to <code>true</code>, it will try to auto register the
    * component according to the content of:
    * <ul>
    *    <li>The annotation {@link org.exoplatform.container.spi.DefinitionByType} if the <code>componentKey</code> is a
    *    class</li>
    *    <li>The annotation {@link org.exoplatform.container.spi.DefinitionByName} if the <code>componentKey</code> is a
    *    string</li>
    *    <li>The annotation {@link org.exoplatform.container.spi.DefinitionByQualifier} if the <code>componentKey</code>
    *    is a qualifier</li>
    * </ul>
    *
    * @param componentKey the key that the component was registered with.
    * @param bindType the expected type of the instance if one can be found.
    * @param autoRegistration indicates whether the auto registration should be performed or not
    * @return an instantiated component, or <code>null</code> if no component has been registered for the specified
    *         key.
    */
   public <T> T getComponentInstance(Object componentKey, Class<T> bindType, boolean autoRegistration)
   {
      T result = super.getComponentInstance(componentKey, bindType, autoRegistration);
      if (result != null || !autoRegistration || Object.class.equals(bindType))
      {
         return result;
      }
      if (componentKey instanceof Class<?> && !((Class<?>)componentKey).isAnnotation())
      {
         @SuppressWarnings("unchecked")
         Class<T> componentType = (Class<T>)componentKey;
         if (componentType.isAnnotationPresent(DefinitionByType.class) && autoRegister(DefinitionType.TYPE, null, componentType))
         {
            return super.getComponentInstanceOfType(componentType, false);
         }
      }
      else if (componentKey instanceof String)
      {
         if (bindType.isAnnotationPresent(DefinitionByName.class) && autoRegister(DefinitionType.NAME, componentKey, bindType))
         {
            return super.getComponentInstance(componentKey, bindType, false);
         }
      }
      else if (componentKey instanceof Class<?>)
      {
         @SuppressWarnings("unchecked")
         final Class<? extends Annotation> annotationType = (Class<? extends Annotation>)componentKey;
         if (annotationType.isAnnotationPresent(Qualifier.class) && bindType.isAnnotationPresent(DefinitionByQualifier.class)
             && autoRegister(DefinitionType.QUALIFIER, componentKey, bindType))
         {
            return super.getComponentInstance(componentKey, bindType, false);
         }
      }
      return result;
   }

   /**
    * This method is equivalent to {@link #getComponentAdapter(Object, Class, boolean)} with <code>true</code> for the parameter
    * <code>autoRegistration</code>
    *
    * @param componentKey the key that the component was registered with.
    * @param bindType the expected raw type of the adapter if one can be found.
    * @return the component adapter associated with this key, or <code>null</code> if no component has been registered
    *         for the specified key.
    */
   public <T> ComponentAdapter<T> getComponentAdapter(Object componentKey, Class<T> bindType)
   {
      return getComponentAdapter(componentKey, bindType, true);
   }

   /**
    * Find a component adapter associated with the specified key. If a component adapter cannot be found in this
    * container, the parent container (if one exists) will be searched. If none can be found and the parameter
    * <code>autoRegistration</code> has been set to <code>true</code>, it will try to auto
    * register the component according to the content of:
    * <ul>
    *    <li>The annotation {@link org.exoplatform.container.spi.DefinitionByType} if the <code>componentKey</code> is a
    *    class</li>
    *    <li>The annotation {@link org.exoplatform.container.spi.DefinitionByName} if the <code>componentKey</code> is a
    *    string</li>
    *    <li>The annotation {@link org.exoplatform.container.spi.DefinitionByQualifier} if the <code>componentKey</code>
    *    is a qualifier</li>
    * </ul>
    *
    * @param componentKey the key that the component was registered with.
    * @param bindType the expected raw type of the adapter if one can be found.
    * @param autoRegistration indicates whether the auto registration should be performed or not
    * @return the component adapter associated with this key, or <code>null</code> if no component has been registered
    *         for the specified key.
    */
   public <T> ComponentAdapter<T> getComponentAdapter(Object componentKey, Class<T> bindType, boolean autoRegistration)
   {
      ComponentAdapter<T> result = super.getComponentAdapter(componentKey, bindType, autoRegistration);
      if (result != null || !autoRegistration || Object.class.equals(bindType))
      {
         return result;
      }
      if (componentKey instanceof Class<?> && !((Class<?>)componentKey).isAnnotation())
      {
         @SuppressWarnings("unchecked")
         Class<T> componentType = (Class<T>)componentKey;
         if (componentType.isAnnotationPresent(DefinitionByType.class) && autoRegister(DefinitionType.TYPE, null, componentType))
         {
            return super.getComponentAdapterOfType(componentType, false);
         }
      }
      else if (componentKey instanceof String)
      {
         if (bindType.isAnnotationPresent(DefinitionByName.class) && autoRegister(DefinitionType.NAME, componentKey, bindType))
         {
            return super.getComponentAdapter(componentKey, bindType, false);
         }
      }
      else if (componentKey instanceof Class<?>)
      {
         @SuppressWarnings("unchecked")
         final Class<? extends Annotation> annotationType = (Class<? extends Annotation>)componentKey;
         if (annotationType.isAnnotationPresent(Qualifier.class) && bindType.isAnnotationPresent(DefinitionByQualifier.class)
            && autoRegister(DefinitionType.QUALIFIER, componentKey, bindType))
         {
            return super.getComponentAdapter(componentKey, bindType, false);
         }
      }
      return result;
   }

   private enum DefinitionType {TYPE, NAME, QUALIFIER}
}
