/**
 * SqlJetEngine.java
 * Copyright (C) 2009-2013 TMate Software Ltd
 *
 * 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; version 2 of the License.
 *
 * 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.
 *
 * For information on how to redistribute this software under
 * the terms of a license other than GNU General Public License
 * contact TMate Software at support@sqljet.com
 */
package org.tmatesoft.sqljet.core.table.engine;

import java.io.File;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;

import org.tmatesoft.sqljet.core.ISqlJetMutex;
import org.tmatesoft.sqljet.core.SqlJetErrorCode;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.SqlJetTransactionMode;
import org.tmatesoft.sqljet.core.internal.ISqlJetBtree;
import org.tmatesoft.sqljet.core.internal.ISqlJetDbHandle;
import org.tmatesoft.sqljet.core.internal.ISqlJetFile;
import org.tmatesoft.sqljet.core.internal.ISqlJetFileSystem;
import org.tmatesoft.sqljet.core.internal.ISqlJetFileSystemsManager;
import org.tmatesoft.sqljet.core.internal.SqlJetBtreeFlags;
import org.tmatesoft.sqljet.core.internal.SqlJetFileOpenPermission;
import org.tmatesoft.sqljet.core.internal.SqlJetFileType;
import org.tmatesoft.sqljet.core.internal.SqlJetPagerJournalMode;
import org.tmatesoft.sqljet.core.internal.SqlJetSafetyLevel;
import org.tmatesoft.sqljet.core.internal.SqlJetUtility;
import org.tmatesoft.sqljet.core.internal.btree.SqlJetBtree;
import org.tmatesoft.sqljet.core.internal.db.SqlJetDbHandle;
import org.tmatesoft.sqljet.core.internal.fs.SqlJetFileSystemsManager;
import org.tmatesoft.sqljet.core.internal.schema.SqlJetSchema;
import org.tmatesoft.sqljet.core.internal.table.SqlJetOptions;
import org.tmatesoft.sqljet.core.table.ISqlJetBusyHandler;
import org.tmatesoft.sqljet.core.table.ISqlJetOptions;
import org.tmatesoft.sqljet.core.table.ISqlJetTransaction;
import org.tmatesoft.sqljet.core.table.SqlJetDefaultBusyHandler;

/**
 * @author TMate Software Ltd.
 * @author Sergey Scherbina (sergey.scherbina@gmail.com)
 * 
 */
public class SqlJetEngine {

	private static final Set READ_FLAGS = Collections
			.unmodifiableSet(SqlJetUtility.of(SqlJetBtreeFlags.READONLY));
	private static final Set READ_PERMISSIONS = Collections
			.unmodifiableSet(SqlJetUtility
					.of(SqlJetFileOpenPermission.READONLY));
	private static final Set WRITE_FLAGS = Collections
			.unmodifiableSet(SqlJetUtility.of(SqlJetBtreeFlags.READWRITE,
					SqlJetBtreeFlags.CREATE));
	private static final Set WRITE_PREMISSIONS = Collections
			.unmodifiableSet(SqlJetUtility.of(
					SqlJetFileOpenPermission.READWRITE,
					SqlJetFileOpenPermission.CREATE));

	protected static final ISqlJetFileSystemsManager FILE_SYSTEM_MANAGER = SqlJetFileSystemsManager
			.getManager();

	protected ISqlJetFileSystem fileSystem = FILE_SYSTEM_MANAGER.find(null);

	protected boolean writable;
	protected ISqlJetDbHandle dbHandle;
	protected ISqlJetBtree btree;
	protected boolean open = false;
	protected File file;

	private boolean transaction;
	private SqlJetTransactionMode transactionMode;

	/**
     *
     */
	public SqlJetEngine(final File file, final boolean writable) {
		this.writable = writable;
		this.file = file;
	}

	/**
	 * @param file
	 * @param writable
	 * @param fs
	 */
	public SqlJetEngine(final File file, final boolean writable,
			final ISqlJetFileSystem fs) {
		this.writable = writable;
		this.file = file;
		this.fileSystem = fs;
	}

	/**
	 * @param file
	 * @param writable
	 * @param fsName
	 * @throws SqlJetException
	 */
	public SqlJetEngine(final File file, final boolean writable,
			final String fsName) throws SqlJetException {
		this.writable = writable;
		this.file = file;
		this.fileSystem = FILE_SYSTEM_MANAGER.find(fsName);
		if (this.fileSystem == null) {
			throw new SqlJetException(String.format(
					"File system '%s' not found", fsName));
		}
	}

	/**
	 * @param fs
	 * @param isDefault
	 * @throws SqlJetException
	 */
	public void registerFileSystem(final ISqlJetFileSystem fs,
			final boolean isDefault) throws SqlJetException {
		FILE_SYSTEM_MANAGER.register(fs, isDefault);
	}

	/**
	 * @param fs
	 * @throws SqlJetException
	 */
	public void unregisterFileSystem(final ISqlJetFileSystem fs)
			throws SqlJetException {
		FILE_SYSTEM_MANAGER.unregister(fs);
	}

	/**
	 * @return database file this engine is created for.
	 */
	public File getFile() {
		return this.file;
	}

	/**
	 * Check write access to data base.
	 * 
	 * @return true if modification is allowed
	 */
	public boolean isWritable() throws SqlJetException {
		return writable;
	}

	public ISqlJetFileSystem getFileSystem() {
		return fileSystem;
	}

	/**
	 * Checks is database open.
	 * 
	 * @return true if database is open.
	 */
	public boolean isOpen() {
		return open;
	}

	protected void checkOpen() throws SqlJetException {
		if (!isOpen())
			throw new SqlJetException(SqlJetErrorCode.MISUSE, "Database closed");
	}

	/**
	 * 

* Opens connection to database. It does not create any locking on database. * First lock will be created when be called any method which requires real * access to options or schema. *

* * @throws SqlJetException * if any trouble with access to file or database format. */ public synchronized void open() throws SqlJetException { if (!open) { dbHandle = new SqlJetDbHandle(fileSystem); dbHandle.setBusyHandler(new SqlJetDefaultBusyHandler()); btree = new SqlJetBtree(); final Set flags = EnumSet .copyOf(writable ? WRITE_FLAGS : READ_FLAGS); final Set permissions = EnumSet .copyOf(writable ? WRITE_PREMISSIONS : READ_PERMISSIONS); final SqlJetFileType type = (file != null ? SqlJetFileType.MAIN_DB : SqlJetFileType.TEMP_DB); btree.open(file, dbHandle, flags, type, permissions); // force readonly. ISqlJetFile file = btree.getPager().getFile(); if (file != null) { Set realPermissions = btree .getPager().getFile().getPermissions(); writable = realPermissions .contains(SqlJetFileOpenPermission.READWRITE); } open = true; } else { throw new SqlJetException(SqlJetErrorCode.MISUSE, "Database is open already"); } } public Object runSynchronized(ISqlJetEngineSynchronized op) throws SqlJetException { checkOpen(); dbHandle.getMutex().enter(); try { return op.runSynchronized(this); } finally { dbHandle.getMutex().leave(); } } /** * Close connection to database. It is safe to call this method if database * connections is closed already. * * @throws SqlJetException * it is possible to get exception if there is actvie * transaction and rollback did not success. */ public void close() throws SqlJetException { if (open) { runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { if (btree != null) { btree.close(); btree = null; open = false; } closeResources(); return null; } }); if (!open) { dbHandle = null; } } } protected void closeResources() throws SqlJetException { } /* * (non-Javadoc) * * @see java.lang.Object#finalize() */ @Override protected void finalize() throws Throwable { try { if (open) { close(); } } finally { super.finalize(); } } /** * Reads database schema and options. * * @throws SqlJetException */ protected void readSchema() throws SqlJetException { runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { btree.enter(); try { dbHandle.setOptions(new SqlJetOptions(btree, dbHandle)); btree.setSchema(new SqlJetSchema(dbHandle, btree)); } finally { btree.leave(); } return null; } }); } /** * Returns database options. * * @return options of this database. */ public ISqlJetOptions getOptions() throws SqlJetException { checkOpen(); if (null == btree.getSchema()) { readSchema(); } return dbHandle.getOptions(); } /** * Refreshes database schema. */ public void refreshSchema() throws SqlJetException { if (null == btree.getSchema() || !getOptions().verifySchemaVersion(false)) { readSchema(); } } protected SqlJetSchema getSchemaInternal() throws SqlJetException { checkOpen(); refreshSchema(); return btree.getSchema(); } /** * Get busy handler. * * @return the busy handler. */ public ISqlJetBusyHandler getBusyHandler() { return dbHandle.getBusyHandler(); } /** * Set busy handler. Busy handler treats situation when database is locked * by other process or thread. * * @param busyHandler * the busy handler. */ public void setBusyHandler(ISqlJetBusyHandler busyHandler) { dbHandle.setBusyHandler(busyHandler); } /** * Retruns threading synchronization mutex. * * @return Semaphore instance used to synchronize database access from * multiple threads within the same process. */ public ISqlJetMutex getMutex() { return dbHandle.getMutex(); } /** * Set cache size (in count of pages). * * @param cacheSize * the count of pages which can hold cache. */ public void setCacheSize(final int cacheSize) throws SqlJetException { checkOpen(); runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { btree.setCacheSize(cacheSize); return null; } }); } /** * Get cache size (in count of pages). * * @return the count of pages which can hold cache. */ public int getCacheSize() throws SqlJetException { checkOpen(); refreshSchema(); return (Integer) runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { return btree.getCacheSize(); } }); } /** * Set safety level * * @param safetyLevel * */ public void setSafetyLevel(final SqlJetSafetyLevel safetyLevel) throws SqlJetException { checkOpen(); runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { btree.setSafetyLevel(safetyLevel); return null; } }); } /** * Set journal mode * * @param journalMode * */ public void setJournalMode(final SqlJetPagerJournalMode journalMode) throws SqlJetException { checkOpen(); runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { btree.setJournalMode(journalMode); return null; } }); } /** * Get safety level * * @return the safety level set. */ public SqlJetSafetyLevel getSafetyLevel() throws SqlJetException { checkOpen(); refreshSchema(); return (SqlJetSafetyLevel) runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { return btree.getSafetyLevel(); } }); } /** * Get jounrnal mode * * @return the safety level set. */ public SqlJetPagerJournalMode getJournalMode() throws SqlJetException { checkOpen(); refreshSchema(); return (SqlJetPagerJournalMode) runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { return btree.getJournalMode(); } }); } /** * Returns true if a transaction is active. * * @return true if there is an active running transaction. */ public boolean isInTransaction() { return transaction; } public SqlJetTransactionMode getTransactionMode() { return transactionMode; } /** * Begin transaction. * * @param mode * transaction's mode. */ public void beginTransaction(final SqlJetTransactionMode mode) throws SqlJetException { checkOpen(); runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { if (!isTransactionStarted(mode)) { doBeginTransaction(mode); } return null; } }); } /** * Commits transaction. * */ public void commit() throws SqlJetException { checkOpen(); runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { if (isInTransaction()) { doCommitTransaction(); } return null; } }); } /** * Rolls back transaction. * */ public void rollback() throws SqlJetException { checkOpen(); runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { doRollbackTransaction(); return null; } }); } /** * Runs transaction. * * @param op * transaction's body (closure). * @param mode * transaction's mode. * @return result of * {@link ISqlJetTransaction#run(org.tmatesoft.sqljet.core.table.SqlJetDb)} * call. * @throws SqlJetException */ protected Object runEngineTransaction(final ISqlJetEngineTransaction op, final SqlJetTransactionMode mode) throws SqlJetException { checkOpen(); return runSynchronized(new ISqlJetEngineSynchronized() { public Object runSynchronized(SqlJetEngine engine) throws SqlJetException { if (isTransactionStarted(mode)) { return op.run(SqlJetEngine.this); } else { doBeginTransaction(mode); boolean success = false; try { final Object result = op.run(SqlJetEngine.this); doCommitTransaction(); success = true; return result; } finally { if (!success) { doRollbackTransaction(); } transaction = false; transactionMode = null; } } } }); } private boolean isTransactionStarted(final SqlJetTransactionMode mode) { return transaction && (transactionMode == mode || mode == SqlJetTransactionMode.READ_ONLY); } private void doBeginTransaction(final SqlJetTransactionMode mode) throws SqlJetException { btree.beginTrans(mode); refreshSchema(); transaction = true; transactionMode = mode; } private void doCommitTransaction() throws SqlJetException { btree.closeAllCursors(); btree.commit(); transaction = false; transactionMode = null; } private void doRollbackTransaction() throws SqlJetException { btree.closeAllCursors(); btree.rollback(); transaction = false; transactionMode = null; } }

Ads help maintain this website.