/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.utils.cache;

import com.opensymphony.oscache.base.Config;
import com.opensymphony.oscache.base.InitializationException;
import com.opensymphony.oscache.base.NeedsRefreshException;
import com.opensymphony.oscache.base.events.CacheEntryEvent;
import com.opensymphony.oscache.base.events.CacheEntryEventListener;
import com.opensymphony.oscache.base.events.CacheGroupEvent;
import com.opensymphony.oscache.base.events.CachePatternEvent;
import com.opensymphony.oscache.base.events.CachewideEvent;
import com.opensymphony.oscache.plugins.clustersupport.AbstractBroadcastingListener;
import com.opensymphony.oscache.plugins.clustersupport.JavaGroupsBroadcastingListener;
import com.queplix.core.error.GenericSystemException;
import com.queplix.core.utils.log.AbstractLogger;
import com.queplix.core.utils.log.Log;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;

/**
 * <p>OS cache implementation of com.queplix.core.utils.cache.Cache</p>
 *
 * @author [ALB] Baranov Andrey
 * @version 1.0
 */

public class OSCache
        extends AbstractCache {

    // OSCacheWrapper.
    protected OSCacheWrapper cacheW;

    // AbstractBroadcastingListener
    protected AbstractBroadcastingListener listener;

    // Logger
    protected AbstractLogger logger = Log.getLog(getClass());

    //
    // Constructor.
    //

    public OSCache() {
        // Create OSCache wrapper
        cacheW = new OSCacheWrapper(true, false, false);

        // Create listener
        listener = new JavaGroupsBroadcastingListener();

        // Init listener
        /** @todo Move settings to config file */
        Config config = new Config();
        config.set("cache.cluster.multicast.ip", "231.12.21.132");

        try {
            listener.initialize(cacheW, config);
        } catch (InitializationException ex) {
            logger.ERROR(ex);
            throw new GenericSystemException(ex);
        }

        // Register listener
        registerListener(listener);
    }

    /**
     * Get value from the cache
     *
     * @param key OSCacheID
     * @return value
     */
    public Object get(OSCacheID key) {
        try {
            return cacheW.getFromCache(key.toOSCacheKey());
        } catch (NeedsRefreshException ex) {
            logger.ERROR(ex);
            return null;
        }
    }

    /*
     * No javadoc
     * @see Cache#get
     */
    public Object get(Object key) {
        try {
            return cacheW.getFromCache(toKey(key));
        } catch (NeedsRefreshException ex) {
            logger.ERROR(ex);
            return null;
        }
    }

    /*
     * No javadoc
     * @see Cache#headMap
     */
    public SortedMap headMap(Comparable toKey) {
        throw new UnsupportedOperationException();
    }

    /*
     * No javadoc
     * @see Cache#subMap
     */
    public SortedMap subMap(Comparable fromKey, Comparable toKey) {
        throw new UnsupportedOperationException();
    }

    /*
     * No javadoc
     * @see Cache#tailMap
     */
    public SortedMap tailMap(Comparable fromKey) {
        throw new UnsupportedOperationException();
    }

    /*
     * No javadoc
     * @see Cache#toMap
     */
    public SortedMap toMap() {
        throw new UnsupportedOperationException();
    }

    /**
     * Checks whether a value with the key specified exists in the cache
     *
     * @param key OSCacheID
     * @return boolean
     */
    public boolean containsKey(OSCacheID key) {
        return (get(key) != null);
    }

    /*
     * No javadoc
     * @see Cache#containsKey
     */
    public boolean containsKey(Object key) {
        return (get(key) != null);
    }

    /*
     * No javadoc
     * @see Cache#keys
     */
    public Collection keys() {
        throw new UnsupportedOperationException();
    }

    /*
     * No javadoc
     * @see Cache#getLastKey
     */
    public Object getLastKey() {
        throw new UnsupportedOperationException();
    }

    /*
     * No javadoc
     * @see Cache#getFirstKey
     */
    public Object getFirstKey() {
        throw new UnsupportedOperationException();
    }

    /*
     * No javadoc
     * @see Cache#values
     */
    public Collection values() {
        throw new UnsupportedOperationException();
    }

    /**
     * Put value to the cache
     *
     * @param key OSCacheID
     * @param o   value
     */
    public void put(OSCacheID key, Object o) {
        if(!isOpen()) {
            throw new IllegalStateException("Cache closed");
        }

        boolean updated = false;
        try {
            cacheW.putInCache(key.toOSCacheKey(), o);
            updated = true;
        } finally {
            if(!updated) {
                cacheW.cancelUpdate(key.toOSCacheKey());
            }
        }
    }

    /*
     * No javadoc
     * @see Cache#put(Object, Object)
     */
    public void put(Object key, Object o) {
        if(!isOpen()) {
            throw new IllegalStateException("Cache closed");
        }

        boolean updated = false;
        try {
            cacheW.putInCache(toKey(key), o);
            updated = true;
        } finally {
            if(!updated) {
                cacheW.cancelUpdate(toKey(key));
            }
        }
    }

    /*
     * No javadoc
     * @see Cache#put(Map)
     */
    public void put(Map _map) {
        if(!isOpen()) {
            throw new IllegalStateException("Cache closed");
        }
        for(Iterator it = _map.keySet().iterator(); it.hasNext();) {
            Object key = it.next();
            put(key, _map.get(key));
        }
    }

    /**
     * Remove value from the cache
     *
     * @param key OSCacheID
     */
    public void remove(OSCacheID key) {
        if(!isOpen()) {
            throw new IllegalStateException("Cache closed");
        }
        cacheW.removeEntry(key.toOSCacheKey());
    }

    /*
     * No javadoc
     * @see Cache#remove
     */
    public void remove(Object key) {
        if(!isOpen()) {
            throw new IllegalStateException("Cache closed");
        }
        cacheW.removeEntry(toKey(key));
    }

    /*
     * No javadoc
     * @see Cache#clear
     */
    public void clear() {
        super.clear();
        cacheW.clear();
    }

    /*
     * No javadoc
     * @see Cache#isEmpty
     */
    public boolean isEmpty() {
        throw new UnsupportedOperationException();
    }

    /*
     * No javadoc
     * @see Object#toString
     */
    public String toString() {
        return "OSCache: open=" + isOpen();
    }

    /**
     * Transform <code>key</code> to OSCache format.
     *
     * @param key Object
     * @return String
     */
    protected String toKey(Object key) {
        if(key == null) {
            return null;
        }

        return (key instanceof OSCacheID) ?
                ((OSCacheID) key).toOSCacheKey():key.toString();
    }

    /**
     * Register new CacheEntryEventListener
     *
     * @param listener CacheEntryEventListener
     */
    protected void registerListener(CacheEntryEventListener listener) {
        cacheW.addCacheEventListener(listener, CacheEntryEventListener.class);
    }

    // ----------------------------------------------------- inner class

    /**
     * OSCache listener stub
     *
     * @author [ALB] Andrey Baranov
     */
    public static class OSCacheEntryEventListener
            implements CacheEntryEventListener {

        public void cacheEntryAdded(CacheEntryEvent cacheEntryEvent) {
            // empty
        }

        public void cacheEntryFlushed(CacheEntryEvent cacheEntryEvent) {
            // empty
        }

        public void cacheEntryRemoved(CacheEntryEvent cacheEntryEvent) {
            // empty
        }

        public void cacheEntryUpdated(CacheEntryEvent cacheEntryEvent) {
            // empty
        }

        public void cacheGroupFlushed(CacheGroupEvent cacheGroupEvent) {
            // empty
        }

        public void cachePatternFlushed(CachePatternEvent cachePatternEvent) {
            // empty
        }

        public void cacheFlushed(CachewideEvent cachewideEvent) {
            // empty
        }

    } //-- end of class

}
