/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.kernel.plugins.dependency;

import java.util.List;

import org.jboss.beans.metadata.spi.BeanMetaData;
import org.jboss.beans.metadata.spi.InstallMetaData;
import org.jboss.dependency.spi.ControllerContext;
import org.jboss.dependency.spi.ControllerState;
import org.jboss.dependency.spi.dispatch.InvokeDispatchContext;
import org.jboss.kernel.Kernel;
import org.jboss.kernel.plugins.dispatch.InvokeDispatchHelper;
import org.jboss.kernel.spi.config.KernelConfigurator;
import org.jboss.kernel.spi.dependency.KernelController;
import org.jboss.kernel.spi.dependency.KernelControllerContext;

/**
 * Installs/Uninstalls aware action.
 *
 * @author <a href="ales.justin@jboss.com">Ales Justin</a>
 */
public abstract class InstallsAwareAction extends KernelControllerContextAction
{
   /**
    * Get the action's state.
    *
    * @return get the action's state
    */
   protected abstract ControllerState getState();

   @Override
   public void installAction(KernelControllerContext context) throws Throwable
   {
      installActionInternal(context);
      doInstalls(context);
      setKernelControllerContext(context);
   }

   /**
    * Execute the installs.
    *
    * @param context the context
    * @throws Throwable for any error
    */
   protected void doInstalls(KernelControllerContext context) throws Throwable
   {
      KernelController controller = (KernelController)context.getController();
      Kernel kernel = controller.getKernel();
      KernelConfigurator configurator = kernel.getConfigurator();
      BeanMetaData metaData = context.getBeanMetaData();

      List<InstallMetaData> installs = metaData.getInstalls();
      if (installs != null && installs.isEmpty() == false)
      {
         int index = 0;
         try
         {
            int size = installs.size();
            for (; index < size; index++)
            {
               InstallMetaData install = installs.get(index);
               if (getState().equals(install.getState()))
               {
                  ControllerContext target = context;
                  if (install.getBean() != null)
                     target = controller.getContext(install.getBean(), install.getDependentState());
                  if (target instanceof InvokeDispatchContext)
                  {
                     ClassLoader previous = SecurityActions.setContextClassLoader(context);
                     try
                     {
                        InvokeDispatchHelper.invoke(
                              configurator,
                              target.getTarget(),
                              (InvokeDispatchContext)target,
                              install.getMethodName(),
                              install.getParameters()
                        );
                     }
                     finally
                     {
                        SecurityActions.resetContextClassLoader(previous);
                     }
                  }
                  else
                  {
                     throw new IllegalArgumentException("Cannot install, context " + target + " does not implement InvokeDispatchContext");
                  }
               }
            }
         }
         catch (Throwable t)
         {
            considerUninstalls(context, index);
            undoInstallAction(context);
            throw t;
         }
      }
   }

   /**
    * Consider the uninstalls.
    *
    * This method is here to be able to override
    * the behavior after installs failed.
    * e.g. perhaps only running uninstalls from the index.
    *
    * By default we run all uninstalls in the case
    * at least one install failed.
    *
    * @param context the context
    * @param index current installs index
    */
   protected void considerUninstalls(KernelControllerContext context, int index)
   {
      if (index > 0)
         doUninstalls(context);
   }

   /**
    * Undo the steps from install action
    * in the case of failed installs.
    * Usually this is what uninstallActionInternal does.
    *
    * @param context the context
    */
   protected void undoInstallAction(KernelControllerContext context)
   {
      uninstallActionInternal(context);
   }

   @Override
   public void uninstallAction(KernelControllerContext context)
   {
      unsetKernelControllerContext(context);
      doUninstalls(context);
      uninstallActionInternal(context);
   }

   /**
    * Execute uninstalls
    *
    * @param context the context
    */
   protected void doUninstalls(KernelControllerContext context)
   {
      BeanMetaData beanMetaData = context.getBeanMetaData();
      List<InstallMetaData> uninstalls = beanMetaData.getUninstalls();
      int size = uninstalls != null ? uninstalls.size() : 0;
      doUninstalls(context, size - 1);
   }

   /**
    * Execute uninstalls from the index.
    *
    * Leaving this method here in case
    * someone wants to use 'symetric' approach
    * to uninstalling installs.
    *
    * @param context the context
    * @param index   the current index
    */
   protected void doUninstalls(KernelControllerContext context, int index)
   {
      KernelController controller = (KernelController) context.getController();
      Kernel kernel = controller.getKernel();
      KernelConfigurator configurator = kernel.getConfigurator();
      BeanMetaData metaData = context.getBeanMetaData();

      List<InstallMetaData> uninstalls = metaData.getUninstalls();
      if (uninstalls != null && uninstalls.isEmpty() == false)
      {
         for (int i = index; i >= 0; --i)
         {
            InstallMetaData uninstall = uninstalls.get(i);
            if (getState().equals(uninstall.getState()))
            {
               ControllerContext target = context;
               if (uninstall.getBean() != null)
               {
                  target = controller.getContext(uninstall.getBean(), uninstall.getDependentState());
                  if (target == null)
                  {
                     log.warn("Ignoring uninstall action on target in incorrect state " + uninstall.getBean());
                     continue;
                  }
               }
               if (target instanceof InvokeDispatchContext)
               {
                  ClassLoader previous = null;
                  try
                  {
                     previous = SecurityActions.setContextClassLoader(context);
                     InvokeDispatchHelper.invoke(
                           configurator,
                           target.getTarget(),
                           (InvokeDispatchContext)target,
                           uninstall.getMethodName(),
                           uninstall.getParameters()
                     );
                  }
                  catch (Throwable t)
                  {
                     log.warn("Ignoring uninstall action on target " + uninstall, t);
                  }
                  finally
                  {
                     if (previous != null)
                        SecurityActions.resetContextClassLoader(previous);
                  }
               }
               else
               {
                  log.warn("Cannot uninstall, context " + target + " does not implement InvokeDispatchContext for " + uninstall.getBean());
               }
            }
         }
      }
   }
}
