/*
* Copyright (C) 2012, Katy Hilgenberg.
* Special acknowledgments to: Knowledge & Data Engineering Group, University of Kassel (http://www.kde.cs.uni-kassel.de).
* Contact: sdcf@cs.uni-kassel.de
*
* This file is part of the SDCFramework (Sensor Data Collection Framework) project.
*
* The SDCFramework is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The SDCFramework 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SDCFramework. If not, see <http://www.gnu.org/licenses/>.
*/
package de.unikassel.android.sdcframework.persistence;
import java.util.Collection;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicLong;
import android.content.Context;
import android.database.sqlite.SQLiteFullException;
import de.unikassel.android.sdcframework.app.facade.SDCService;
import de.unikassel.android.sdcframework.data.Sample;
import de.unikassel.android.sdcframework.data.SampleCollection;
import de.unikassel.android.sdcframework.persistence.facade.DatabaseCommand;
import de.unikassel.android.sdcframework.persistence.facade.DatabaseFullStrategy;
import de.unikassel.android.sdcframework.persistence.facade.DatabaseManager;
import de.unikassel.android.sdcframework.persistence.facade.DatabaseSample;
import de.unikassel.android.sdcframework.persistence.facade.PersistentStorageManager;
import de.unikassel.android.sdcframework.preferences.facade.ServiceConfiguration;
import de.unikassel.android.sdcframework.util.AbstractAsynchrounousSampleObserver;
import de.unikassel.android.sdcframework.util.Logger;
/**
* The persistent storage manager does provide the persistent storage feature
* for the framework. <br/>
* <br/>
* It does extend the {@linkplain AbstractAsynchrounousSampleObserver} to cache
* observed samples for further asynchronous processing.
*
* @see de.unikassel.android.sdcframework.service.ServiceManagerImpl
* @author Katy Hilgenberg
*
*/
public final class PersistentStorageManagerImpl
extends AbstractAsynchrounousSampleObserver
implements PersistentStorageManager
{
/**
* The actual count of records stored to database in the actual work period
*/
private final AtomicLong savedRecordCount;
/**
* The database manager used for database access
*/
private final DatabaseManager dbManager;
/**
* The strategy to use in case of SQLiteFullException
*/
private DatabaseFullStrategy dbFullStrategy;
/**
* The last not finished database command
*/
private InsertSamplesCommand currentCommand;
/**
* The service class
*/
private final Class< ? extends SDCService > serviceClass;
/**
* Constructor
*
* @param applicationContext
* the application context
* @param config
* the service configuration
* @param dbManager
* the database manager to use
* @param serviceClass
* the service class
*/
public PersistentStorageManagerImpl( Context applicationContext,
ServiceConfiguration config, DatabaseManager dbManager,
Class< ? extends SDCService > serviceClass )
{
super();
if ( applicationContext == null )
throw new IllegalArgumentException( "context is null" );
if ( dbManager == null )
throw new IllegalArgumentException( "dbManager is null" );
if ( config == null )
throw new IllegalArgumentException( "config is null" );
if ( serviceClass == null )
throw new IllegalArgumentException( "serviceClass is null" );
this.serviceClass = serviceClass;
this.dbManager = dbManager;
this.savedRecordCount = new AtomicLong( 0L );
updateDatabaseFullStrategy( applicationContext, config );
}
/*
* (non-Javadoc)
*
* @see
* de.unikassel.android.sdcframework.persistence.facade.PersistentStorageManager
* #updateDatabaseFullStrategy(android.content.Context,
* de.unikassel.android.sdcframework.preferences.facade.ServiceConfiguration)
*/
@Override
public final synchronized void updateDatabaseFullStrategy( Context context,
ServiceConfiguration config )
{
this.dbFullStrategy = DatabaseFullStrategyBuilder.buildStrategy(
context, config, serviceClass );
}
/*
* (non-Javadoc)
*
* @see de.unikassel.android.sdcframework.util.AbstractWorkerThread#doWork()
*/
@Override
protected final void doWork()
{
try
{
if ( currentCommand == null )
{
// take samples from queue and process
SampleCollection samples = new SampleCollection();
if ( collector.dequeue( samples, Integer.MAX_VALUE ) > 0 )
{
currentCommand =
new InsertSamplesCommand( convertSamplesToDBSamples( samples ) );
}
else
sleep( 1000 );
}
// try to process last insert command
doExecuteCurrentCommand();
}
catch ( SQLiteFullException e )
{
doHandleDatabaseSizeLimitExceeded();
}
catch ( InterruptedException e )
{}
catch ( Exception e )
{
Logger.getInstance().error( this,
"Exception in doWork: " + e.getMessage() );
e.printStackTrace();
}
}
/**
* Conversion of samples to database samples
*
* @param samples
* the sample collection to convert to a database sample collection
* @return a collection of the converted samples
*/
private final Collection< DatabaseSample > convertSamplesToDBSamples(
SampleCollection samples )
{
Collection< DatabaseSample > dbSamples =
new Vector< DatabaseSample >();
for ( Sample sample : samples )
{
try
{
dbSamples.add( new DatabaseSample( sample ) );
}
catch ( Exception e )
{
Logger.getInstance().warning( this,
"failed to convert sample to database format: " + e.getMessage() );
e.printStackTrace();
}
}
return dbSamples;
}
/*
* (non-Javadoc)
*
* @see
* de.unikassel.android.sdcframework.persistence.facade.PersistentStorageManager
* #doExecuteCurrentCommand()
*/
@Override
public synchronized final boolean doExecuteCurrentCommand()
{
if ( currentCommand == null )
return true;
Boolean success = doExecuteCommand( currentCommand );
if ( success != null )
{
if ( success )
{
// update current instance count of saved records
long cnt = currentCommand.getSamples().size();
long recordCount;
do
{
recordCount = savedRecordCount.get();
}
while ( !savedRecordCount.compareAndSet( recordCount, recordCount + cnt ) );
currentCommand = null;
}
return success;
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* de.unikassel.android.sdcframework.persistence.facade.PersistentStorageManager
* #deleteOldestSamplesInDatabase(long, boolean)
*/
@Override
public synchronized final long doDeleteOldestSamplesInDatabase( long count,
boolean lowestPriorityFirst )
{
return dbManager.doDeleteOldestSamplesInDatabase( count,
lowestPriorityFirst );
}
/**
* Handler for the SQL database full exception
*/
private synchronized final void doHandleDatabaseSizeLimitExceeded()
{
Logger.getInstance().debug( this,
"Total record count in database: " + getRecordCountInDatabase() );
Logger.getInstance().error( this,
"Database size limit exceeded. Applying strategy!" );
if ( !dbFullStrategy.doWork( this ) )
{
Logger.getInstance().error( this,
"Database full can not be resolved. Stopping work!" );
stopWork();
}
}
/*
* (non-Javadoc)
*
* @see
* de.unikassel.android.sdcframework.util.facade.LifeCycleObject#onResume(
* android.content.Context)
*/
@Override
public final void onResume( Context applicationContext )
{
savedRecordCount.set( 0L );
Logger.getInstance().debug( this,
"Total record count in database: " + getRecordCountInDatabase() );
super.onResume( applicationContext );
}
/*
* (non-Javadoc)
*
* @see
* de.unikassel.android.sdcframework.util.facade.LifeCycleObject#onPause(android
* .content.Context)
*/
@Override
public final void onPause( Context applicationContext )
{
super.onPause( applicationContext );
int eventCount = collector.getEventCount();
if ( eventCount > 0 )
{
Logger.getInstance().warning( this,
"" + eventCount + " unsaved samples in queue!" );
}
long savedRecordCount = getSavedRecordCount();
if ( savedRecordCount > 0L )
{
Logger.getInstance().debug( this,
"" + savedRecordCount + " sample(s) stored persistent!" );
}
Logger.getInstance().debug( this,
"Total record count in database: " + getRecordCountInDatabase() );
}
/*
* (non-Javadoc)
*
* @see
* de.unikassel.android.sdcframework.persistence.facade.PersistentStorageManager
* #getSavedRecordCount()
*/
@Override
public final long getSavedRecordCount()
{
return savedRecordCount.get();
}
/*
* (non-Javadoc)
*
* @see de.unikassel.android.sdcframework.persistence.facade.DatabaseManager#
* getSampleCountInDatabase()
*/
@Override
public synchronized final long getRecordCountInDatabase()
{
return dbManager.getRecordCountInDatabase();
}
/*
* (non-Javadoc)
*
* @see de.unikassel.android.sdcframework.persistence.facade.DatabaseManager#
* getMaximumDatabaseSize()
*/
@Override
public synchronized final long getMaximumDatabaseSize()
{
return dbManager.getMaximumDatabaseSize();
}
/*
* (non-Javadoc)
*
* @see de.unikassel.android.sdcframework.persistence.facade.DatabaseManager#
* setMaximumDatabaseSize(long)
*/
@Override
public synchronized final long setMaximumDatabaseSize( long size )
{
return dbManager.setMaximumDatabaseSize( size );
}
/*
* (non-Javadoc)
*
* @see de.unikassel.android.sdcframework.persistence.facade.DatabaseManager#
* doExecuteCommand
* (de.unikassel.android.sdcframework.persistence.facade.DatabaseCommand)
*/
@Override
public < T > T doExecuteCommand( DatabaseCommand< T > command )
{
return dbManager.doExecuteCommand( command );
}
}