/**
 * ファイルベースのコンテンツ管理を行なう，最も単純なコンテンツアクセサ
 * 検索やinodeの存在その他について，全く何も考慮していない，開発とテストのためのもの
 */
package org.logical_paradox.rss.rcm.accessor.simple;

import org.logical_paradox.common.btree.Btree;
import org.logical_paradox.common.btree.BtreeResultSet;
import org.logical_paradox.common.btree.BtreeSession;
import org.logical_paradox.common.vrm.RecordFactory;
import org.logical_paradox.common.vrm.VariableRecordManager;
import org.logical_paradox.rss.rcm.RSSContentsMngrServiceImpl;
import org.logical_paradox.rss.rcm.RSSRCMConfig;
import org.logical_paradox.rss.rcm.accessor.ContentsAccessor;
import org.logical_paradox.rss.rcm.df.DataFilter;
import org.logical_paradox.rss.rcm.df.DataFilterFactory;


public class SimpleFileContentsAccessor implements ContentsAccessor {
	private static Btree btree = null;					// VMで一意のb-tree管理クラス
	private static int clientCounter = 0;				// b-treeセッションのカウンタ
	private BtreeSession session = null;					// b-treeセッション
	private static DataFilter dataFilter;					// コンテンツデータフィルタ
	private static VariableRecordManager manager = null;	// コンテンツを格納する可変長レコードマネージャー
	private static int fileno = 0;						// 格納するファイルの番号

	/** このコンテンツアクセサを所有するコンテンツサービスの完全修飾サービス名 */
	private String fqsn;

	private static VirtualQueueServer queue;				// 仮想キューサーバー

	static {
		dataFilter = DataFilterFactory.getFilter(DataFilterFactory.GZIP);
	}

	/**
	 * コンテンツアクセサを初期化する．
	 * @param initparam 初期化パラメータ
	 */
	public void init(String initparam) {
		fqsn = initparam;
		open(initparam);
	}

	/**
	 * コンテンツ管理ストレージへの接続をオープンする
	 * ファイル管理なので，別に接続の必要はない
 	 */
	public void open(String openstr) {
		try {
			if(btree == null) {
				// VMで初回の場合はBtreeクラスのインタンスを1つ生成する
				btree = Btree.getInstance(new RCMBtreeFactory());
				btree.open(openstr + ".idx");

				// 仮想キューサーバーへ接続する
				queue = new VirtualQueueServer(new RSSRCMConfig(RSSContentsMngrServiceImpl.RCM_CONF_FILENAME).getRSSDistributedServiceRegistry());
			}
			// b-tree操作用のセッションをオープンする
			session = btree.getSession();
			clientCounter++;

			if(manager == null) {
				// VMで初回の場合はVRMのインスタンスを1つ作成する
				manager = new VariableRecordManager(openstr + ".dat", RecordFactory.getInstance("org.logical_paradox.rss.rcm.accessor.simple.ContentsRecordFactory"));
			}
		} catch(Exception e) {
			e.printStackTrace();
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * コンテンツを登録する
	 * 
	 * @param url 登録するURL
	 * @param contents 登録するコンテンツ
	 */
	public void regist(String url, String contents) {
		try {
			// 指定URLのMD5ダイジェストを取得して，コンテンツと関連付けるキーとする
			String digest = dataFilter.filterURL(url);
			RCMBtreeKey key = (RCMBtreeKey)session.newKey(digest);

			// 指定キーに該当するデータが既に登録されているかどうかを調べる
			// ダイジェストが重なる場合がないとも言えないので
			BtreeResultSet result = session.find(key);
			ContentsRecord cr = new ContentsRecord();
			if(result != null) {
				key = (RCMBtreeKey)result.getKey();
				// 既にデータがあるので，既存のデータを取得する
				cr = (ContentsRecord)manager.get(key.getOffset());
			}
			// コンテンツを登録する
			cr.add(new ContentsByteStream(dataFilter.compressContents(contents.getBytes())));
			long rno = manager.insert(cr);

			// レコードのバイト変位を設定後，キーを更新する
			key.setOffset(rno);
			session.insert(key);

			// キューにURLを投入する
			queue.enqueue(digest + "::" + fqsn);
		} catch(Exception e) {
			e.printStackTrace();
			throw new IllegalStateException(e.getMessage());
		}
	}

	/**
	 * コンテンツを削除する
	 */
	public void unregist( String url ) {
		throw new IllegalStateException( "このメソッドは実装されていません" );
	}

	/**
	 * 全てのコンテンツを削除する
	 */
	public void unregistAll() {
		throw new IllegalStateException( "このメソッドは実装されていません" );
	}

	/**
	 * コンテンツを探す
	 * 
	 * @param url URL
	 * @return 取得できたコンテンツ
	 */
	public String find(String url) {
		try {
			RCMBtreeKey key = (RCMBtreeKey)session.newKey(dataFilter.filterURL(url));
			BtreeResultSet result = session.find(key);
			if(result == null || result.getKey() == null) {
				return null;
			}

			// コンテンツがあったので，キーに記録されているレコードをVRMでロードする
			key = (RCMBtreeKey)result.getKey();
			ContentsRecord cr = (ContentsRecord)manager.get(key.getOffset());
			if(cr == null) {
				throw new IllegalStateException("no such record:" + key.getOffset() + " binded as " + key.toString());
			}
			ContentsByteStream cbs = cr.toArray()[0];

			// バイト列(gzipで圧縮されている)からコンテンツ文字列を復元して返す
			return new String(dataFilter.decompressContents(cbs.byteStream()));

		} catch(Exception e) {
			e.printStackTrace();
			throw new IllegalStateException(e.getMessage());
		}
	}

	/**
	 * ストレージとの接続を閉じる
	 */
	public void close() {
		clientCounter--;

		if(clientCounter == 0) {
			// もうオープンしているセッションがないのでb-treeとVRMを閉じる
			try {
				session.close();
				manager.close();
			} catch (Exception e) {
				e.printStackTrace();
				throw new IllegalStateException(e.getMessage());
			}
		}
		session = null;
		manager = null;
	}
}

// end of SimpleFileContentsAccessor.java
