/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package pdfdb.data.db;
import java.io.File;
import java.io.IOException;
import java.sql.*;
import java.util.Hashtable;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import pdfdb.indexing.plugins.Indexer;
import pdfdb.indexing.plugins.IndexingException;
import pdfdb.indexing.plugins.IndexingProvider;
import pdfdb.structure.*;
/** Provides external access to the database layer. A layer of abstraction
* ensuring external classes never deal with SQL, or connections. This class
* contains SQL and communicates directly with the DatabaseConnection class.
* @author ug22cmg */
public class FileProvider
{
public final static int NO_LIMIT = Integer.MIN_VALUE;
private static IndexableFileList fullList = new IndexableFileList();
private static Hashtable<Integer, IndexableFileList> searchList =
new Hashtable<Integer, IndexableFileList>();
private static ReadWriteLock lock = new ReentrantReadWriteLock();
/** Initializes the file provider by caching the contents of the
* database. This is a slow operation and would be performed in a
* multi-threaded context to allow other operations to continue.
* @throws java.sql.SQLException If an error occurs. */
public static void initialize() throws SQLException
{
for (IndexableFile file : getFiles(NO_LIMIT))
{
addToCache(file);
}
}
/** Gets the total number of files.
* @return The number of files. */
public static int getFileCount()
{
return getList().size();
}
/** Gets the file cache for a specific word id.
* @param wordId The word id.
* @return The cached file list. */
public static IndexableFileList getFilesForSearch(int wordId)
{
return getSearchList().get(wordId);
}
/** Clones the cached files.
* @return An indexable file list instance. */
public static IndexableFileList getCloneOfCache()
{
return (IndexableFileList) getList().clone();
}
/** Deletes a file from the database.
* @param conn The connection to use.
* @param fileName The filename to delete.
* @throws java.sql.SQLException if an error occurs. */
private static void deleteFile(Connection conn, String fileName) throws
SQLException
{
String sql = "DELETE FROM Files WHERE FilePath = ?";
PreparedStatement statement = null;
try
{
removeFromCache(fileName);
removeThumbnails(conn, fileName);
statement = conn.prepareStatement(sql);
statement.setString(1, fileName);
statement.executeUpdate();
}
finally
{
DatabaseConnection.close(statement);
}
}
/** Removes the thumbnail file associated with the specified indexed
* filename if one exists.
* @param conn The connection to use to get required information.
* @param fileName The filename that may have thumbnails associated. */
private static void removeThumbnails(Connection conn, String fileName)
{
try
{
IndexableFile file = getFile(conn, fileName);
Hashtable<String, String> properties = file.getProperties();
String thumbnail = properties.get("THUMBNAIL");
if (thumbnail != null)
{
File f = new File(thumbnail);
if (f.exists()) f.delete();
}
}
catch (Exception e)
{
}
}
/** Removes a file from the cache.
* @param path The path to remove. */
private static void removeFromCache(String path)
{
try
{
IndexableFile file = FileProvider.getFile(path);
if (file != null)
{
getList().remove(path);
for (Region r : file.getRegions())
{
for (Integer wordId : r.getIndexes().keySet())
{
IndexableFileList list = getSearchList().get(wordId);
if (list != null) list.remove(file);
}
}
}
}
catch (SQLException ex)
{
}
}
/** Adds the file into the cache.
* @param file The file to add. */
private static void addToCache(IndexableFile file)
{
getList().add(file);
for (Region r : file.getRegions())
{
for (Integer wordId : r.getIndexes().keySet())
{
IndexableFileList list = getSearchList().get(wordId);
if (list == null)
{
getSearchList().put(wordId, new IndexableFileList());
list = getSearchList().get(wordId);
}
if (!list.contains(file)) list.add(file);
}
}
}
/** Deletes a file from the database using a new connection.
* @param fileName The filename.
* @throws java.sql.SQLException If an error occurs. */
public static void deleteFile(String fileName) throws SQLException
{
Lock l = lock.writeLock();
Connection conn = null;
try
{
l.lock();
conn = DatabaseConnection.getNewConnection();
deleteFile(conn, fileName);
}
finally
{
l.unlock();
DatabaseConnection.close(conn);
}
}
/** Gets a single file from the database.
* @param path The path to get.
* @return The file instance or null.
* @throws SQLException If used after shutdown */
public static IndexableFile getFile(String path) throws SQLException
{
Connection conn = null;
Lock l = lock.readLock();
try
{
l.lock();
conn = DatabaseConnection.getNewConnection();
return getFile(conn, path);
}
finally
{
l.unlock();
DatabaseConnection.close(conn);
}
}
/** Gets a file from the database using the connection specified.
* @param conn The connection to use.
* @param path The path to get.
* @return The file instance or null.
* @throws SQLException If used after shutdown. */
private static IndexableFile getFile(Connection conn, String path) throws
SQLException
{
PreparedStatement statement = null;
ResultSet rs = null;
try
{
final String sql = "SELECT LastIndexed FROM Files WHERE " +
"FilePath = ?";
statement =
conn.prepareStatement(sql);
statement.setString(1, path);
rs =
statement.executeQuery();
if (rs.next())
{
IndexableFile file = new IndexableFile(path, rs.getDate(
"LastIndexed"));
file.setRegions(RegionProvider.getRegions(path));
file.setProperties(PropertyProvider.getProperties(conn, path));
return file;
}
else
return null;
}
catch (SQLException se)
{
return null;
}
finally
{
DatabaseConnection.close(statement, rs);
}
}
/** Gets limit number of files from the database in most recently
* indexed order.
* @param limit Either the number of files to limit the query by
* or NO_LIMIT.
* @return IndexableFileList containing less than or equal to limit
* files.
* @throws java.sql.SQLException If an error occurs. */
public static IndexableFileList getFiles(int limit) throws
SQLException
{
Lock l = lock.readLock();
Connection conn = null;
IndexableFileList list = new IndexableFileList();
Statement statement = null;
ResultSet rs = null;
String sql = "SELECT FilePath, LastIndexed FROM Files " +
"ORDER BY LastIndexed DESC";
try
{
l.lock();
conn = DatabaseConnection.getNewConnection();
statement = conn.createStatement();
if (limit != NO_LIMIT)
statement.setMaxRows(limit);
rs =
statement.executeQuery(sql);
while (rs.next())
{
String path = rs.getString("FilePath");
IndexableFile file = new IndexableFile(path, rs.getDate(
"LastIndexed"));
file.setRegions(RegionProvider.getRegions(path));
file.setProperties(
PropertyProvider.getProperties(conn, path));
list.add(file);
}
}
catch (SQLException se)
{
}
finally
{
l.unlock();
DatabaseConnection.close(statement, rs);
DatabaseConnection.close(conn);
}
return list;
}
/** Indexes a file using the indexer provided by the IndexingProvider.
* @param path The path to index.
* @return The region array that the indexer provides.
* @throws pdfdb.indexing.plugins.IndexingException If the file is
* unindexable. */
private static Region[] indexFile(String path) throws IndexingException
{
Indexer indexer = IndexingProvider.getIndexer(path);
Region[] regions = indexer.index(path);
if (regions == null)
throw new IndexingException();
else
return regions;
}
/** Starts a database transaction.
* @param conn The connection to start a transaction with.
* @throws java.sql.SQLException If there is a problem connecting to the
* database. */
private static void startTransaction(Connection conn) throws SQLException
{
Statement s = null;
try
{
s = conn.createStatement();
s.execute("CHECKPOINT");
}
finally
{
DatabaseConnection.close(s);
}
}
/** Indexes and adds a file to the database.
* @param path The path to add.
* @return The IndexableFile instance or null if unsuccessful */
public static IndexableFile addFile(String path)
{
Lock l = lock.writeLock();
IndexableFile file = null;
Connection conn = null;
try
{
l.lock();
conn = DatabaseConnection.getNewConnection();
conn.setAutoCommit(false);
startTransaction(conn);
deleteFile(conn, path);
java.util.Date d1 = new java.util.Date();
long time = d1.getTime();
java.sql.Date d2 = new java.sql.Date(time);
file = insertFile(conn, path, d2);
file.setProperties(PropertyProvider.getProperties(conn, path));
conn.commit();
addToCache(file);
return file;
}
catch (Throwable ex)
{
try
{
rollback(conn);
}
catch (SQLException ex1)
{
}
return null;
}
finally
{
l.unlock();
DatabaseConnection.close(conn);
}
}
/** Rolls back the database.
* @param conn The connection to rollback.
* @throws java.sql.SQLException If an error occurs. */
private static void rollback(Connection conn) throws SQLException
{
Statement statement = null;
try
{
if (conn != null)
{
statement = conn.createStatement();
statement.execute("ROLLBACK");
}
}
finally
{
if (statement != null) statement.close();
}
}
/** Performs the database insert for adding a file, its regions and
* indexes. The file should not exist before calling this method.
* @param conn The connection object to use.
* @param path The path of the file.
* @param date The date of the indexing.
* @return An instance of the IndexableFile object that represents the data
* that has been added or null if an error occurred.
* @throws java.sql.SQLException If the database cannot be accessed.
* @throws java.io.IOException If the file cannot be read. */
private static IndexableFile insertFile(Connection conn, String path,
Date date)
throws SQLException, IOException, IndexingException
{
String sql = "INSERT INTO Files(FilePath, LastIndexed) VALUES(?, ?)";
PreparedStatement statement = null;
try
{
IndexableFile file = new IndexableFile(path, date);
Region[] regions = null;
statement =
conn.prepareStatement(sql);
statement.setString(1, path);
statement.setDate(2, date);
statement.executeUpdate();
regions =
indexFile(path);
if (regions == null) throw new IndexingException();
file.setRegions(regions);
return file;
}
finally
{
DatabaseConnection.close(statement);
}
}
/** Gets the cached file list in a threadsafe way.
* @return The list. */
private static synchronized IndexableFileList getList()
{
return fullList;
}
/** Gets the cached search list in a threadsafe way.
* @return The list. */
private static synchronized Hashtable<Integer, IndexableFileList> getSearchList()
{
return searchList;
}
}