/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/common/XDescriptorsCache.java,v 1.4 2005/02/23 16:36:54 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2005/02/23 16:36:54 $
 *
 * ====================================================================
 *
 * 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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.slide.store.tamino.jdomobjects.XTLock;
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.XAssertionFailed;
import org.apache.slide.util.cache.LRUSoftCache;
import org.jdom.Element;


/**
 * Cache for XDescriptors. A sub-component of the descriptors handler.
 * Avoids out-of-memory exceptions by using techniques such as soft
 * references and LRU queues. Within the cache, descriptors objects are
 * uniquely identified by a unique URI.
 *
 * @author    peter.nevermann@softwareag.com
 *
 * @version   $Revision: 1.4 $
 *
 * @see       IDescriptors
 */
public class XDescriptorsCache extends AbstractMonitorable {
    /**
     * The initial capacity for the global cache
     */
    private static final int INITIAL_CAPACITY = 11;

    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 CACHE_ENTRIES       = "Cache entries";
    private final static String TLOCKED_ENTRIES     = "T-Locked entries";
    private final static String ENTRY_LIST          = "entrylist";
    private final static String KEY_LIST            = "keylist";
    private final static String TLOCKED_LIST        = "tlockedlist";
    private final static String URI                 = "uri";
    private final static String TLOCKED             = "tlocked";
    private final static String TLOCKED_BY          = "tlockedby";
    private final static String TLOCKED_AT          = "tlockedat";
    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 final Object sync;

    private XTLockSettings tlockSettings;

    /** Maps uuris to TLocks with SoftReferences */
    private final Map container;

    /** Thread specific data. */
    private XThreadMap threadMap;

    /**
     * Default constructor.
     */
    public XDescriptorsCache(Object sync, XTLockSettings tlockSettings, Object onOpenTaSyncPoint) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>", new Object[]{tlockSettings} );

        this.sync = sync;
        this.tlockSettings = tlockSettings;
        this.container = new LRUSoftCache(QUEUE_LENGTH, INITIAL_CAPACITY);
        this.threadMap = new XThreadMap(onOpenTaSyncPoint);

        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 = "GlobalCache";
        // this.monParent = ...;  already set from parent side (DescriptorsHandler) !!
        // 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 );
        monitor.registerProperty( TLOCKED_ENTRIES );
        monitor.registerProperty( CACHE_ENTRIES );
        getCalled = monitor.getCounter( GET_COUNTER );
        getReturnedNull = monitor.getCounter( GET_FAIL_COUNTER );

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "initialize" );
    }

    //--

    /**
     * Get the descriptors objects identified by given URI.
     * Overwrites inherited in order to add monitoring.
     *
     * @pre        uri != null
     * @post
     *
     * @param      uri   the URI
     *
     * @return     the descriptors object identified by uri; null, if not found.
     */
    public IDescriptors checkout( int lockType, String uri, String uuri ) throws XTLockedException {
        XTLock lock;

        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "checkout", new Object[]{uuri} );

        getCalled.increment();
        lock = lookup(uuri);
        if( lock == null ) {
            getReturnedNull.increment();
        } else {
            int oldLockType = lock.acquire(lockType);
            if (lookup(uuri) != lock) {
                // The current thread might have been preempted between (the first) lookup() and
                // acquire(). Thus, it's possible that some other thread removed the descriptor
                // from cache. In this case (i.e. the second lookup() != lock), we throw a
                // XTLockedException to indicate a conflict between these threads.
                // (This problem first showed up in the delete409File.xml test)

                // TODO: do not throw an exception, but return an empty descriptor
                throw new XTLockedException("deleted");
            }
            if (oldLockType == XTLock.NO_LOCK) {
                if (lockType != XTLock.NO_LOCK) {
                    threadMap.add(lock, null);
                }
            }
            if (lockType != XTLock.NO_LOCK) {
                if (threadMap.getUriCache().get(uri) == null) {
                    threadMap.getUriCache().put(uri, uuri);
                }
            }
        }
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "get", lock );

        validate();
        return (lock == null)? null : lock.getDescriptors();
    }

    /** for internal use only */
    protected XTLock lookup(String uuri) {
        return (XTLock) container.get( uuri );
    }
    /**
     ** Add the given descriptors object to cache. Throws a RuntimeException if the cache previously
     ** contained an entry for the URI in question, this will be replaced.
     **
     ** @pre        desc != null
     ** @pre        desc.getUri() != null
     ** @pre        checkout(desc.getUuri()) == null
     ** @post       desc == checkout( desc.getUri() )
     **
     ** @param      desc   the descriptors object
     **/
    public IDescriptors add( int lockType, String uri, IDescriptors desc) {
        XTLock lock;

        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "add", new Object[]{desc} );

        lock = new XTLock(sync, tlockSettings, desc);
        try {
            if (lock.acquire(lockType) != XTLock.NO_LOCK) {
                // we've just created the TLock object - nobody else can see/look ip
                throw new XAssertionFailed();
            }
        } catch (XTLockedException e) {
            // we've just created the TLock object - nobody else can see/look ip
            throw new XAssertionFailed(e);
        }
        if (lockType != XTLock.NO_LOCK) {
            threadMap.add( lock, uri );
        }
        if (container.put( desc.getUuri(), lock ) != null) {
            throw new XAssertionFailed();
        }

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "add" );

        validate();
        return lock.getDescriptors();
    }

    public void checkinAll(boolean commit) {
        List lst;
        Iterator i;
        XTLock lock;

        lst = threadMap.getLocks();
        if (lst != null) {
            i = lst.iterator();
            while( i.hasNext() ) {
                lock = (XTLock) i.next();
                lock.release(commit);
                IDescriptors d = lock.getDescriptors();

                if( d.isDeleted() ) {
                    container.remove(d.getUuri());
                } else {
                    d.commitEvent();
                }
                if( logger.isLoggable(Level.FINE) )
                    logger.fine(CLASSNAME, "commit", "Committed: "+d );
            }
            threadMap.remove();
        }
        validate();
    }

    /**
     * Get a report of locked resources
     */
    public String reportLockedResources() {
        return threadMap.reportLockedResources();
    }

    public boolean isModified() {
       return !threadMap.isEmpty();
    }

    public boolean isModifying() {
        return threadMap.getUriCache() != null;
    }

    public List getModifiedDescriptors() {
        List result;
        List lst;
        XTLock lock;
        Iterator iter;

        // TODO: expensive
        result = new ArrayList();
        lst = threadMap.getLocks();
        if (lst != null) {
            iter = lst.iterator();
            while (iter.hasNext()) {
                lock = (XTLock) iter.next();
                result.add(lock.getDescriptors());
            }
        }
        return result;
    }

    public IDescriptors checkoutUriCache(int lockType, String uri) throws XTLockedException {
        String uuri;

        uuri = threadMap.lookupUri(uri);
        if (uuri == null) {
            return null;
        }
        return checkout(lockType, uri, uuri);
    }

    /**
     * Remove all descriptors objects contained in cache.
     *
     * @pre
     * @post       isEmpty() == true
     */
    public void clear() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeAll" );

        container.clear();
        threadMap.clear();

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeAll" );
    }

    //-------------------------------------------------------------
    // IMonitorable interface
    // ------------------------------------------------------------

    /**
     ** Get the value of a registered property.
     ** @param name the property name
     ** @return the value of the indicated property as String
     **
     **/
    public Object getMonProperty( String name ) {
        Object result;

        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( URI );
                k.addContent( (String)i.next() );
                klistElm.addContent( k );
            }
            result = klistElm;
        }
        else if( TLOCKED_ENTRIES.equals(name) ) {
            Iterator i = container.values().iterator();
            Element tlistElm = new Element( TLOCKED_LIST );
            while( i.hasNext() ) {
                XTLock lock = (XTLock)i.next();
                Thread activeWriter = lock.getActiveWriter();
                if( activeWriter == null )
                    continue;
                IDescriptors d = lock.getDescriptors();
                Element t = new Element( TLOCKED );
                Element tb = new Element( TLOCKED_BY );
                t.setAttribute( URI, d.getUuri() );
                tb.addContent( String.valueOf(activeWriter) );
                t.addContent( tb );
                tlistElm.addContent( t );
            }
            result = tlistElm;
        }
        else if( CACHE_ENTRIES.equals(name) ) {
            Iterator i = container.values().iterator();
            Element elistElm = new Element( ENTRY_LIST );
            while( i.hasNext() ) {
                XTLock lock = (XTLock)i.next();
                IDescriptorsDocument descDoc = (IDescriptorsDocument) lock.getDescriptors();

                // WAM What about tsdLanguage???
                Element root = descDoc.toXml().getRootElement();
                root.detach();
                elistElm.addContent (root);
            }
            result = elistElm;
        }
        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 );
        }
        else {
            result = null;
        }

        return result;
    }

    private static final boolean VALIDATE = false;  // TODO

    private void validate() {
        XTLock lock;
        Iterator iter;

        if (VALIDATE) {
            iter = container.values().iterator();
            while (iter.hasNext()) {
                lock = (XTLock) iter.next();
                if (lock.isLocked()) {
                    if (!threadMap.contains(lock)) {
                        throw new XAssertionFailed("locked object not listed in lockedDescriptors");
                    }
                }
            }
            threadMap.validate(this);
        }
    }

    public String toString() {
        return container.toString();
    }
}


