/*
 * SRBBitStore.java
 *
 * Version: $Revision: 1759 $
 *
 * Date: $Date: 2007-04-05 15:26:37 +0000 (Thu, 05 Apr 2007) $
 *
 * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts
 * Institute of Technology.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of the Hewlett-Packard Company nor the name of the
 * Massachusetts Institute of Technology nor the names of their
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package org.dspace.storage.bitstore.impl;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import edu.sdsc.grid.io.GeneralFile;
import edu.sdsc.grid.io.srb.SRBAccount;
import edu.sdsc.grid.io.srb.SRBFile;
import edu.sdsc.grid.io.srb.SRBFileInputStream;
import edu.sdsc.grid.io.srb.SRBFileOutputStream;
import edu.sdsc.grid.io.srb.SRBFileSystem;

import org.dspace.core.ConfigurationManager;
import org.dspace.core.Utils;
import org.dspace.storage.bitstore.BitStore;

/**
 * Implements an asset store using the Storage Resource Broker
 * 
 * @author David Little, Richard Rodgers
 */

public class SRBBitStore implements BitStore
{
    /** log4j log */
    private static Logger log = Logger.getLogger(SRBBitStore.class);
    
    // You should not change these settings if you have data in the
    // asset store, as the BitstreamStorageManager will be unable
    // to find your existing data.
    private static final int digitsPerLevel = 2;

    private static final int directoryLevels = 3;
    
    // Checksum algorithm
    private static final String CSA = "MD5";
    
	private SRBFile baseDir = null;
	
	public SRBBitStore()
	{
	}
	
	public void init(String params)
	{
		SRBAccount account = new SRBAccount(
				   ConfigurationManager.getProperty("srb.host"),
				   ConfigurationManager.getIntProperty("srb.port"),
				   ConfigurationManager.getProperty("srb.username"),
				   ConfigurationManager.getProperty("srb.password"),
				   ConfigurationManager.getProperty("srb.homedirectory"),
				   ConfigurationManager.getProperty("srb.mdasdomainname"),
				   ConfigurationManager.getProperty("srb.defaultstorageresource"),
				   ConfigurationManager.getProperty("srb.mcatzone"));
		SRBFileSystem srbFileSystem = null;
		try
		{
			srbFileSystem = new SRBFileSystem((SRBAccount)account);
		}
		catch (NullPointerException e)
		{
			log.error("No SRBAccount for assetstore ");
		}
		catch (IOException e)
		{
			log.error("Problem getting SRBFileSystem for assetstore" );
		}
		if (srbFileSystem == null)
		{
			log.error("SRB FileSystem is null for assetstore ");
		}
		String sSRBAssetstore = ConfigurationManager.getProperty("srb.parentdir");
		if (sSRBAssetstore == null)
		{
			log.error("srb.parentdir is undefined for assetstore ");
		}
		baseDir = new SRBFile(srbFileSystem, sSRBAssetstore);
	}
	
	public String generateId()
	{
        return Utils.generateKey();
	}

	public InputStream get(String id) throws IOException
	{
		return new SRBFileInputStream(getFile(id));
	}
	
    /**
     * Store a stream of bits.
     * 
     * <p>
     * If this method returns successfully, the bits have been stored.
     * If an exception is thrown, the bits have not been stored.
     * </p>
     * 
     * @param context
     *            The current context
     * @param in
     *            The stream of bits to store
     * @exception IOException
     *             If a problem occurs while storing the bits
     * 
     * @return Map containing technical metadata (size, checksum, etc)
     */
	public Map put(InputStream in, String id) throws IOException
	{
		SRBFile file = getFile(id);
		
		// Make the parent dirs if necessary
		GeneralFile parent = file.getParentFile();
        if (!parent.exists())
        {
            parent.mkdirs();
        }
        //Create the corresponding file and open it
        file.createNewFile();
        
		SRBFileOutputStream fos = new SRBFileOutputStream(file);

		// Read through a digest input stream that will work out the MD5
        DigestInputStream dis = null;

        try
        {
            dis = new DigestInputStream(in, MessageDigest.getInstance(CSA));
        }
        // Should never happen
        catch (NoSuchAlgorithmException nsae)
        {
            log.warn("Caught NoSuchAlgorithmException", nsae);
        }

        Utils.bufferedCopy(dis, fos);
        fos.close();
        in.close();
        
        Map attrs = new HashMap();
        attrs.put( "size_bytes", String.valueOf(file.length()));
        attrs.put( "checksum", Utils.toHex(dis.getMessageDigest().digest()));
        attrs.put( "checksum_algorithm", CSA);
        return attrs;
	}
	
    /**
     * Obtain technical metadata about an asset in the asset store.
     * 
     * @param context
     *            The current context
     * @param id
     *            The ID of the asset to describe
     * @param attrs
     *            A Map whose keys consist of desired metadata fields
     * 
     * @exception IOException
     *            If a problem occurs while obtaining metadata
     * @return attrs
     *            A Map with key/value pairs of desired metadata
     */
	public Map about(String id, Map attrs) throws IOException
	{
		// pretty worthless hack - get MD5 on just the filename (!) for SRB file
		SRBFile file = getFile(id);
		if (file != null && file.exists())
		{
		    if (attrs.containsKey("size_bytes"))
		    {
		        attrs.put("size_bytes", String.valueOf(file.length()));
		    }
		    if (attrs.containsKey("checksum"))
		    {
		        // get MD5 on just the filename (!) for SRB file
		        int iLastSlash = id.lastIndexOf('/');
		        String filename = id.substring(iLastSlash + 1);
		        MessageDigest md = null;
		        try 
		        {
			        md = MessageDigest.getInstance(CSA);
		        } 
		        catch (NoSuchAlgorithmException e) 
		        {
			        log.error("Caught NoSuchAlgorithmException", e);
			        throw new IOException("Invalid checksum algorithm");
		        }
			    attrs.put("checksum", Utils.toHex(md.digest(filename.getBytes())));
			    attrs.put("checksum_algorithm", CSA);
		    }
		    return attrs;
		}
		return null;
	}
	
	public void remove(String id) throws IOException
	{
		//return true;
	}
	
    ////////////////////////////////////////
    // Internal methods
    ////////////////////////////////////////
	
    /**
     * Delete empty parent directories.
     * 
     * @param file
     *            The file with parent directories to delete
     */
    private synchronized static void deleteParents(SRBFile file)
    {
        if (file == null)
        {
            return;
        }
 
		GeneralFile tmp = file;
        for (int i = 0; i < directoryLevels; i++)
        {
			GeneralFile directory = tmp.getParentFile();
			GeneralFile[] files = directory.listFiles();

            // Only delete empty directories
            if (files.length != 0)
            {
                break;
            }

            directory.delete();
            tmp = directory;
        }
    }
	
	private SRBFile getFile(String id) throws IOException
	{
		String fileName = getIntermediatePath(id) + id;
		if (log.isDebugEnabled())
		{
			log.debug("SRB filename for " + id + " is "
					  + (baseDir.toString() + fileName));
		}
		return new SRBFile(baseDir, fileName);
	}
	
	/**
	 * Return the path derived from the internal_id. This method
	 * splits the id into groups which become subdirectories.
	 *
	 * @param iInternalId
	 *            The internal_id
	 * @return The path based on the id without leading or trailing separators
	 */
	private static String getIntermediatePath(String id)
	{
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < directoryLevels; i++) {
			int digits = i * digitsPerLevel;
			if (i > 0)
			{
				buf.append(File.separator);
			}
			buf.append(id.substring(digits, digits	+ digitsPerLevel));
		}
		buf.append(File.separator);
		return buf.toString();
	}
}
