/*
 * Copyright (C) 2005-2011 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco 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 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Alfresco. If not, see .
 */
package org.alfresco.repo.content.caching;
import java.io.File;
import net.sf.ehcache.CacheManager;
import org.alfresco.repo.cache.EhCacheAdapter;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.repo.content.filestore.FileContentWriter;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentStreamListener;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.TempFileProvider;
/**
 * The one and only implementation of the ContentCache class.
 * 
 * Binary content data itself is stored on disk in temporary files managed
 * by Alfresco (see {@link org.alfresco.util.TempFileProvider}).
 * 
 * The in-memory lookup table is provided by Ehcache.
 * 
 * @author Matt Ward
 */
public class ContentCacheImpl implements ContentCache
{
    private static final String CACHE_DIR = "caching_cs";
    private static final String TMP_FILE_EXTENSION = ".tmp";
    private static final String EHCACHE_NAME = "contentStoreCache";
    private static final long T24_HOURS = 86400;
    private final File cacheRoot = TempFileProvider.getLongLifeTempDir(CACHE_DIR);
    private EhCacheAdapter memoryStore;
    
    public ContentCacheImpl()
    {
        // TODO: Configuration to be moved out into Spring
        memoryStore = new EhCacheAdapter();
        configureMemoryStore();   
    }
    private void configureMemoryStore()
    {
        CacheManager manager = CacheManager.getInstance();
        
        // Create the cache if it hasn't already been created.
        if (!manager.cacheExists(EHCACHE_NAME))
        {
            net.sf.ehcache.Cache memoryOnlyCache = 
                new net.sf.ehcache.Cache(EHCACHE_NAME, 10000, false, false, T24_HOURS, T24_HOURS);
            
            manager.addCache(memoryOnlyCache);
        }
        
        memoryStore.setCache(manager.getCache(EHCACHE_NAME));
    }
    
    
    @Override
    public boolean contains(String contentUrl)
    {
        return memoryStore.contains(contentUrl);
    }
    
    
    @Override
    public ContentReader getReader(String contentUrl)
    {
        if (memoryStore.contains(contentUrl))
        {
            String path = memoryStore.get(contentUrl);
            return new FileContentReader(new File(path), contentUrl);
        }
        
        throw new CacheMissException(contentUrl);
    }
    
    
    @Override
    public boolean put(String contentUrl, ContentReader source)
    {
        File cacheFile = createCacheFile(contentUrl);
        
        // Copy the content from the source into a cache file
        if (source.getSize() > 0L)
        {
            source.getContent(cacheFile);
            // Add a record of the cached file to the in-memory cache.
            memoryStore.put(contentUrl, cacheFile.getAbsolutePath());
            return true;
        }
        return false;
    }
    
    /**
     * Create a File object and makes any intermediate directories in the path.
     * 
     * @param contentUrl
     * @return File
     */
    private File createCacheFile(String contentUrl)
    {
        File path = new File(cacheRoot, pathFromUrl(contentUrl));
        File parentDir = path.getParentFile();
        
        parentDir.mkdirs();
        
        File cacheFile = TempFileProvider.createTempFile(path.getName(), TMP_FILE_EXTENSION, parentDir);
        return cacheFile;
    }
    
    
    
    
    /*
     * @see org.alfresco.repo.content.caching.ContentCache#remove(java.lang.String)
     */
    @Override
    public void remove(String contentUrl)
    {
        // Remove from the in-memory cache, but not from disk. Let the clean-up process do this asynchronously.
        memoryStore.remove(contentUrl);
    }
    
    /*
     * @see org.alfresco.repo.content.caching.ContentCache#getWriter(org.alfresco.repo.content.ContentContext)
     */
    @Override
    public ContentWriter getWriter(final String url)
    {
        // Get a writer to a cache file.
        final File cacheFile = createCacheFile(url);
        ContentWriter writer = new FileContentWriter(cacheFile);
        
        // Attach a listener to populate the in-memory store when done writing.
        writer.addListener(new ContentStreamListener()
        {
            @Override
            public void contentStreamClosed() throws ContentIOException
            {
                memoryStore.put(url, cacheFile.getAbsolutePath());
            }
        });
        
        return writer;
    }
    
    /**
     * Converts a content URL to a relative path name where the protocol will
     * be the name of a subdirectory. For example:
     * 
     * store://2011/8/5/15/4/386595e0-3b52-4d5c-a32d-df9d0b9fd56e.bin
     * 
     * will become:
     * 
     * store/2011/8/5/15/4/386595e0-3b52-4d5c-a32d-df9d0b9fd56e.bin
     * 
     * @param contentUrl
     * @return String representation of relative path to file.
     */
    private String pathFromUrl(String contentUrl)
    {
        return contentUrl.replaceFirst("://", "/");
    }
}