/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.mx.interceptor;

import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import javax.management.Descriptor;
import javax.management.PersistentMBean;
import javax.management.MBeanException;
import javax.management.InstanceNotFoundException;
import javax.management.modelmbean.ModelMBeanInfo;

import org.jboss.mx.modelmbean.ModelMBeanConstants;
import org.jboss.mx.service.ServiceConstants;
import org.jboss.mx.server.InvocationException;
import org.jboss.mx.server.Invocation;
import org.jboss.mx.server.MBeanInvoker;

/** A peristence interceptor that uses the java.util.Timer framework for the
 * scheculed peristence policies.
 *
 * @see PersistentMBean
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.2.8.8 $
 */
public class PersistenceInterceptor
         extends AbstractInterceptor
         implements ModelMBeanConstants, ServiceConstants
{
   /** The HashMap<name, policy> of attribute level policies */
   private HashMap attrPersistencePolicies = new HashMap();
   /** The HashMap<name, PersistenceTimerTask> of scheduled peristence */
   private HashMap timerTaskMap = new HashMap();
   /** The bean level peristence policy */
   private String mbeanPersistencePolicy;
   /** The PersistentMBean load/store callback interface */
   private PersistentMBean callback;

   /**
    * @param info the mbean info
    * @param invoker An invoker that must implement PersistentMBean
    */
   public PersistenceInterceptor()
   {
      super("Default Persistence Interceptor");
   }

   // Public --------------------------------------------------------
   public Object invoke(Invocation invocation) throws InvocationException
   {
      if( callback == null )
      {
         try
         {
            lazyInit(invocation);
         }
         catch(MBeanException e)
         {
            throw new InvocationException(e, "Failed to init persistence policy");
         }
      }

      Object returnValue = invocation.nextInterceptor().invoke(invocation);
      String type = invocation.getType();
      if (type != Invocation.OP_SETATTRIBUTE )
         return returnValue;

      String attrName = invocation.getName();
      String policy = (String)attrPersistencePolicies.get(attrName);
      if (policy == null)
         policy = mbeanPersistencePolicy;

      if (policy.equalsIgnoreCase(PM_ON_UPDATE) == true)
      {
         try
         {
            MBeanInvoker invoker = invocation.getInvoker();
            Descriptor attrDesc = invocation.getDescriptor();
            invoker.updateAttributeInfo(attrDesc);            
            callback.store();
         }
         catch (Throwable t)
         {
            // FIXME: check the exception handling
            throw new InvocationException(t, "Cannot persist the MBean data.");
         }
      }
      else if(policy.equalsIgnoreCase(PM_NO_MORE_OFTEN_THAN) == true)
      {
         PersistenceTimerTask task = (PersistenceTimerTask) timerTaskMap.get(attrName);
         if( task != null )
            task.setHasUpdated(true);
      }
      return returnValue;
   }

   /**
    * 
    * @param invocation
    */ 
   private synchronized void lazyInit(Invocation invocation) throws MBeanException
   {
      // This requires the invoker to implement PersistentMBean
      MBeanInvoker invoker = invocation.getInvoker();
      callback = (PersistentMBean) invocation.getInvoker();
      ModelMBeanInfo info = (ModelMBeanInfo) invoker.getMetaData();
      Descriptor[] descriptors = info.getDescriptors(ALL_DESCRIPTORS);
      mbeanPersistencePolicy = PM_NEVER;
      for (int i = 0; i < descriptors.length; ++i)
      {
         String policy = (String) descriptors[i].getFieldValue(PERSIST_POLICY);
         String persistPeriod = (String)descriptors[i].getFieldValue(PERSIST_PERIOD);
         String type = (String) descriptors[i].getFieldValue(DESCRIPTOR_TYPE);

         if( type.equalsIgnoreCase(MBEAN_DESCRIPTOR) )
         {
            if( policy == null )
               policy = PM_NEVER;
            mbeanPersistencePolicy = policy;
            if( mbeanPersistencePolicy.equalsIgnoreCase(PM_ON_TIMER) ||
               mbeanPersistencePolicy.equalsIgnoreCase(PM_NO_MORE_OFTEN_THAN) )
            {
               boolean isNoMoreOftenThan = mbeanPersistencePolicy.equalsIgnoreCase(PM_NO_MORE_OFTEN_THAN);
               schedulePersistenceNotifications(Long.parseLong(persistPeriod),
                  MBEAN_DESCRIPTOR, isNoMoreOftenThan);
            }
         }
         else if( policy != null )
         {
            String name = (String) descriptors[i].getFieldValue(NAME);
            attrPersistencePolicies.put(name, policy);

            if( policy.equalsIgnoreCase(PM_ON_TIMER) ||
               policy.equalsIgnoreCase(PM_NO_MORE_OFTEN_THAN) )
            {
               boolean isNoMoreOftenThan = policy.equalsIgnoreCase(PM_NO_MORE_OFTEN_THAN);
               schedulePersistenceNotifications(Long.parseLong(persistPeriod),
                  name, isNoMoreOftenThan);
            }
         }
      }
   }

   private void schedulePersistenceNotifications(long persistPeriod, String name,
      boolean isNoMoreOftenThan)
   {
      // @todo: unschedule on unregistration/descriptor field change
      PersistenceTimerTask task = new PersistenceTimerTask(name, isNoMoreOftenThan);
      Timer timer = new Timer(true);
      timer.scheduleAtFixedRate(task, 0, persistPeriod);
      timerTaskMap.put(name, task);
   }

   // Inner classes -------------------------------------------------
   private class PersistenceTimerTask extends TimerTask
   {
      boolean noMoreOftenThan;
      boolean hasUpdated;
      String name;
      PersistenceTimerTask(String name, boolean noMoreOftenThan)
      {
         this.name = name;
         this.noMoreOftenThan = noMoreOftenThan;
      }
      synchronized void setHasUpdated(boolean flag)
      {
         hasUpdated = flag;
      }
      public void run()
      {
         try
         {
            // @todo: add PersistenceContext field to MBean's descriptor to
            //        relay attribute name (and possibly other) info with the
            //        persistence callback
            boolean doStore = (noMoreOftenThan == true && hasUpdated == true)
               || noMoreOftenThan == false;
            if( doStore == true )
            {
               callback.store();
               setHasUpdated(false);
            }
         }
         catch (MBeanException e)
         {
            // FIXME: log exception
         }
         catch (InstanceNotFoundException e)
         {
            // FIXME: log exception
         }
      }
   }
}
