/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library 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 any later version.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id:PropertiesConfigurationActuator.java 10753 2007-06-26 13:39:43Z durieuxp $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.management.reconfig.actuator;

// general Java imports
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;

import org.ow2.jonas.lib.management.reconfig.IConfigurationActuator;
import org.ow2.jonas.lib.management.reconfig.ReconfigException;

import org.objectweb.util.monolog.api.BasicLevel;

/**
 * This class allows for persistent reconfiguration of a JOnAS service or of a JOnAS resource
 * being configured by a .properties file.
 * @author Adriana Danes
 */
public class PropertiesConfigurationActuator extends BaseConfigurationActuator {

    /**
     * Initial configuration values, then saved reconfigured values.
     */
    private Properties stableConfig;

    /**
     * IConfigurationData values (non yet saved).
     */
    private Properties currentConfig;

    /**
     * Construct an {@link IConfigurationActuator} for a JOnAS service or a JOnAS resource.
     * @param name Service or resource name to reconfigure. The name of the .properties configuration
     *        file to be updated. It may be 'jonas.properties' if the IConfigurationActuator is associated to
     *        a JOnAS service, or a 'resource.properties' if the {@link IConfigurationActuator}
     *        is associated to a data source or a mail factory.
     * @param config the initial content of the .properties configuration file
     * @param configFileName name of the configuration file to update/save
     */
    public PropertiesConfigurationActuator(final String name, final String configFileName, final Properties config) {
        super(name, configFileName);
        stableConfig = config;
        currentConfig = new Properties();
    }

    /**
     * Updates the configuration file.
     * @param key the modified property name
     * @param value the new value
     * @param sequence the sequence number of management notification producing the update
     */
    public void updateConfig(final String key, final String value, final long sequence) {
        if (sequence > lastSequence) {
            currentConfig.setProperty(key, value);
            lastSequence = sequence;
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "- " + sequence + " - Recufigured property to update "
                        + key + " with value : " + value);
            }
        } else {
            logger.log(BasicLevel.WARN, "Received out of order reconfiguration message !");
            logger.log(BasicLevel.WARN, "Reconfiguration value for property " + key + " : " + value + " lost!");
        }
    }

    /**
     * Updates the configuration file.
     * @param key the modified property name
     * @param value the new value
     * @param add if true, the value has to be appended to the old value, if false, it has to be removed from the old value
     * @param sequence the sequence number of management notification producing the update
     */
    public void updateConfig(final String key, final String value, final boolean add, final long sequence) {
        if (sequence > lastSequence) {
            String oldValue = currentConfig.getProperty(key);
            if (oldValue == null) {
                oldValue = stableConfig.getProperty(key);
            }
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "- " + sequence + " - Recufigured property to update "
                        + key + " having value : " + oldValue);
            }
            String newValue = updateValue(oldValue, value, add);
            currentConfig.setProperty(key, newValue);
            lastSequence = sequence;
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "- " + sequence + " - Updated property " + key + " with value : " + newValue);
            }
        } else {
            logger.log(BasicLevel.WARN, "Received out of order reconfiguration message !");
            logger.log(BasicLevel.WARN, "Reconfiguration value for property " + key + " : " + value + " lost!");
        }
    }

    /**
     * Updates the configuration file.
     * @param props set of modified properties with their associated values
     * @param sequence the sequence number of management notification producing the update
     */
    public void updateConfig(final Properties props, final long sequence) {
        if (sequence > lastSequence) {
            for(Enumeration pNames = props.propertyNames(); pNames.hasMoreElements(); ) {
                String aName = (String) pNames.nextElement();
                String aValue = props.getProperty(aName);
                if (aValue != null) {
                    currentConfig.setProperty(aName, aValue);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "- " + sequence + " - Updated property "
                                + aName + " with value : " + aValue);
                    }
                }
            }
            lastSequence = sequence;
        } else {
            logger.log(BasicLevel.WARN, "Received out of order reconfiguration message !");
        }
    }

    /**
     *
     * @param oldValue
     * @param value
     * @param add
     * @return
     */
    public String updateValue(String oldValue, String value, final boolean add) {
        value = value.trim();
        oldValue = oldValue.trim();
        String returnValue;
        if (add) {
            returnValue = new String(oldValue);
            returnValue = returnValue + ',' + value;
        } else {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Remove " + value + " from " + oldValue);
            }
            returnValue = new String();
            boolean firstToken = true;
            StringTokenizer st = new StringTokenizer(oldValue, ",");
            while (st.hasMoreTokens()) {
                String token = st.nextToken().trim();
                if (!token.equals(value)) {
                    // add the token
                    if (firstToken) {
                        returnValue = new String(token);
                        firstToken = false;
                    } else {
                        returnValue = returnValue + ',' + token;
                    }
                }
            }
        }
        return returnValue;
    }

    /**
     * Saves the updated configuration.
     * @param sequence the sequence number of management notification producing the save (in fact store) operation
     * @throws ReconfigException if Failed to save reconfiguration
     */
    public void saveConfig(final long sequence) throws ReconfigException {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "");
        }
        if (sequence > lastSequence) {
            // Update stableConfig with properties in currentConfig
            for (Enumeration props = currentConfig.keys(); props.hasMoreElements();) {
                String reconfiguredProp = (String) props.nextElement();
                String reconfiguredPropValue = currentConfig.getProperty(reconfiguredProp);
                stableConfig.setProperty(reconfiguredProp, reconfiguredPropValue);
            }
            try {
                // Store stableConfig
                FileOutputStream fo = new FileOutputStream(configFileName);
                stableConfig.store(fo,  "Saved configuration file at ");
                fo.close();
                lastSequence = sequence;
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "Configuration file " + configFileName + " updated");
                }
            } catch (FileNotFoundException e) {
                throw new ReconfigException("Cant' save configuration file: " + e.toString());
            } catch(IOException ioe) {
                throw new ReconfigException("Cant' save configuration file: " + ioe.toString());
            }
        } else {
            logger.log(BasicLevel.WARN, "Received out of order save reconfiguration message for " + name + " !");
            logger.log(BasicLevel.WARN, "Can not save !!");
            logger.log(BasicLevel.WARN, "Please reconfigure and than save !!");
            currentConfig = new Properties();
        }
    }
}
