//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2006-2007 Michael Bayne, Pär Winzell
//
// 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
// (at your option) 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

package com.samskivert.jdbc.depot;

import java.io.Serializable;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.distribution.CacheManagerPeerListener;
import net.sf.ehcache.distribution.CacheManagerPeerProvider;
import net.sf.ehcache.distribution.RMIAsynchronousCacheReplicator;

import static com.samskivert.jdbc.depot.Log.log;

/**
 * An implementation of {@link CacheAdapter} for ehcache.
 */
public class EHCacheAdapter
    implements CacheAdapter
{
    public EHCacheAdapter ()
    {
        _cachemgr = CacheManager.getInstance();

        CacheManagerPeerListener listener = _cachemgr.getCachePeerListener();
        CacheManagerPeerProvider provider = _cachemgr.getCachePeerProvider();
        if ((provider != null) != (listener != null)) {
            // we want either both listener and provider, or neither
            log.warning("EHCache misconfigured, distributed mode disabled [listener =" +
                listener + ", provider=" + provider);
            _distributed = false;

        } else {
            _distributed = (listener != null);
        }
    }

    public <T> CacheBin<T> getCache (String id)
    {
        return new EHCacheBin<T>(id);
    }

    /**
     * The main ehcache-bridging class, a {@link CacheBin} interface against {@link Cache}.
     */
    protected class EHCacheBin<T> implements CacheBin<T>
    {
        // from CacheBin
        public CachedValue<T> lookup (Serializable key)
        {
            Element hit = _cache.get(key);
            if (hit == null) {
                return null;
            }

            @SuppressWarnings("unchecked") final T value = (T) hit.getValue();
            return new CachedValue<T>() {
                public T getValue () {
                    return value;
                }
                public String toString () {
                    return String.valueOf(value);
                }
            };
        }

        // from CacheBin
        public void store (Serializable key, T value)
        {
            _cache.put(new Element(key, value));
        }

        // from CacheBin
        public void remove (Serializable key)
        {
            _cache.remove(key);
        }

        // from CacheBin
        public Iterable<Serializable> enumerateKeys ()
        {
            @SuppressWarnings("unchecked") Iterable<Serializable> keys = _cache.getKeys();
            return keys;
        }

        protected EHCacheBin (String id)
        {
            _cache = _cachemgr.getCache(id);
            if (_cache == null) {
                _cache = new Cache(id, 5000, false, false, 600, 60);

                if (_distributed) {
                    // for reasons unknown we have to add this event listener programmatically
                    // rather than include it in the defaultCache portion of the ehcache.xml
                    // configuration file.
                    _cache.getCacheEventNotificationService().registerListener(
                        new RMIAsynchronousCacheReplicator(true, true, true, true, 1000));
                }
                _cachemgr.addCache(_cache);
            }
        }

        protected Cache _cache;
    }

    // from CacheAdapter
    public void shutdown ()
    {
        CacheManager.getInstance().shutdown();
    }

    protected boolean _distributed;
    protected CacheManager _cachemgr;
}

@@ -2478,32 +2478,84 @@
 %0A            %7D%0A%0A
+            Serializable rawValue = hit.getValue();%0A
             @Sup
@@ -2580,16 +2580,32 @@
 hecked%22)
+%0A               
  final T
@@ -2617,29 +2617,64 @@
 e = (T) 
-hit.getValue(
+(rawValue instanceof NullValue ? null : rawValue
 );%0A     
@@ -3073,16 +3073,39 @@
 y, value
+ != null ? value : NULL
 ));%0A    
@@ -3602,285 +3602,677 @@
     
-_cache = new Cache(id, 5000, false, false, 600, 60);%0A%0A                if (_distributed) %7B%0A                    // for reasons unknown we have to add this event listener programmatically%0A                    // rather than include it in the defaultCache portion of the ehcache.xml
+// create the cache programatically with reasonable settings%0A                // TODO: we will eventually need this to be configurable in .properties%0A%0A                _cache = new Cache(id,%0A                    5000,       // keep 5000 elements in RAM%0A                    true,       // overflow the rest to disk%0A                    false,      // don't keep records around eternally%0A                    600,        // but do keep them for 10 minutes after they're created%0A                    60);        // or 1 minute after last access%0A%0A                if (_distributed) %7B%0A                    // a programatically created cache has to have its replicator event listener
 %0A   
@@ -4295,26 +4295,29 @@
  // 
-configuration file
+programatically added
 .%0A  
@@ -4797,11 +4797,639 @@
 achemgr;
+%0A%0A    // this is just for convenience and memory use; we don't rely on pointer equality anywhere%0A    protected static Serializable NULL = new NullValue() %7B%7D;%0A%0A    /** A class to represent an explicitly Serializable concept of null for EHCache. */%0A    protected static class NullValue implements Serializable%0A    %7B%0A        public String toString ()%0A        %7B%0A            return %22%3CEHCache Null%3E%22;%0A        %7D%0A%0A        public boolean equals (Object other)%0A        %7B%0A            return other != null && other.getClass().equals(NullValue.class);%0A        %7D%0A%0A        public int hashCode ()%0A        %7B%0A            return 1;%0A        %7D%0A    %7D
 %0A%7D%0A

Convert nulls to instances of a special NullValue class when we store them, and back again when retrieved. EHCache's replication mechanism flips out if you cache actual nulls (I have no idea why, nulls are obviously serializable). Also fix up some comments -- very soon we are going to have to add better (probably runtime) configurability to the Caches, and probably also write some code to display/collate their statistics.