/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/store/monitoring/Monitor.java,v 1.4 2004/07/30 06:52:03 ozeigermann Exp $
 * $Revision: 1.4 $
 * $Date: 2004/07/30 06:52:03 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.slide.store.tamino.store.monitoring;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.JDom;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;


/**
 ** Monitors a monitorable component.
 ** Manages monitored items (counters and timers) of the monitored component.
 ** One instance of the Monitor
 ** holds all timers and counters of one IMonitorable object.
 **
 ** @author  peter.nevermann@softwareag.com
 ** @version $Revision: 1.4 $
 ** @see IMonitorable
 **/
public class Monitor implements IMonitor {
    
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    /** The hierarchy of monitored objects */
    private static Map allMonitored = new HashMap();
    
    /** The root of the monitored hierarchy */
    private static IMonitorable root = new AbstractMonitorable();
    static {
        ((AbstractMonitorable)root).monitor = getMonitor( root );
    }
    
    /** the object monitored by this monitor */
    private IMonitorable monitored;
    
    /** Map of all monitored items of the attached IMonitorable.
     ** The items are addressed by name.
     **/
    private Map items = new HashMap ();
    
    /** All monitored items of the attached IMonitorable in a specified order. **/
    private List itemsList = new ArrayList();
    
    /** Indicates, if monitoring attached to this is enabled */
    private boolean enabled = true;
    
    /** The XML outputter */
    private XMLOutputter xmlOut = JDom.outputter();
    
    
    // -------------------------------------------------------------------------
    // static methods
    // -------------------------------------------------------------------------
    
    /**
     ** Factory method of Monitor.
     ** The IMonitorable is stored in a Map identified by a unique Name.
     **
     ** @pre     monitored != null
     **
     ** @param      monitored   the object to be monitored
     ** @return    instance of Monitor associated with the calling IMonitorable
     **
     **/
    public static Monitor getMonitor( IMonitorable monitored ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getMonitor", new Object[] {monitored} );
        
        Monitor monitor = getMonitor(monitored, false); // displaceRoot=false
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getMonitor", monitor );
        return monitor;
    }
    
    /**
     ** Factory method of Monitor which is able to displace the ROOT.
     ** The IMonitorable is stored in a Map identified by a unique Name.
     **
     ** @pre       monitored != null
     **
     ** @param     monitored the object to be monitored
     ** @param     displaceRoot if true, the given monitorable displaces ROOT
     ** @return    instance of Monitor associated with the calling IMonitorable
     **
     **/
    public static Monitor getMonitor( IMonitorable monitored, boolean displaceRoot ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getMonitor", new Object[] {monitored, new Boolean(displaceRoot)} );
        
        String monUri = uriFor( monitored, displaceRoot );
        
        if( root != monitored && displaceRoot ) {
            // special handling: ROOT can be can be displaced!
            monitored.getMonChildren().addAll( root.getMonChildren() );
            root = monitored;
        }
        
        Object monDispl = allMonitored.put( monUri, monitored );
        
        if( monDispl != null ) {
            if( root != monitored ) {
                throw new RuntimeException(
                    "IMonitorable "+monUri+" already registered" );
            }
            else {
                // displace root
                logger.fine( CLASSNAME, "getMonitor", "Displaced root "+monUri );
            }
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.fine( CLASSNAME, "getMonitor", "Registered "+monUri );
        
        if( root != monitored && monitored.getMonParent() == null )
            root.getMonChildren().add( monitored );
        
        Monitor monitor = new Monitor(monitored);
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getMonitor", monitor );
        return monitor;
    }
    
    /**
     ** Create the uri associated to the given monitored object.
     **
     ** @pre     monitored != null
     **
     ** @param      monitored the monitored object
     ** @param      isRoot if true, this method returns "/"
     ** @return     return the uri
     **/
    public static String uriFor( IMonitorable monitored, boolean isRoot ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "uriFor", new Object[] {monitored, new Boolean(isRoot)} );
        
        String uri = null;
        
        if( isRoot ) {
            if( monitored.getMonParent() != null )
                throw new IllegalStateException( "Monitorable '"+monitored.getMonName()+"' cannot be root, "+
                                                    "because has parent '"+monitored.getMonParent().getMonName()+"'" );
            uri = "/";
        }
        else {
            StringBuffer sb = new StringBuffer();
            sb.insert( 0, monitored.getMonName() );
            
            while( monitored.getMonParent() != null ) {
                sb.insert( 0, '/' );
                monitored = monitored.getMonParent();
                sb.insert( 0, monitored.getMonName() );
            }
            
            sb.insert( 0, '/' );
            uri = sb.toString();
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "uriFor", uri );
        return uri;
    }
    
    /**
     ** Returns the IMonitorable identified by uri.
     **
     ** @param      uri the uri of the monitored object
     ** @return     the monitored object
     **
     **/
    public static IMonitorable getMonitored( String uri ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getMonitored", new Object[] {uri} );
        
        IMonitorable monitored = (IMonitorable) allMonitored.get( uri );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getMonitored", monitored );
        return monitored;
    }
    
    /**
     ** Clear all monitored.
     **/
    public static void clear() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "clear" );
        
        allMonitored.clear();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "clear" );
    }
    
    /**
     ** Returns all monitored objects.
     **
     ** @return collection of monitored objects
     **
     **/
    public static Collection xslPiTargetored() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getAllMonitored" );
        
        Collection result = allMonitored.values();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getAllMonitored", result );
        return result;
    }
    
    /**
     * Method dump
     *
     */
    public static void dump() {
        System.out.println("Monitor Dump");
        System.out.println("------------");
        Iterator i = allMonitored.keySet().iterator();
        while( i.hasNext() ) {
            String uri = (String)i.next();
            IMonitorable monitored = getMonitored( uri );
            System.out.println(uri+": "+monitored.getMonName());
        }
        System.out.println("");
    }
    
    
    // -------------------------------------------------------------------------
    // instance methods
    // -------------------------------------------------------------------------
    
    /**
     ** Constructs a new Monitor.
     ** This constructor may only be instantiated by the factory method getMonitor.
     **
     ** @param       monitored the monitored object
     **
     **/
    private Monitor(IMonitorable monitored) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>", new Object[] {monitored} );
        
        this.monitored = monitored;
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }
    
    /**
     ** Create a new timer. Name must be unique within one instance
     ** of a Monitor.
     **
     ** @param      name the timer's name
     ** @return     a new monitored timer item.
     **
     **/
    public synchronized MonitoredTimer getTimer (String name) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getTimer", new Object[] {name} );
        
        MonitoredTimer timer = new MonitoredTimer (name, this);
        if (items.put (name, timer) != null)
            throw new IllegalStateException(
                "Timer "+name+" already registered" );
        
        itemsList.add( timer );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getTimer", timer );
        return timer;
    }
    
    /**
     ** Create a new counter. Name must be unique within one instance
     ** of a Monitor.
     **
     ** @param      name the counter's name
     ** @return     a new monitored counter item.
     **
     **/
    public synchronized MonitoredCounter getCounter (String name) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getCounter", new Object[] {name} );
        
        MonitoredCounter counter = new MonitoredCounter (name, this);
        if (items.put (name, counter) != null)
            throw new IllegalStateException(
                "Counter " + name + " already registered");
        
        itemsList.add( counter );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getCounter", counter );
        return counter;
    }
    
    /**
     ** Register a property knowing its name. The name must be unique within one instance
     ** of a Monitor. Via this name the monitorable is requested to get the value of this
     ** property.
     **
     ** @param  propName   name of the property
     **
     **/
    public void registerProperty(String propName) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "registerProperty", new Object[] {propName} );
        
        MonitoredProperty prop = new MonitoredProperty (propName, this);
        if (items.put (propName, prop) != null)
            throw new IllegalStateException(
                "Property " + propName + " already registered");
        
        itemsList.add( prop );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "registerProperty", propName );
    }
    
    /**
     * Return true, if this monitor is enabled.
     *
     * @return  true, if this monitor is enabled
     */
    public boolean isEnabled () {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "isEnabled" );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "isEnabled", new Boolean(enabled) );
        return enabled;
    }
    
    // --------------------------------------------------------------------------
    // methods for interface IMonitor
    // --------------------------------------------------------------------------
    
    /**
     ** Get the monitored object.
     **
     ** @return the monitored object
     **
     **/
    public IMonitorable getMonitored () {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getMonitored" );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getMonitored", monitored );
        return monitored;
    }
    
    /**
     ** Creates a snapshot as XML string.
     **
     ** @return a snapshot as XML string
     ** @exception IOException
     **
     **/
    public String snapshotXML() throws java.io.IOException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "snapshotAsXML" );
        
        Document doc = new Document( (Element)null );
        Element root = snapshotElement();
        doc.setRootElement( root );
        
        String result = xmlOut.outputString( doc );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "snapshotAsXML", result );
        return result;
    }

    public Element snapshotElement() {
        Element root = new Element( "snapshot" );
        root.setAttribute( "monitored", getMonitored().getMonName() );
        Iterator it = itemsList.iterator();
        
        while( it.hasNext() ) {
            MonitoredItem item = (MonitoredItem)it.next();
            Element e = null;
            
            if( item instanceof MonitoredCounter )
                e = new Element( "counter" );
            else if( item instanceof MonitoredTimer )
                e = new Element( "timer" );
            else if( item instanceof MonitoredProperty )
                e = new Element( "property" );
            else
                e = new Element( "item" );
            
            e.setAttribute( "name", String.valueOf(item.getName()) );
            
            Object v = item.getValue();
            if( v instanceof Element )
                e.addContent( (Element)v );
            else if( v instanceof String )
                e.addContent( (String)v );
            else
                e.addContent( String.valueOf(v) );
            
            root.addContent( e );
        }
        return root;
    }
    
    /**
     ** Reset all items of this monitor.
     **
     **/
    public void resetAllItems() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "resetAllItems" );
        
        Iterator it = items.values ().iterator();
        
        while (it.hasNext()) {
            MonitoredItem item = (MonitoredItem)it.next();
            item.reset ();
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "resetAllItems" );
    }
    
    /**
     ** Reset the specified monitored item.
     **
     ** @param      name of the item
     **
     **/
    public void resetItem (String name) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "resetItem", new Object[] {name} );
        
        MonitoredItem item = getItem (name);
        item.reset();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "resetItem" );
    }
    
    /**
     ** Enable/disable all items of this monitor.
     **
     ** @param      enabled if true, all items are enabled
     **
     **/
    public void setEnabled (boolean enabled) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setEnabled", new Object[] {new Boolean(enabled)} );
        
        this.enabled = enabled;
        Iterator it = items.values ().iterator();
        
        while (it.hasNext()) {
            MonitoredItem item = (MonitoredItem)it.next();
            item.setEnabled (enabled);
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setEnabled" );
    }
    
    /**
     ** Enable/disable the specified item.
     **
     ** @param      enabled if true, the item is enabled
     ** @param      name the items's name
     **
     **/
    public void setEnabled (boolean enabled, String name) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setEnabled",
                            new Object[] {new Boolean(enabled),name} );
        
        MonitoredItem item = getItem (name);
        item.setEnabled (enabled);
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setEnabled" );
    }
    
    private MonitoredItem getItem (String name) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getItem", new Object[] {name} );
        
        MonitoredItem item = (MonitoredItem)items.get (name);
        if (item == null)
            throw new RuntimeException ("Item " + name + " not registered");
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getItem", item );
        return item;
    }
    
    /**
     * Deregister a monitorable.
     *
     * @param  monitored   the monitorable to be deregistered
     *
     */
    public static void deregister (IMonitorable monitored) {
        String monUri = uriFor (monitored, false);
        allMonitored.remove (monUri);
    }
    
    /**
     ** Returns the string representation of this object.
     ** @return string representation of this object.
     **/
    public String toString() {
        return "Monitor_on_"+monitored.getMonName();
    }
}
