/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you 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 the following location:
 *
 *   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.apereo.portal.utils.cache;

import java.io.Serializable;
import java.util.Map;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.ObjectExistsException;

import org.apache.commons.collections.map.ReferenceMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.utils.threading.MapCachingDoubleCheckedCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * CacheFactory impl that provides Map instances that wrap a configured {@link CacheProviderFacade}. This uses the
 * {@link MapCacheProvider} to perform the wrapping. Refer to that class for which operations on the {@link Map}
 * interface are supported.
 * 
 * @author Eric Dalquist
 * @version $Revision$
 */
@Service("cacheFactory")
public class CacheProviderFactory implements CacheFactory {
    protected final Log logger = LogFactory.getLog(this.getClass());

    // Stores caches that are created with a soft reference to the cache Map to avoid re-creating wrapper objects without need
    private final MapCacheCreator mapCacheCreator = new MapCacheCreator();

    private CacheManager cacheManager;
    
    /**
     * @param cacheManager the cacheManager to set
     */
    @Autowired
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.utils.cache.CacheFactory#getCache()
     */
    public <K extends Serializable, V> Map<K, V> getCache() {
        return this.getCache(DEFAULT);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.utils.cache.CacheFactory#getCache(java.lang.String)
     */
    @SuppressWarnings("unchecked")
    public <K extends Serializable, V> Map<K, V> getCache(String cacheName) throws IllegalArgumentException {
        return (Map<K, V>) this.mapCacheCreator.get(cacheName);
    }

    private class MapCacheCreator extends MapCachingDoubleCheckedCreator<String, Map<?, ?>> {
        @SuppressWarnings("unchecked")
        public MapCacheCreator() {
            super(new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT));
        }
        
        /* (non-Javadoc)
         * @see org.apereo.portal.utils.threading.MapCachingDoubleCheckedCreator#getKey(java.lang.Object[])
         */
        @Override
        protected String getKey(Object... args) {
            return (String) args[0];
        }

        
        /* (non-Javadoc)
         * @see org.apereo.portal.utils.threading.MapCachingDoubleCheckedCreator#createInternal(java.lang.Object, java.lang.Object[])
         */
        @SuppressWarnings("unchecked")
        @Override
        protected Map<?, ?> createInternal(String cacheName, Object... args) {
            final Ehcache cache;
            if (cacheManager.cacheExists(cacheName)) {
                cache = cacheManager.getCache(cacheName);
                
                if (logger.isDebugEnabled()) {
                    logger.debug("Using existing EhCache for '" + cacheName + "'");
                }
            }
            else {
                try {
                    cacheManager.addCache(cacheName);
                }
                catch (ObjectExistsException oee) {
                    //Ignore, some other thread created the cache while we were trying to do the same thing
                }
                cache = cacheManager.getCache(cacheName);
                
                if (logger.isWarnEnabled()) {
                    logger.warn("Created new default EhCache for '" + cacheName + "'");
                }
            }

            return new MapCacheProvider(cache);
        }
    }
}
