/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file 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.test.jmx.xmbean;

import java.beans.PropertyEditor;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.Descriptor;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanInfo;

import org.jboss.mx.metadata.MBeanInfoConversion;
import org.jboss.mx.modelmbean.ModelMBeanConstants;
import org.jboss.mx.modelmbean.ModelMBeanInvoker;
import org.jboss.mx.persistence.PersistenceManager;

import org.jboss.logging.Logger;
import org.jboss.util.StringPropertyReplacer;
import org.jboss.util.propertyeditor.PropertyEditors;

/**
 * XML File Persistence Manager. <p>
 *
 * Persists the MBean to the file system using an XML file.
 * 
 * The file has to follow this (simple) DTD:
 * <pre>
 * <!ELEMENT attribute (#PCDATA)>
 * <!ATTLIST attribute
 *	name CDATA #REQUIRED
 *	type CDATA #REQUIRED
 *  >
 * <!ELEMENT attributes (attribute*)>
 * </pre>
 * 
 * @author Heiko.Rupp@cellent.de
 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>.
 * @version $Revision: 81036 $
 */
public class XMLFilePersistenceManager extends Object
   implements PersistenceManager
{
   protected static Logger log = Logger.getLogger(XMLFilePersistenceManager.class);
   /** A flag set to true to prevent attribute updates from within load
    * triggering stores.
    */
   protected boolean isLoading;

   // Constructors --------------------------------------------------

   public XMLFilePersistenceManager()
   {
      super();
   }

   // Public --------------------------------------------------------

   /**
    * Loads the attributes from the given file
    *
    * @param  mbean to store to
    * @param  metadata with file location etc.
    * @exception  MBeanException
    */
   public void load(ModelMBeanInvoker mbean, MBeanInfo metadata)
      throws MBeanException
   {
      log.debug("load, resource:" + mbean.getResource());

      if (metadata == null)
      {
         return;
      }
      if (log.isTraceEnabled())
         log.trace("metadata: " + metadata);

      File storeFile = getStoreFile(metadata, false);
      if (storeFile == null)
      {
         return;
      }

      try
      {
         AttributeList attributes = new AttributeList();

         FileInputStream fis = new FileInputStream(storeFile);
         BufferedReader buf = new BufferedReader(new InputStreamReader(fis));

         String line, line2;
         while ((line = buf.readLine()) != null)
         {
            log.trace("Line: " + line);
            if (line.equals("<attributes>"))
               continue;
            if (line.equals("</attributes>"))
               continue;

            // trim leading spaces
            line = line.substring(line.indexOf("<"));

            if (!line.startsWith("<attribute"))
            {
               log.warn("Unknown line, skipping " + line);
               continue;
            }

            // read attribute name
            int pos = line.indexOf("name=\"") + 6; // skip name="
            line2 = line.substring(pos);
            int pos2 = line2.indexOf("\"");
            String name = line2.substring(0, pos2);
            log.debug("name: " + name);

            // read attribute type
            pos = line.indexOf("type=\"") + 6; // skip type="
            line2 = line.substring(pos);
            pos2 = line2.indexOf("\"");
            String type = line2.substring(0, pos2);
            log.debug("type: " + type);

            // read attribute value
            pos = line.indexOf(">");
            pos2 = line.lastIndexOf("<");
            String value = line.substring(pos + 1, pos2);
            log.debug("value :" + value);

            Object oVal = convert(value, type);
            Attribute att = new Attribute(name, oVal);
            attributes.add(att);

         } // while

         mbean.setAttributes(attributes);
      }
      catch (Exception e)
      {
         log.error("Error loading MBean state", e);
      }
      setIsLoading(false);
   }

   /** What we need to get here is 1) the persist location, and 2) the entire
    * contents of the mbean. #2 contains the entire contents (state) of the
    * model object, as well as the meta data that the mbean provides.
    * As such, serializing this (MBeanInfo) object (brute force) in effect
    * serializes the model as well.
    *
    * @param  metadata
    * @exception  MBeanException
    */
   public void store(MBeanInfo metadata) throws MBeanException
   {
      if (isLoading())
      {
         return;
      }

      ModelMBeanInfo mmeta =
         MBeanInfoConversion.toModelMBeanInfo(metadata, true);

      log.debug("store");
      if (log.isTraceEnabled())
         log.trace("metadata: " + metadata);
      File storeFile = getStoreFile(metadata, true);
      if (storeFile == null)
      {
         return;
      }

      try
      {
         log.debug("Storing to file: " + storeFile.getAbsolutePath());
         FileOutputStream fos = new FileOutputStream(storeFile);
         MBeanAttributeInfo[] mais = mmeta.getAttributes();
         StringBuffer buf = new StringBuffer();
         buf.append("<attributes>\n");
         for (int i = 0; i < mais.length; i++)
         {
            ModelMBeanAttributeInfo mai = (ModelMBeanAttributeInfo) mais[i];
            buf.append(" <attribute name=\"" + mai.getName() + "\" ");
            buf.append("type=\"" + mai.getType() + "\">");
            log.debug("Trying to load " + mai.getName());
            Descriptor aDesc = mai.getDescriptor();
            if (aDesc==null)
            	throw new Exception("aDesc is null");
            log.debug(aDesc.toString());
            Object att = aDesc.getFieldValue(ModelMBeanConstants.ATTRIBUTE_VALUE);
            if (att!=null)
            	buf.append(att.toString());
            else
            	log.warn("att was null");
            buf.append("</attribute>\n");
         }
         buf.append("</attributes>");
         log.trace(buf.toString());
         fos.write(buf.toString().getBytes());
         fos.close();
      }
      catch (Exception e)
      {
      		e.printStackTrace();
         throw new MBeanException(e, "Error in persisting MBean.");
      }
   }

   // Protected -----------------------------------------------------

   protected boolean isLoading()
   {
      return isLoading;
   }

   protected void setIsLoading(boolean newIsLoading)
   {
      isLoading = newIsLoading;
   }

   /**
    * Obtain the store location from the ModelMBean Descriptor.
    * If the file name does not end on ".xml", it will be converted
    * to do so.
    * @param metadata
    * @param createFile
    * @return
    * @throws MBeanException
    */
   protected File getStoreFile(MBeanInfo metadata, boolean createFile)
      throws MBeanException
   {
      Descriptor d = ((ModelMBeanInfo) metadata).getMBeanDescriptor();
      String dirPath =
         (String) d.getFieldValue(ModelMBeanConstants.PERSIST_LOCATION);
      String file = (String) d.getFieldValue(ModelMBeanConstants.PERSIST_NAME);
      if (dirPath == null)
      {
         log.debug(
            "No "
               + ModelMBeanConstants.PERSIST_LOCATION
               + " descriptor value found, using '.'");
         dirPath = ".";
      }
      if (file == null)
      {
         log.debug(
            "No "
               + ModelMBeanConstants.PERSIST_NAME
               + " descriptor value found");
         return null;
      }

      dirPath = StringPropertyReplacer.replaceProperties(dirPath);
      file = StringPropertyReplacer.replaceProperties(file);
      // tack on .xml if not there
      if (!file.endsWith(".xml"))
         file = file + ".xml";
      File dir = new File(dirPath);
      File storeFile = new File(dir, file);
      boolean exists = storeFile.exists();
      log.debug("Store file is: " + storeFile.getAbsolutePath());
      if (exists == false && createFile == true)
      {
         dir.mkdirs();
         try
         {
            storeFile.createNewFile();
         }
         catch (IOException e)
         {
            throw new MBeanException(e, "Failed to create store file");
         }
      }
      else if (exists == false)
      {
         storeFile = null;
      }
      return storeFile;
   }

   /**
    * Convert val into an Object of type type -- same as in twiddle.setCommand
    * @param val The given value
    * @param oType the wanted return type
    * @return the value in the correct representation
    * @throws Exception various :)
    */
   private Object convert(String val, String oType) throws Exception
   {
      PropertyEditor editor = PropertyEditors.getEditor(oType);
      editor.setAsText(val);
      return editor.getValue();
   }
}
