/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, 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.mx.persistence;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Vector;

import javax.management.Descriptor;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.RequiredModelMBean;

import org.jboss.mx.modelmbean.ModelMBeanConstants;
import org.jboss.mx.modelmbean.ModelMBeanInvoker;
import org.jboss.mx.modelmbean.RequiredModelMBeanInstantiator;
import org.jboss.mx.server.registry.MbeanInfoDb;

/**
 * Class responsible for storing and loading the MBean info database.
 * @author Matt Munz
 */
public class MbeanInfoDbPm
   extends MBeanInfoOdb
   implements PersistenceManager
{

   protected boolean fLoadedFromFs;
   protected Vector fNamesToRestore;
   protected Vector fInfosToRestore;
   protected MBeanServer fMbeanServer;
   protected MbeanInfoDb fMbeanInfoDb;
   protected File fMbiDbRoot;

   public MbeanInfoDbPm()
   {
      super();
   }

   /**
    * @todo finish implementing
    */
   public void load(ModelMBeanInvoker mbean, MBeanInfo info)
     throws MBeanException
   {

      logger().debug("Loading the MBI DB");
      setMbeanInfoDb((MbeanInfoDb) mbean.getResource());
      if(!loadedFromFs())
      {

         // get mbi db root from descriptor
         Descriptor d = ((ModelMBeanInfo) info).getMBeanDescriptor();
         String dir = (String) d.getFieldValue(
                         ModelMBeanConstants.PERSIST_LOCATION);
         if((dir != null) && (!dir.equals("")))
         {

            setMbiDbRoot(new File(dir));
         }
         String message = "Loading the MBI DB from the filesystem.  location: ";
         message += mbiDbRoot();
         logger().debug(message);
         File[] mbiFiles = mbiDbRoot().listFiles();
         if(mbiFiles == null)
         {

            return;
         }
         // loop over each item in the mbeaninfo store
         for(int index = 0; index < mbiFiles.length; index++)
         {

            /// for each, load, make a new mbean, and register that mbean
            File curFile = mbiFiles[index];
            ObjectName nameToRestore = null;
            try
            {

               nameToRestore = objectName(curFile);
            }
            catch(MalformedObjectNameException cause)
            {

               throw new MBeanException(cause, 
                                        "Object Name was stored incorrectly.");
            }
            MBeanInfo infoToRestore = null;
            try
            {

               infoToRestore = load(curFile);
            }
            catch(IOException cause)
            {

               throw new MBeanException(cause, 
                                        "Couldn't read the MBeanInfo file.");
            }
            catch(ClassNotFoundException cause2)
            {

               String message2 = "Couldn't find the Class specified in the object file.";
               throw new MBeanException(cause2, message2);
            }
            if(infoToRestore == null)
            {

               throw new MBeanException(new Exception(
                                           "Null loaded MBean info.  Error on load."), 
                                        "Could not load");
            }
            namesToRestore().add(nameToRestore);
            infosToRestore().add(infoToRestore);
         }
         setLoadedFromFs(true);
      }
      try
      {

         register();
      }
      catch(Exception cause)
      {

         throw new MBeanException(cause, 
                                  "Error trying to register loaded MBeans");
      }
   }

   public void store(MBeanInfo info) throws MBeanException
   {

      logger().debug("storing MBI DB State");
      Enumeration queue = mbeanInfoDb().mbiPersistenceQueue();
      for(; queue.hasMoreElements();)
      {

         ObjectName curName = (ObjectName) queue.nextElement();
         logger().debug("queue elem: " + curName);
         MBeanInfo curInfo = null;
         try
         {

            curInfo = getMBeanServer().getMBeanInfo(curName);
         }
         catch(InstanceNotFoundException cause)
         {

            throw new MBeanException(cause);
         }
         catch(JMException cause3)
         {

            throw new MBeanException(cause3);
         }
         if(curInfo == null)
         {

            throw new MBeanException(
               new Exception("Current MBean Info object is null."), 
               "Could not store null object.");
         }
         try
         {

            store(curName, curInfo);
         }
         catch(IOException cause2)
         {

            throw new MBeanException(cause2);
         }
         mbeanInfoDb().removeFromMbiQueue(curName);
         logger().info("Successfully stored mbi for " + curName);
      }
   }

   protected void store(ObjectName name, MBeanInfo info)
     throws IOException
   {

      File location = new File(mbiDbRoot(), fileName(name));
      logger().debug("Storing mbi at: " + location);
      mbiDbRoot().mkdirs();
      store(info, location);
   }

   /**
    * Iterates over the loaded MBean infos and ObjectNames, creates an MBean for each, and
    * registers it with the server.  This includes creating the resource object as well.
    */
   protected void register() throws JMException, 
                                    InvalidTargetObjectTypeException
   {

      // iterate over all loaded infos and register them.  
      logger().debug("registering...");
      Enumeration names = namesToRestore().elements();
      Enumeration infos = infosToRestore().elements();
      try
      {

         while(names.hasMoreElements() && infos.hasMoreElements())
         {

            ObjectName curName = (ObjectName) names.nextElement();
            logger().debug("curName: " + curName);
            ModelMBeanInfo curInfo = (ModelMBeanInfo) infos.nextElement();
            Descriptor mbeanDescriptor = curInfo.getMBeanDescriptor();
            String fieldName = ModelMBeanConstants.RESOURCE_CLASS;
            String className = (String) mbeanDescriptor.getFieldValue(fieldName);
            logger().debug("className: " + className);
            Object resource = getMBeanServer().instantiate(className);
            ModelMBean modelmbean = RequiredModelMBeanInstantiator.instantiate();
            modelmbean.setModelMBeanInfo(curInfo);
            modelmbean.setManagedResource(resource, "ObjectReference");
            getMBeanServer().registerMBean(modelmbean, curName);
         }
      }
      finally
      {

         namesToRestore().removeAllElements();
         infosToRestore().removeAllElements();
      }
   }

   protected MBeanServer getMBeanServer() throws JMException
   {

      if(fMbeanServer == null)
      {

         Collection col = MBeanServerFactory.findMBeanServer(null);
         if(col.isEmpty())
         {

            throw new JMException("No MBeanServer found");
         }
         fMbeanServer = (MBeanServer) col.iterator().next();
      }
      return fMbeanServer;
   }
   
   /**
    * JDK 1.3.1 substitute for jdk 1.4.1 String.replaceAll()
    * Does not accept regular expressions...
    * returns the object string where all instances of target are replaced with replacement
    * @todo replace with a more appropriate string util if available
    */
   protected String replaceAll(String object, String target, String replacement)
   {
      
      return replaceAll(object, target, replacement, 0);
   }
   
   /**
    * Iterative
    */
   protected String replaceAll(String object, String target, String replacement, int curIndex)
   {
     int indexOfMatch = object.indexOf(target, curIndex);
     if(indexOfMatch < curIndex)
     {
      
        return object;
     }
     String prefix = "";
     if(indexOfMatch > 0)
     {
         
        prefix = object.substring(0, indexOfMatch);
     }
     String tail = object.substring(indexOfMatch + target.length());
     String newObject = prefix + replacement + tail;
     return replaceAll(newObject, target, replacement, indexOfMatch + replacement.length());     
   }

   protected String objNameSeparator()
   {

      return ":";
   }

   protected String objNameSepRep()
   {

      return "___";
   }

   protected String fileName(ObjectName name)
   {

      String fileName = name.getCanonicalName();
      fileName = replaceAll(fileName, objNameSeparator(), objNameSepRep());
      return fileName;
   }

   protected ObjectName objectName(File fileName)
     throws MalformedObjectNameException
   {

      String objectName = fileName.getName();
      objectName = replaceAll(objectName, objNameSepRep(), objNameSeparator());
      return new ObjectName(objectName);
   }

   protected boolean loadedFromFs()
   {

      return fLoadedFromFs;
   }

   protected void setLoadedFromFs(boolean newLoadedFromFs)
   {

      fLoadedFromFs = newLoadedFromFs;
   }

   protected Vector namesToRestore()
   {

      if(fNamesToRestore == null)
      {

         fNamesToRestore = new Vector(10);
      }
      return fNamesToRestore;
   }

   protected Vector infosToRestore()
   {

      if(fInfosToRestore == null)
      {

         fInfosToRestore = new Vector(10);
      }
      return fInfosToRestore;
   }

   protected File mbiDbRoot()
   {

      if(fMbiDbRoot == null)
      {

         fMbiDbRoot = new File("../conf/mbean-info-db/");
      }
      return fMbiDbRoot;
   }

   protected void setMbiDbRoot(File newMbiDbRoot)
   {

      fMbiDbRoot = newMbiDbRoot;
   }

   protected MbeanInfoDb mbeanInfoDb()
   {

      return fMbeanInfoDb;
   }

   protected void setMbeanInfoDb(MbeanInfoDb newMbeanInfoDb)
   {

      fMbeanInfoDb = newMbeanInfoDb;
   }
}
