/*
 * $Id: FileDataStore.java,v 1.11 2006/03/23 11:41:47 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: FileDataStore.java,v $
 * Revision 1.11  2006/03/23 11:41:47  marbobo
 * + Mögliche NPE abgefangen
 *
 * Revision 1.10  2004/11/10 15:02:49  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.9  2003/12/24 10:12:04  bigmac
 * Projektweite Import-Optimierung
 *
 * Revision 1.8  2003/11/29 22:20:36  koppy
 * bugfix
 *
 * Revision 1.7  2003/11/28 17:17:55  koppy
 * Anpassung an VKCInputStream
 *
 * Revision 1.6  2003/11/21 14:47:37  koppy
 * Um die Möglichkeit, "Archivleichen" zu entfernen, erweitert
 *
 * Revision 1.5  2003/05/26 15:50:01  koppy
 * SS um getFile( id:string ) erweitert
 *
 * Revision 1.4  2003/04/22 12:33:03  koppy
 * + getReader( String id )
 *
 * Revision 1.3  2003/03/12 00:34:20  koppy
 * Möglichkeit den StoreKeeper dynamisch zu setzen
 *
 * Revision 1.2  2002/11/12 15:33:02  koppy
 * Behandlung von Streams hinzugefügt
 *
 * Revision 1.1  2002/10/21 14:36:14  bigmac
 * *** empty log message ***
 *
 * Revision 1.2  2002/04/29 14:54:21  koppy
 * Anpassung an modifiziertes DataStore-Interface
 *
 * Revision 1.1  2002/04/05 15:35:20  koppy
 * initial revision
 *
 */

package vkc.server.archive;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;

import vkc.misc.VKCLogManager;
import vkc.util.FileUtils;
import vkc.util.VKCInputStream;

public class FileDataStore extends AbstractDataStore {
	protected File storageDir = null;

	protected StoreKeeper storeKeeper = null;

	public FileDataStore() {
	}

	/**
	 * Konstruktor
	 *
	 * @param storageDirUrl - (file)-URL für den darunterliegenden Speichermechanismus
	 * @author koppy
	 */
	public FileDataStore(String storageDirUrl) throws DataStoreException {
		setStorageUrl(storageDirUrl);
	}

	/**
	 * Konstruktor
	 *
	 * @param storageDirUrl - (file)-URL für den darunterliegenden Speichermechanismus
	 * @param storeKeeper der Datenverwalter für dieses Archiv
	 * @author koppy
	 */
	public FileDataStore(String storageDirUrl, StoreKeeper storeKeeper) throws DataStoreException {
		this(storageDirUrl);
		this.storeKeeper = storeKeeper;
		this.storeKeeper.setStorageDir(this.storageDir);

	}

	/**
	 * Legt die URL für den darunterliegenden Speichermechanismus fest
	 *
	 * @param   storageUrl
	 * @exception   DataStoreException
	 * @author koppy
	 */
	public void setStorageUrl(String urlString) throws DataStoreException {
		URL storageUrl = null;

		try {
			storageUrl = new URL(urlString);
		} catch (MalformedURLException e) {
			throw new DataStoreException("Incorrect URL: " + urlString);
		}

		if (!storageUrl.getProtocol().equalsIgnoreCase("file")) {
			throw new DataStoreException("A file data store needs a file URL, not " + storageUrl);
		}
		
		this.storageDir = new File(storageUrl.getFile());
	}

	/**
	 * Gibt den URLString für den unterliegenden Speichermechanismus zurück
	 *
	 * @author koppy
	 */
	public String getStorageUrl() {
		if (this.storageDir == null) {
			return null;
		}
		
		URL url = null;

		try {
			url = storageDir.toURL();

			return url.toString();
		} catch (MalformedURLException e) {
			// Das kann eigentlich gar nicht passieren,
			// da wir storageDir aus einer korrekten URL generiert haben
			return "";
		}

	}

	/**
	 * Öffnet ein "Datenlager"
	 *
	 * @author koppy
	 */
	protected synchronized void openDataStore() throws DataStoreException {
		if (storageDir == null) {
			throw new DataStoreException("Cannot open store: storage directory not specified yet!");
		}
		
		if (!storageDir.exists()) {
			// Verzeichnis existiert nicht! --> neu anlegen !
			if (!storageDir.mkdir()) {
				throw new DataStoreException("Cannot create file data store: storage directory " + storageDir + " cannot be created !");
			}
		}

		if (!storageDir.canRead()) {
			throw new DataStoreException("Cannot open store: " + storageDir + " is not readable!");
		}
		
		try {
			if (storeKeeper == null) {
				storeKeeper = new HashStoreKeeper(storageDir);
			}
			
			// Default StoreKeeper
			this.storeKeeper.init();
		} catch (IOException e) {
			VKCLogManager.errorLog(this, "", e.getMessage(), e);
			throw new DataStoreException("Cannot init storekeeper: " + storageDir);
		}

	}

	/**
	 * Schließt ein geöffnetes "Datenlager"
	 *
	 * @author koppy
	 */
	protected synchronized void closeDataStore() throws DataStoreException {
		// storeKeepxer = null;
		// Ähhhhhmmm
	}

	/**
	 * Speichert ein Byte-Array
	 *
	 * @param   data  - ein Byte-Array
	 * @exception   DataStoreException
	 * @author koppy
	 */
	protected void storeData(byte[] data, String id) throws DataStoreException {
		try {
			storeKeeper.insert(data, id);
		} catch (Exception e) {
			throw new DataStoreException("Cannot store data: \n" + e);
		}
	}

	/**
	 * Speichert einen InputStream unter gegebener ID
	 *
	 * @param   inputStream der InputStream
	 * @param   id
	 * @exception   DataStoreException
	 * @author koppy
	 */
	protected void storeInputStream(InputStream inputStream, String id) throws DataStoreException {
		try {
			//write the stream to the file specified
			OutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(this.storeKeeper.getTargetDir(id), id)));
			FileUtils.write(inputStream, bos);
			bos.close();
		} catch (Exception e) {
			throw new DataStoreException("Cannot store stream for id:" + id + "\n" + e);
		} finally {
			if (!(inputStream instanceof VKCInputStream)) {
				FileUtils.closeInputStream(inputStream);
			}
		}
	}

	/**
	 * Fuegt ein Byte-Array einem vorhandenen hinzu
	 *
	 * @param   data - ein Byte-Array
	 * @param   id
	 * @exception   DataStoreException
	 * @author koppy
	 */
	protected void appendData(byte[] data, String id) throws DataStoreException {
		try {
			File file = this.search(id);

			// hier Byte-Array hinzufuegen
			RandomAccessFile out = new RandomAccessFile(file, "rw");
			out.seek(file.length());
			out.write(data, 0, data.length);
			out.close();
		} catch (Exception e) {
			throw new DataStoreException("Cannot append data for id:" + id + "\n" + e);
		}
	}

	/**
	 * Gibt die Datei unter der gesuchten ID wieder
	 *
	 * @param   id - Dateiname
	 * @return falls GEFUNDEN, Datei mit Namen id
	 *         sonst Verzeichnis, in welches Datei mit Namen id gehört
	 * @author koppy
	 */
	private File search(String id) throws IOException {
		File file = this.storeKeeper.search(id);

		if (file == null || !file.isFile() || !file.exists()) {
			throw new IOException("File for id = " + id + " not found!" + (file != null ? "\nFound " + file : ""));
		}

		return file;
	}

	/**
	 * Lädt das Byte-Array einer gegebenen ID
	 *
	 * @param   id
	 * @return ein Byte-Array
	 * @exception   DataStoreException
	 * @author koppy
	 */
	protected byte[] loadData(String id) throws DataStoreException {
		try {
			return FileUtils.getFileAsByteArray(this.search(id));
		} catch (Exception e) {
			throw new DataStoreException("Cannot load data for id: " + id + "\n" + e);
		}
	}

	/**
	 * Lädt das unter ID gespeicherte Datum und gibt es als
	 * InputStream zurück
	 *
	 * @param   id
	 * @return den InputStream
	 * @exception  DataStoreException
	 */
	protected InputStream loadDataAsInputStream(String id) throws DataStoreException {
		try {
			File f = this.search(id);

			return new VKCInputStream(new BufferedInputStream(new FileInputStream(f)),f.length());
		} catch (Exception e) {
			throw new DataStoreException("Cannot load InputStream for id: " + id + "\n" + e);
		}
	}


	/**
	 * Lädt das unter ID gespeicherte Datum und gibt es
	 * als File - Objekt zurück
	 *
	 * @param   id
	 * @return das File-Objekt
	 * @exception  DataStoreException
	 */
	protected  File loadDataAsFile(String id) throws DataStoreException {
	    try {
		    return this.search(id);
	    } catch (Exception e) {
		    throw new DataStoreException("Cannot load File for id: " + id + "\n" + e);
	    }
	}

	/**
	 * Löscht die unter id gespeicherten Daten
	 *
	 * @param   id
	 * @exception   DataStoreException
	 * @author koppy
	 */
	protected void deleteData(String id) throws DataStoreException {
		try {
			storeKeeper.delete(id);
		} catch (Exception e) {
			throw new DataStoreException("Cannot delete data for id: " + id + "\n" + e);
		}
	}

	/**
	 * @see vkc.server.archive.DataStore#iterator()
	 */
	public Iterator iterator() {
		return this.storeKeeper.iterator();
	}
}