/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.factories;

import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.factories.annotations.DefaultFactoryFor;
import org.jboss.cache.interceptors.*;
import org.jboss.cache.interceptors.base.CommandInterceptor;

/**
 * Factory class that builds an interceptor chain based on cache configuration.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
@DefaultFactoryFor(classes = InterceptorChain.class)
public class InterceptorChainFactory extends ComponentFactory
{
   /**
    * Note - this method used to return a singleton instance, and since 2.1.0 returns a new instance.  The method is
    * deprecated and you should use the no-arg constructor to create a new instance of this factory.
    *
    * @return a NEW instance of this class.
    */
   @Deprecated
   public static InterceptorChainFactory getInstance()
   {
      return new InterceptorChainFactory();
   }

   private CommandInterceptor createInterceptor(Class<? extends CommandInterceptor> clazz) throws IllegalAccessException, InstantiationException
   {
      CommandInterceptor chainedInterceptor = componentRegistry.getComponent(clazz);
      if (chainedInterceptor == null)
      {
         chainedInterceptor = clazz.newInstance();
         componentRegistry.registerComponent(chainedInterceptor, clazz);
      }
      else
      {
         // wipe next/last chaining!!
         chainedInterceptor.setNext(null);
      }
      chainedInterceptor.setStatisticsEnabled(configuration.getExposeManagementStatistics());
      return chainedInterceptor;
   }

   public InterceptorChain buildInterceptorChain() throws IllegalAccessException, InstantiationException, ClassNotFoundException
   {
      boolean optimistic = configuration.isNodeLockingOptimistic();
      // load the icInterceptor first
      CommandInterceptor first = createInterceptor(InvocationContextInterceptor.class);
      InterceptorChain interceptorChain = new InterceptorChain(first);

      // add the interceptor chain to the registry first, since some interceptors may ask for it.
      componentRegistry.registerComponent(interceptorChain, InterceptorChain.class);

      // load the cache management interceptor next
      if (configuration.getExposeManagementStatistics())
         interceptorChain.appendIntereceptor(createInterceptor(CacheMgmtInterceptor.class));

      // load the tx interceptor
      interceptorChain.appendIntereceptor(createInterceptor(optimistic ? OptimisticTxInterceptor.class : TxInterceptor.class));

      if (configuration.isUseLazyDeserialization())
         interceptorChain.appendIntereceptor(createInterceptor(MarshalledValueInterceptor.class));
      interceptorChain.appendIntereceptor(createInterceptor(NotificationInterceptor.class));

      switch (configuration.getCacheMode())
      {
         case REPL_SYNC:
         case REPL_ASYNC:
            interceptorChain.appendIntereceptor(optimistic ? createInterceptor(OptimisticReplicationInterceptor.class) : createInterceptor(ReplicationInterceptor.class));
            break;
         case INVALIDATION_SYNC:
         case INVALIDATION_ASYNC:
            interceptorChain.appendIntereceptor(createInterceptor(InvalidationInterceptor.class));
            break;
         case LOCAL:
            //Nothing...
      }

      if (!optimistic)
      {
         interceptorChain.appendIntereceptor(createInterceptor(PessimisticLockInterceptor.class));
      }

      if (configuration.isUsingCacheLoaders())
      {
         if (configuration.getCacheLoaderConfig().isPassivation())
         {
            interceptorChain.appendIntereceptor(createInterceptor(ActivationInterceptor.class));
            interceptorChain.appendIntereceptor(createInterceptor(PassivationInterceptor.class));
         }
         else
         {
            interceptorChain.appendIntereceptor(createInterceptor(CacheLoaderInterceptor.class));
            interceptorChain.appendIntereceptor(createInterceptor(CacheStoreInterceptor.class));
         }
      }

      if (configuration.isUsingBuddyReplication())
         interceptorChain.appendIntereceptor(createInterceptor(DataGravitatorInterceptor.class));

      if (optimistic)
      {
         interceptorChain.appendIntereceptor(createInterceptor(OptimisticLockingInterceptor.class));
         interceptorChain.appendIntereceptor(createInterceptor(OptimisticValidatorInterceptor.class));
         interceptorChain.appendIntereceptor(createInterceptor(OptimisticCreateIfNotExistsInterceptor.class));
      }
      // eviction interceptor to come before the optimistic node interceptor
      if (configuration.getEvictionConfig() != null && configuration.getEvictionConfig().isValidConfig())
         interceptorChain.appendIntereceptor(createInterceptor(configuration.isUsingBuddyReplication() ? BuddyRegionAwareEvictionInterceptor.class : EvictionInterceptor.class));

      if (optimistic) interceptorChain.appendIntereceptor(createInterceptor(OptimisticNodeInterceptor.class));
      CommandInterceptor callInterceptor = createInterceptor(CallInterceptor.class);
      interceptorChain.appendIntereceptor(callInterceptor);
      if (log.isTraceEnabled()) log.trace("Finished building interceptor chain.");
      return interceptorChain;
   }

   @Override
   @SuppressWarnings("unchecked")
   protected <T> T construct(Class<T> componentType)
   {
      try
      {
         return (T) buildInterceptorChain();
      }
      catch (Exception e)
      {
         throw new ConfigurationException("Unable to build interceptor chain", e);
      }
   }

   public static InterceptorChainFactory getInstance(ComponentRegistry componentRegistry, Configuration configuration)
   {
      InterceptorChainFactory icf = new InterceptorChainFactory();
      icf.componentRegistry = componentRegistry;
      icf.configuration = configuration;
      return icf;
   }
}
