/*
* Copyright (C) 2006 http://www.chaidb.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
*/
package org.chaidb.db.index.btree.bufmgr;
import org.apache.log4j.Logger;
import org.chaidb.db.exception.ChaiDBException;
import org.chaidb.db.exception.ErrorCode;
import org.chaidb.db.index.btree.Debug;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;
class Storage {
/**
* keep storage information of data file.
*/
class FileInfo {
/**
* This class implements the basic functions related to physical
* file storage, like read a page from a file, store a page into a file, open the
* database file, close the database file, etc.
*/
class PagedFile {
/**
* the database file reference
*/
private RandomAccessFile _file;
/**
* page size
*/
private int _pageSize;
PagedFile(int pgsize) {
_pageSize = pgsize;
}
void open(File file) throws ChaiDBException {
try {
this._file = new RandomAccessFile(file, "rw");
} catch (FileNotFoundException fnfe) {
try {
String dir = file.getAbsolutePath();
dir = dir.substring(0, dir.lastIndexOf(File.separator));
new File(dir).mkdirs();
this._file = new RandomAccessFile(file, "rw");
//modified by Stanley
virtualFiles.remove(file.getAbsolutePath());
} catch (Exception e) {
// moved to details -ranjeet
String details = "Open for file " + file.getName() + " failed. " + e.toString() + ".";
throw new ChaiDBException(ErrorCode.BTREE_NOT_FOUND, details);
}
} catch (IOException ioe) {
// moved to details -ranjeet
String details = "Open for file " + file.getName() + " failed. " + ioe.toString() + ".";
throw new ChaiDBException(ErrorCode.BTREE_IO_ERROR, details);
}
}
/**
* close the database file
*/
synchronized void close() throws ChaiDBException {
try {
_file.close();
} catch (IOException ioe) {
// moved to details -ranjeet
String details = "Close for the page file failed. " + ioe.toString() + ".";
throw new ChaiDBException(ErrorCode.BTREE_IO_ERROR, details);
}
}
synchronized void sync() throws ChaiDBException {
try {
_file.getFD().sync();
} catch (IOException ioe) {
String details = "Sync file to disk failed." + ioe.toString() + ".";
throw new ChaiDBException(ErrorCode.BTREE_IO_ERROR, details);
}
}
/**
* read a page from the database file
*
* @param pageIndex The position to read the page, unit is pageSize
* @param page The place to hold the page
* @return if no such page, return false, and page is untouched; else return true,
* page will have the new data.
*/
synchronized boolean readPage(int pageIndex, byte[] page) throws ChaiDBException {
try {
if (_file.length() <= pageIndex * _pageSize) {
return false;
}
_file.seek(((long) pageIndex) * _pageSize);
_file.read(page);
return true;
} catch (IOException e) {
// details -ranjeet
String details = "The operation read page failed for page index " + pageIndex + ". " + e.toString() + ".";
throw new ChaiDBException(ErrorCode.BTREE_IO_ERROR, details);
}
}
/**
* write a page back to the database file
*
* @param data The page data
* @param pageIndex The position to put the page, unit is pageSize
*/
synchronized void writePage(int pageIndex, byte[] data) throws ChaiDBException {
if (data.length > _pageSize) {
// details -ranjeet
String details = "Data length is less than page size.";
throw new ChaiDBException(ErrorCode.BTREE_DATA_SIZE_OVERFLOW, details);
}
try {
_file.seek(((long) pageIndex) * _pageSize);
_file.write(data);
} catch (IOException ioe) {
// details -ranjeet
String details = "The operation write page failed for page index " + pageIndex + ". " + ioe.toString() + ".";
throw new ChaiDBException(ErrorCode.BTREE_IO_ERROR, details);
}
}
} // end of class PagedFile
private String _filePath; //which dirctory store data (full path?)
private int _fileNumber; //file serial number {0,1,2...}
private PagedFile _file; // the reference to the pagedFile
public FileInfo(String filePath, int fileNumber) {
_filePath = filePath;
_fileNumber = fileNumber;
_file = new PagedFile(_PAGE_SIZE);
}
/*access method*/
void open() throws ChaiDBException {
_file.open(new File(_filePath));
}
/**
* close the file
* might need other clean up ######### Ying
*/
void close() throws ChaiDBException {
_file.close();
}
void sync() throws ChaiDBException {
_file.sync();
}
int getFileNumber() {
return _fileNumber;
}
/**
* read a page from the database file
*
* @return if no such page, return false, and page is untouched; else return true,
* page will have the new data.
*/
String getFilePath() {
return _filePath;
}
boolean readPage(int pageNumber, byte[] page) throws ChaiDBException {
return _file.readPage(pageNumber, page);
}
/**
* write a page back to the database file
*
* @param data The page data
* @param pageNumber The position to put the page, unit is pageSize
*/
void writePage(int pageNumber, byte[] data) throws ChaiDBException {
_file.writePage(pageNumber, data);
}
} // end of class FileInfo
// ------------------------------------------------------------ constants
/** class debug flag */
// private final static boolean _DEBUG = false;
/**
* The max pages a file can contain
*/
public static final int MAX_FILESIZE_IN_PAGE = Debug.DEBUG_STORAGE_FILE_SIZE ? 0x100 : 0x7FFFF;
/**
* The maximize number of simultaneous open files
*/
private static final short _MAX_OPEN_FILES = 1500;
// private static final short _KICK_FILES = 300;
private final int _PAGE_SIZE;
// --------------------------------------------------------- class variables
/**
* logger, it may be null
*/
private static Logger logger = Logger.getLogger(Storage.class);
// ------------------------------------------------------ instance variables
//no need synchronized, we synchronized on each method using this.
// private Map _fileInfoList = new LinkedHashMap() {
// protected boolean removeEldestEntry(Map.Entry eldest) {
// if (_fileInfoList.size() > _MAX_OPEN_FILES) {
// FileInfo fo = (FileInfo) eldest.getValue();
// try {
// fo.close();
// if (_DEBUG) {
// logger.debug("kick off file " + fo.getFilePath());
// }
// } catch (ChaiDBException e) {
// logger.error(e);
// }
// return true;
// }
// return false;
// }
// };
private FileInfoMap _fileInfoList = new FileInfoMap();
// add by Stanley for storing Collection directories information
private StorageMeta storageMeta = new StorageMeta();
private LinkedList virtualFiles = new LinkedList();
private class FileInfoMap {
LinkedHashMap _infoMap = new LinkedHashMap() {
protected boolean removeEldestEntry(Map.Entry eldest) {
// return super.removeEldestEntry(eldest);
if (this.size() > _MAX_OPEN_FILES) {
logger.debug("Begin to kick off opened files.");
Map files = (Map) eldest.getValue();
closeAll(files);
logger.debug("End of kicking off opened files.");
return true;
}
return false;
}
};
FileInfoMap() {
}
boolean containsKey(Object key1) {
assert key1 != null;
return (_infoMap.containsKey(key1));
}
boolean containsKey(Object key1, int key2) {
assert key1 != null;
if (!_infoMap.containsKey(key1)) {
return false;
}
Map alist = (Map) _infoMap.get(key1);
if (alist.containsKey(new Integer(key2))) {
return true;
}
return false;
}
Map get(Object key1) {
assert key1 != null;
return (Map) _infoMap.get(key1);
}
Object get(Object key1, int key2) {
assert key1 != null;
Map alist = (Map) _infoMap.get(key1);
if (alist == null) {
return null;
}
return alist.get(new Integer(key2));
}
Object put(Object key1, int key2, Object value) {
assert key1 != null;
Map alist = (Map) _infoMap.get(key1);
Object oldValue = null;
if (alist == null) {
alist = new HashMap();
alist.put(new Integer(key2), value);
_infoMap.put(key1, alist);
} else {
oldValue = alist.put(new Integer(key2), value);
}
return oldValue;
}
Map remove(Object key1) {
assert key1 != null;
return (Map) _infoMap.remove(key1);
}
Set keySet() {
return _infoMap.keySet();
}
Collection values() {
return _infoMap.values();
}
}
synchronized void closeAll(Map files) {
Iterator it = files.keySet().iterator();
while (it.hasNext()) {
Integer fileno = (Integer) it.next();
FileInfo fileInfo = (FileInfo) files.get(fileno);
logger.debug("close file " + fileInfo.getFilePath());
try {
fileInfo.close();
} catch (ChaiDBException e) {
logger.error(e);
}
}
}
// ------------------------------------------------------ public methods
/**
* Constructor
*/
Storage(int pageSize) {
_PAGE_SIZE = pageSize;
}
synchronized void extendCollectionDir(int id, String primaryDir, String extendedDir) throws ChaiDBException {
}
synchronized void open(File file) throws ChaiDBException {
assert file != null;
final String filePath = file.getAbsolutePath();
if (_fileInfoList.containsKey(filePath)) {
return; //opened already
}
addNewFile(filePath, 0);
}
synchronized void close(String filePath) {
assert filePath != null;
logger.debug("storage.close filepath=" + filePath);
Map files = _fileInfoList.remove(filePath);
if (files != null) // files maybe closed becaue opened files count is larger than max open files.
closeAll(files);
// Iterator it = _fileInfoList.keySet().iterator();
// while (it.hasNext()) {
// FileInfo fileInfo = (FileInfo) it.next();
// if (fileInfo.getFilePath().equals(filePath)) {
// if (_DEBUG) {
// logger.debug("close file " + fileInfo.getFilePath());
// }
// it.remove();
// try {
// fileInfo.close();
// } catch (ChaiDBException e) {
// logger.error(e);
// }
// }
// }
}
boolean readPage(String filePath, PageNumber pageIndex, byte[] page) throws ChaiDBException {
assert filePath != null;
assert pageIndex != null;
FileInfo fo = getFile(filePath, pageIndex.getFileNumber());
if (fo == null) return false;
return fo.readPage(pageIndex.getPageInFile(), page);
}
/**
* write a page back to the database file
*
* @param data The page data
* @param pageIndex The position to put the page, unit is pageSize
*/
void writePage(String filePath, PageNumber pageIndex, byte[] data) throws ChaiDBException {
assert filePath != null;
assert pageIndex != null;
// int tid = pageIndex.getTreeId();
FileInfo fo = getFile(filePath, pageIndex.getFileNumber());
if (fo != null) fo.writePage(pageIndex.getPageInFile(), data);
}
synchronized void sync() {
Collection lists = _fileInfoList.values();
for (Iterator itList = lists.iterator(); itList.hasNext();) {
Map filesMap = (Map) itList.next();
Collection files = filesMap.values();
for (Iterator itFiles = files.iterator(); itFiles.hasNext();) {
FileInfo fileInfo = (FileInfo) itFiles.next();
logger.debug("sync file " + fileInfo.getFilePath());
try {
fileInfo.sync();
} catch (ChaiDBException e) {
logger.error(e);
}
}
}
}
// ------------------------------------------------------ private methods
/**
* Kick out and close some old files for new open file.
* Because JDK limits the maxise number of simulataneous open file up to 2030~2042.
* Thus old files should be closed to free file descriptors preoccupied by them
*/
// private void kickOffOldOpenedFiles() {
// int count = 0;
// while (count < _KICK_FILES) {
// FileInfo fo = (FileInfo) _fileInfoList.remove(0);
// try {
// fo.close();
// count++;
// if (_DEBUG) {
// logger.debug("kick off file " + fo.getFilePath());
// }
// } catch (ChaiDBException e) {
// logger.error(e);
// }
// }
// }
//
synchronized private FileInfo addNewFile(String fname, int fileno) throws ChaiDBException {
// if (_fileInfoList.size() > _MAX_OPEN_FILES) {
// kickOffOldOpenedFiles();
// }
boolean fileExists = false;
String str = null;
if (storageMeta.getCollectionID(fname) != null) {
Iterator possibleFiles = storageMeta.getAllFileNames(fname);
while (possibleFiles.hasNext()) {
str = (String) possibleFiles.next();
if (fileno > 0) str += fileno;
File f = new File(str);
if (f.exists()) {
fileExists = true;
break;
}
}
if (!fileExists) {
str = storageMeta.getCurrentFileName(fname);
if (fileno > 0) str += fileno;
}
} else {
str = fname;
if (fileno > 0) str += fileno;
}
FileInfo fo = new FileInfo(str, fileno);
fo.open();
_fileInfoList.put(fname, fileno, fo);
return fo;
}
synchronized private FileInfo getFile(String filePath, int fileNumber) throws ChaiDBException {
FileInfo fi = (FileInfo) _fileInfoList.get(filePath, fileNumber);
if (fi != null) return fi;
return addNewFile(filePath, fileNumber);
}
/**
* @param filePath
* @param fileno
* @return
*/
synchronized public boolean curDirHasFile(String filePath, int fileno) {
boolean fileExists = false;
String str;
str = storageMeta.getCurrentFileName(filePath);
if (fileno > 0) str += fileno;
if (virtualFiles.contains(str)) return true;
FileInfo fi = (FileInfo) _fileInfoList.get(filePath, fileno);
if (fi != null) {
if (fi.getFilePath().equals(str)) return true;
}
File f = new File(str);
if (f.exists()) {
fileExists = true;
}
return fileExists;
}
synchronized public void addVirtualFile(String filePath, int fileno) {
String str = storageMeta.getCurrentFileName(filePath);
if (fileno > 0) str += fileno;
if (!virtualFiles.contains(str)) virtualFiles.add(str);
}
public void addExtendedDir(int collectionID, String extendedDir) throws ChaiDBException {
storageMeta.addExtendedDirectory(collectionID, extendedDir);
}
public String listCollectionDirs(int collectionID) throws ChaiDBException {
return storageMeta.listCollectionDir(collectionID);
}
public Iterator listColDirs(int collectionID) throws ChaiDBException {
return storageMeta.listColDirs(collectionID);
}
public Iterator removeExtendedColDirs(int collectionID) throws ChaiDBException {
return storageMeta.removeExtendedColDirs(collectionID);
}
synchronized public CollectionStorage getColStorage(int collectionId) {
return storageMeta.getColStorage(collectionId);
}
}