/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/common/XContentCache.java,v 1.3 2004/07/30 06:51:52 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:51:52 $
 *
 * ====================================================================
 *
 * 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.common;

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.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import org.apache.slide.store.tamino.store.monitoring.AbstractMonitorable;
import org.apache.slide.store.tamino.store.monitoring.Monitor;
import org.apache.slide.store.tamino.store.monitoring.MonitoredCounter;
import org.apache.slide.store.tamino.store.monitoring.MonitoredRatio;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XException;
import org.apache.slide.util.cache.LRUSoftCache;
import org.jdom.Element;


/**
 * The content cache is a sub-component of the content handler and supports
 * caching of small content for optimization reasons. XContentCache
 * avoids out-of-memory exceptions by using techniques such as soft
 * references and LRU queues. Within the cache, content instances are
 * uniquely identified by a CONTENT_ID.
 *
 * @author    peter.nevermann@softwareag.com
 *
 * @version   $Revision: 1.3 $
 *
 * @see       IContent
 */
public class XContentCache extends AbstractMonitorable implements IContentCache {
    
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);

    /** Constants */
    private final static int    QUEUE_LENGTH        = 100;
    private final static String CACHE_SIZE          = "Cache size [#]";
    private final static String QUEUE_LENGTH_PROP   = "LRU queue length [#]";
    private final static String CACHE_ENTRIES_SHORT = "Cache keys";
    private final static String ENTRY_LIST          = "entrylist";
    private final static String KEY_LIST            = "keylist";
    private final static String CONTENT_ID          = "contentid";
    private final static String GET_COUNTER         = "Get [#]";
    private final static String GET_FAIL_COUNTER    = "Get failed [#]";
    private final static String GET_HIT_RATE        = "Hit rate for Get";
    
    // monitoring counters and timers...
    private MonitoredCounter getCalled = null;
    private MonitoredCounter getReturnedNull = null;
    
    private Map container;
    
    /** Maximum entry size in the content cache */
    private long contentCacheMaxEntrySize = 0;
    
    /**
     * Default constructor.
     */
    XContentCache( long contentCacheMaxEntrySize ) {
        // use synchronized map
        this.container = Collections.synchronizedMap(
                  new LRUSoftCache(QUEUE_LENGTH, INITIAL_CAPACITY) );
        this.contentCacheMaxEntrySize = contentCacheMaxEntrySize;
        
//      System.out.println("@@@@@ Content caching enabled: "+(contentCacheMaxEntrySize>0));
        
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>" );
            
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }
    
    /**
     * Initializes this component.
     */
    public void initialize() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "initialize" );
                
        // monitoring variables
        this.monName = "ContentCache";
        // this.monParent = ...;  already set from parent side (ContentHandler) !!
        // this.monChildren.add(...); no children
        this.monitor = Monitor.getMonitor( this ); // this comes last!!
        
        // monitoring items
        monitor.registerProperty( CACHE_SIZE );
        monitor.registerProperty( GET_HIT_RATE );
        monitor.registerProperty( QUEUE_LENGTH_PROP );
        monitor.registerProperty( CACHE_ENTRIES_SHORT );
        getCalled = monitor.getCounter( GET_COUNTER );
        getReturnedNull = monitor.getCounter( GET_FAIL_COUNTER );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "initialize" );
    }
    
    /**
     * Get the objects identified by given CONTENT_ID.
     *
     * @pre        contentId != null
     * @post
     *
     * @param      contentId   the CONTENT_ID
     *
     * @return     the object identified by contentId; null, if not found.
     */
    public IContent get( String contentId ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "get", new Object[]{contentId} );

        getCalled.increment();
        IContent result = (IContent) container.get( contentId );
        if( result == null )
            getReturnedNull.increment();

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "get", result );
        return result;
    }
    
    /**
     * Get all objects contained in cache.
     *
     * @pre
     * @post
     *
     * @return     collection of all objects contained in cache;
     *             empty collection if none.
     */
    public Collection getAll() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getAll" );

        Collection result = container.values();

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getAll", result );
        return result;
    }
    
    /**
     * Check whether the objects identified by given content ID is contained in cache.
     *
     * @pre        contentId != null
     * @post
     *
     * @param      contentId   the content ID
     *
     * @return     true, if object identified by contentId is contained;
     *             false, otherwise.
     */
    public boolean contains( String contentId ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "contains", new Object[]{contentId} );
            
        boolean result = container.containsKey( contentId );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "contains", new Boolean(result) );
        return result;
    }
    
    /**
     * Add the given object to cache. If the cache previously contained
     * an entry for the content ID in question, this will be displaced.
     *
     * @pre        d != null
     * @pre        d.getUri() != null
     * @post       d == get( d.getUri() )
     *
     * @param      d   the object
     */
    public void add( IContent d ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "add", new Object[]{d} );
        
        long l = d.getLength();
        String k = d.getContentId();
        
//      System.out.println("@@@@@ k="+k+", l="+l+", contentCacheMaxEntrySize="+contentCacheMaxEntrySize);
        if( 0 < l && l <= contentCacheMaxEntrySize ) {
            byte[] cb = d.bufferContent();
            
            if( l != cb.length ) {
                // oh oh !!
                (new XException("Length incompatibility: l="+l+"; cb.length="+cb.length)).printStackTrace();
            }
            
            IContent d1 = new XContent( cb );
            d1.setLength( l );
            d1.setContentId( k );
            container.put( k, d1 );
        }
        else {
            container.remove( k );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "add" );
    }
    
    /**
     * Add all objects in the given collection to cache. If the cache previously
     * contained entries for some of the content IDs in question, these will be displaced.
     *
     * @pre        pre of add for each d in dl
     * @post       post of add for each d in dl
     *
     * @param      dl   the collection of objects
     */
    public void addAll( Collection dl ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "addAll", new Object[]{dl} );
            
        Iterator i = dl.iterator();
        while( i.hasNext() ) {
            add( (IContent) i.next() );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "addAll" );
    }
    
    /**
     * Remove the objects identified by given content ID.
     *
     * @pre        contentId != null
     * @post       get( contentId ) == null
     *
     * @param      contentId   the content ID
     *
     * @return     the (removed) object identified by contentId; null, if not found.
     */
    public IContent remove( String contentId ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "remove", new Object[]{contentId} );

        IContent result = (IContent)container.remove( contentId );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "remove", result );
        return result;
    }
    
    /**
     * Remove all objects contained in cache.
     *
     * @pre
     * @post       isEmpty() == true
     */
    public void removeAll() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeAll" );
            
        container.clear();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeAll" );
    }
    
    //-------------------------------------------------------------
    // IMonitorable interface
    // ------------------------------------------------------------
            
    /**
     ** Get the value of a registered property.
     ** @param name property name
     ** @return the value of the indicated property as String
     **
     **/
    public Object getMonProperty( String name ) {
        Object result = null;

        if( CACHE_SIZE.equals(name) ) {
            result = String.valueOf(container.size());
        }
        else if( CACHE_ENTRIES_SHORT.equals(name) ) {
            Iterator i = container.keySet().iterator();
            Element klistElm = new Element( KEY_LIST );
            while( i.hasNext() ) {
                Element k = new Element( CONTENT_ID );
                k.addContent( (String)i.next() );
                klistElm.addContent( k );
            }
            result = klistElm;
        }
        else if( GET_HIT_RATE.equals(name) ) {
            // asPecentage=true, complemented=true
            result = new MonitoredRatio( getCalled, getReturnedNull, true, true );
        }
        else if( QUEUE_LENGTH_PROP.equals(name) ) {
            result = String.valueOf( QUEUE_LENGTH );
        }
            
        return result;
    }
}
