com.abubusoft.kripton.android.sqlite.AbstractDataSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kripton-orm Show documentation
Show all versions of kripton-orm Show documentation
Kripton Persistence Library - ORM module
/*******************************************************************************
* Copyright 2015, 2017 Francesco Benincasa ([email protected]).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
/**
*
*/
package com.abubusoft.kripton.android.sqlite;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.abubusoft.kripton.android.KriptonLibrary;
import com.abubusoft.kripton.android.Logger;
import com.abubusoft.kripton.common.Pair;
import com.abubusoft.kripton.exception.KriptonRuntimeException;
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
// TODO: Auto-generated Javadoc
/**
*
* Base class for data source
*
* .
*
* @author Francesco Benincasa ([email protected])
*/
public abstract class AbstractDataSource implements AutoCloseable {
/** The on error listener. */
protected OnErrorListener onErrorListener = new OnErrorListener() {
@Override
public void onError(Throwable e) {
throw (new KriptonRuntimeException(e));
}
};
/**
* On session opened.
*/
protected void onSessionOpened() {
this.context.onSessionOpened();
}
/**
* On session closed.
*
* @return the sets the
*/
protected Set onSessionClosed() {
return this.context.onSessionClosed();
}
/**
* Get error listener, in transations.
*
* @return the on error listener
*/
public OnErrorListener getOnErrorListener() {
return onErrorListener;
}
/**
* Set error listener for transactions.
*
* @param onErrorListener
* the new on error listener
*/
public void setOnErrorListener(OnErrorListener onErrorListener) {
this.onErrorListener = onErrorListener;
}
/**
* Interface for database operations.
*
* @param
* the element type
*/
public interface AbstractExecutable {
/**
* Execute transation. Method need to return
* {@link TransactionResult#COMMIT} to commit results or
* {@link TransactionResult#ROLLBACK} to rollback. If exception is
* thrown, a rollback will be done.
*
* @param daoFactory
* the dao factory
* @return the transaction result
*/
TransactionResult onExecute(E daoFactory);
}
/**
* The listener interface for receiving onError events. The class that is
* interested in processing a onError event implements this interface, and
* the object created with that class is registered with a component using
* the component's addOnErrorListener
method. When the onError
* event occurs, that object's appropriate method is invoked.
*
*/
public interface OnErrorListener {
/**
* Manages error situations.
*
* @param e
* exception
*/
void onError(Throwable e);
}
/**
* The Enum TypeStatus.
*/
public static enum TypeStatus {
/** The closed. */
CLOSED,
/** The read and write opened. */
READ_AND_WRITE_OPENED,
/** The read only opened. */
READ_ONLY_OPENED
}
/** database instance. */
SQLiteDatabase database;
/**
* used to clear prepared statements.
*/
public abstract void clearCompiledStatements();
/** The log enabled. */
public boolean logEnabled;
/** The lock access. */
private final ReentrantReadWriteLock lockAccess = new ReentrantReadWriteLock();
/** The lock db. */
private final ReentrantLock lockDb = new ReentrantLock();
/** The lock read access. */
private final Lock lockReadAccess = lockAccess.readLock();
/** The lock read write access. */
private final Lock lockReadWriteAccess = lockAccess.writeLock();
/**
*
* file name used to save database,
*
* .
*/
public final String name;
/** The open counter. */
private AtomicInteger openCounter = new AtomicInteger();
/** The options. */
protected DataSourceOptions options;
/** The sqlite helper. */
protected SQLiteOpenHelper sqliteHelper;
/** The context. */
protected SQLContextImpl context;
/**
* Context.
*
* @return the SQL context
*/
public SQLContext context() {
return context;
}
/**
* Content values for update.
*
* @param compiledStatement
* the compiled statement
* @return the kripton content values
*/
protected KriptonContentValues contentValuesForUpdate(SQLiteStatement compiledStatement) {
return context.contentValuesForUpdate(compiledStatement);
}
/**
* Content values.
*
* @param compiledStatement
* the compiled statement
* @return the kripton content values
*/
protected KriptonContentValues contentValues(SQLiteStatement compiledStatement) {
return context.contentValues(compiledStatement);
}
/**
* Content values for content provider.
*
* @param values
* the values
* @return the kripton content values
*/
protected KriptonContentValues contentValuesForContentProvider(ContentValues values) {
return context.contentValuesForContentProvider(values);
}
/**
* Sql builder.
*
* @return the string builder
*/
protected StringBuilder sqlBuilder() {
return context.sqlBuilder();
}
/**
* Checks if is log enabled.
*
* @return true, if is log enabled
*/
public boolean isLogEnabled() {
return context.isLogEnabled();
}
/** The status. */
protected ThreadLocal status = new ThreadLocal() {
@Override
protected TypeStatus initialValue() {
return TypeStatus.CLOSED;
}
};
/** if true, database was update during this application run. */
protected boolean versionChanged;
/**
*
* True if dataSource is just created
*
* .
*/
protected boolean justCreated = false;
/**
*
* True if dataSource is just created
*
* .
*
* @return true, if is just created
*/
public boolean isJustCreated() {
return justCreated;
}
/**
*
* database version
*
* .
*/
protected int version;
/**
* Gets the version.
*
* @return the version
*/
public int getVersion() {
return version;
}
/**
* Sets the version.
*
* @param version
* the new version
*/
void setVersion(int version) {
this.version = version;
}
/**
* Instantiates a new abstract data source.
*
* @param name
* the name
* @param version
* the version
* @param options
* the options
*/
protected AbstractDataSource(String name, int version, DataSourceOptions options) {
DataSourceOptions optionsValue = (options == null) ? DataSourceOptions.builder().build() : options;
this.name = options.inMemory ? null : name;
this.version = version;
// create new SQLContext
this.context = new SQLContextImpl(this);
this.options = optionsValue;
this.logEnabled = optionsValue.logEnabled;
}
/*
* (non-Javadoc)
*
* @see android.database.sqlite.SQLiteOpenHelper#close()
*/
@Override
public void close() {
lockDb.lock();
try {
if (openCounter.decrementAndGet() <= 0) {
if (!this.options.inMemory) {
// Closing database
if (database != null) {
clearCompiledStatements();
database.close();
}
database = null;
}
if (logEnabled)
Logger.info("database CLOSED (%s) (connections: %s)", status.get(), openCounter.intValue());
} else {
if (logEnabled)
Logger.info("database RELEASED (%s) (connections: %s)", status.get(), openCounter.intValue());
}
} finally {
switch (status.get()) {
case READ_AND_WRITE_OPENED:
if (database == null)
status.set(TypeStatus.CLOSED);
lockReadWriteAccess.unlock();
lockDb.unlock();
break;
case READ_ONLY_OPENED:
if (database == null)
status.set(TypeStatus.CLOSED);
lockReadAccess.unlock();
lockDb.unlock();
break;
case CLOSED:
// do nothing
lockDb.unlock();
break;
default:
lockDb.unlock();
throw (new KriptonRuntimeException("Inconsistent status"));
}
}
}
/**
* Force close.
*/
void forceClose() {
openCounter.set(0);
}
/**
* Builds the task list.
*
* @param previousVersion
* the previous version
* @param currentVersion
* the current version
* @return the list
*/
protected List buildTaskList(int previousVersion, int currentVersion) {
List result = new ArrayList<>();
for (Pair item : this.options.updateTasks) {
if (item.value0 - 1 == previousVersion) {
result.add(item.value1);
previousVersion = item.value0;
}
if (previousVersion == currentVersion)
break;
}
if (previousVersion != currentVersion) {
Logger.warn(String.format("Can not find version update task from version %s to version %s", previousVersion, currentVersion));
}
return result;
}
/**
* Creates the helper.
*
* @param options
* the options
*/
protected void createHelper(DataSourceOptions options) {
if (KriptonLibrary.getContext() == null)
throw new KriptonRuntimeException("Kripton library is not properly initialized. Please use KriptonLibrary.init(context) somewhere at application startup");
if (options.inMemory) {
Logger.info("In-memory database");
} else {
File dbFile=KriptonLibrary.getContext().getDatabasePath(name);
Logger.info("Database file %s", dbFile.getAbsolutePath());
}
sqliteHelper = new SQLiteOpenHelper(KriptonLibrary.getContext(), name, options.factory, version, options.errorHandler) {
@Override
public void onConfigure(SQLiteDatabase database) {
AbstractDataSource.this.database = database;
AbstractDataSource.this.onConfigure(database);
}
@Override
public void onCreate(SQLiteDatabase database) {
AbstractDataSource.this.onCreate(database);
}
@Override
public void onDowngrade(SQLiteDatabase database, int oldVersion, int newVersion) {
AbstractDataSource.this.onDowngrade(database, oldVersion, newVersion);
}
@Override
public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
AbstractDataSource.this.onUpgrade(database, oldVersion, newVersion);
}
};
}
/**
*
* Return database object or runtimeexception if no database is opened.
*
*
* @return the SQ lite database
*/
public SQLiteDatabase database() {
if (database == null)
throw (new KriptonRuntimeException("No database connection is opened before use " + this.getClass().getCanonicalName()));
return database;
}
/**
*
* return true if database is already opened.
*
*
* @return true if database is opened, otherwise false
*/
public boolean isOpen() {
return database != null && database.isOpen() && database.isDbLockedByCurrentThread();
}
/**
*
* return true if database is already opened in write mode.
*
*
* @return true if database is opened, otherwise false
*/
public boolean isOpenInWriteMode() {
// return database != null && database.isOpen() &&
// !database.isReadOnly() && database.isDbLockedByCurrentThread();
return database != null && !database.isReadOnly() && database.isDbLockedByCurrentThread();
}
/**
* Checks if is upgraded version.
*
* @return the upgradedVersion
*/
public boolean isUpgradedVersion() {
return versionChanged;
}
/**
* On configure.
*
* @param database
* the database
*/
public abstract void onConfigure(SQLiteDatabase database);
/**
* On create.
*
* @param database
* the database
*/
public abstract void onCreate(SQLiteDatabase database);
/**
* On downgrade.
*
* @param db
* the db
* @param oldVersion
* the old version
* @param newVersion
* the new version
*/
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (AbstractDataSource.this.options.databaseLifecycleHandler != null) {
AbstractDataSource.this.options.databaseLifecycleHandler.onUpdate(db, oldVersion, newVersion, false);
versionChanged = true;
}
}
/**
* On upgrade.
*
* @param db
* the db
* @param oldVersion
* the old version
* @param newVersion
* the new version
*/
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (AbstractDataSource.this.options.databaseLifecycleHandler != null) {
AbstractDataSource.this.options.databaseLifecycleHandler.onUpdate(db, oldVersion, newVersion, true);
versionChanged = true;
}
}
/**
*
* Open a read only database.
*
*
* @return read only database
*/
public SQLiteDatabase openReadOnlyDatabase() {
lockDb.lock();
try {
if (sqliteHelper == null)
createHelper(options);
status.set(TypeStatus.READ_ONLY_OPENED);
if (openCounter.incrementAndGet() == 1) {
// open new read database
if (database == null) {
sqliteHelper.setWriteAheadLoggingEnabled(true);
database = sqliteHelper.getReadableDatabase();
}
if (logEnabled)
Logger.info("database OPEN %s (connections: %s)", status.get(), (openCounter.intValue() - 1));
} else {
if (logEnabled)
Logger.info("database REUSE %s (connections: %s)", status.get(), (openCounter.intValue() - 1));
}
} finally {
lockDb.unlock();
lockReadAccess.lock();
}
return database;
}
/**
*
* open a writable database.
*
*
* @return writable database
*/
public SQLiteDatabase openWritableDatabase() {
lockDb.lock();
try {
if (sqliteHelper == null)
createHelper(options);
status.set(TypeStatus.READ_AND_WRITE_OPENED);
if (openCounter.incrementAndGet() == 1) {
// open new write database
if (database == null) {
sqliteHelper.setWriteAheadLoggingEnabled(true);
database = sqliteHelper.getWritableDatabase();
}
if (logEnabled)
Logger.info("database OPEN %s (connections: %s)", status.get(), (openCounter.intValue() - 1));
} else {
if (logEnabled)
Logger.info("database REUSE %s (connections: %s)", status.get(), (openCounter.intValue() - 1));
}
} finally {
lockDb.unlock();
lockReadWriteAccess.lock();
}
return database;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy