/*
* Copyright (C) 2006-2008 Alfresco Software Limited.
*
* 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.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.jlan.server.filesys.db.oracle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.server.config.InvalidConfigurationException;
import org.alfresco.jlan.server.filesys.FileAttribute;
import org.alfresco.jlan.server.filesys.FileExistsException;
import org.alfresco.jlan.server.filesys.FileInfo;
import org.alfresco.jlan.server.filesys.FileName;
import org.alfresco.jlan.server.filesys.FileOpenParams;
import org.alfresco.jlan.server.filesys.FileStatus;
import org.alfresco.jlan.server.filesys.FileType;
import org.alfresco.jlan.server.filesys.cache.FileState;
import org.alfresco.jlan.server.filesys.db.DBDataDetails;
import org.alfresco.jlan.server.filesys.db.DBDataDetailsList;
import org.alfresco.jlan.server.filesys.db.DBDataInterface;
import org.alfresco.jlan.server.filesys.db.DBDeviceContext;
import org.alfresco.jlan.server.filesys.db.DBException;
import org.alfresco.jlan.server.filesys.db.DBFileInfo;
import org.alfresco.jlan.server.filesys.db.DBInterface;
import org.alfresco.jlan.server.filesys.db.DBObjectIdInterface;
import org.alfresco.jlan.server.filesys.db.DBQueueInterface;
import org.alfresco.jlan.server.filesys.db.DBSearchContext;
import org.alfresco.jlan.server.filesys.db.JdbcDBInterface;
import org.alfresco.jlan.server.filesys.db.ObjectIdFileLoader;
import org.alfresco.jlan.server.filesys.db.RetentionDetails;
import org.alfresco.jlan.server.filesys.loader.CachedFileInfo;
import org.alfresco.jlan.server.filesys.loader.FileRequest;
import org.alfresco.jlan.server.filesys.loader.FileRequestQueue;
import org.alfresco.jlan.server.filesys.loader.FileSegment;
import org.alfresco.jlan.server.filesys.loader.FileSegmentInfo;
import org.alfresco.jlan.server.filesys.loader.MultipleFileRequest;
import org.alfresco.jlan.server.filesys.loader.SingleFileRequest;
import org.alfresco.jlan.smb.server.ntfs.StreamInfo;
import org.alfresco.jlan.smb.server.ntfs.StreamInfoList;
import org.alfresco.jlan.util.MemorySize;
import org.alfresco.jlan.util.StringList;
import org.alfresco.jlan.util.WildCard;
import org.alfresco.jlan.util.db.DBConnectionPool;
import org.springframework.extensions.config.ConfigElement;
/**
* Oracle Base Database Interface Class
*
* <p>Oracle specific implementation of the database interface used by the database filesystem driver (DBDiskDriver).
*
* @author gkspencer
*/
public class OracleDBInterface extends JdbcDBInterface implements DBQueueInterface, DBDataInterface, DBObjectIdInterface {
// Constants
//
// Default database table names
public final static String DefaultDBPrefix = "JLAN";
public final static String OraFileSysTable = "FileSys";
public final static String OraStreamsTable = "Streams";
public final static String OraRetentionTable = "Retain";
public final static String OraQueueTable = "Queue";
public final static String OraTransactQueueTable = "TransQueue";
public final static String OraDataTable = "Data";
public final static String OraJarDataTable = "JarData";
public final static String OraObjectIdTable = "ObjectIds";
public final static String OraSymLinkTable = "SymLinks";
// Memory buffer maximum size
public final static long MaxMemoryBuffer = MemorySize.MEGABYTE / 2; // 1/2Mb
// Lock file name, used to check if server shutdown was clean or not
public final static String LockFileName = "OracleLoader.lock";
// Database connection and prepared statement used to write file requests to the queue tables
private Connection m_dbConn;
private PreparedStatement m_reqStmt;
private PreparedStatement m_tranStmt;
// Database table prefix
private String m_dbTablePrefix = DefaultDBPrefix;
/**
* Default constructor
*/
public OracleDBInterface() {
super();
}
/**
* Return the database interface name
*
* @return String
*/
public String getDBInterfaceName() {
return "Oracle";
}
/**
* Get the supported database features mask
*
* @return int
*/
protected int getSupportedFeatures() {
// Determine the available database interface features
return FeatureNTFS + FeatureRetention + FeatureSymLinks + FeatureQueue + FeatureData + FeatureJarData + FeatureObjectId;
}
/**
* Return the database table prefix
*
* @return String
*/
public final String getTablePrefix() {
return m_dbTablePrefix;
}
/**
* Initialize the database interface
*
* @param dbCtx DBDeviceContext
* @param params ConfigElement
* @exception InvalidConfigurationException
*/
public void initializeDatabase(DBDeviceContext dbCtx, ConfigElement params)
throws InvalidConfigurationException {
// Set the JDBC driver class, must be set before the connection pool is created
setDriverName("oracle.jdbc.driver.OracleDriver");
// Save the device context
m_dbCtx = dbCtx;
// Parse the standard JDBC database interface parameters
ConfigElement nameVal = null;
nameVal = params.getChild("DSN");
if ( nameVal != null)
m_dsn = nameVal.getValue();
nameVal = params.getChild("Username");
if ( nameVal != null)
m_userName = nameVal.getValue();
nameVal = params.getChild("Password");
if ( nameVal != null)
m_password = nameVal.getValue();
nameVal = params.getChild( "TablePrefix");
if ( nameVal != null) {
m_dbTablePrefix = nameVal.getValue();
if ( m_dbTablePrefix == null || m_dbTablePrefix.length() == 0)
throw new InvalidConfigurationException( "Invalid database table prefix, empty string");
else if ( m_dbTablePrefix.length() > 15)
throw new InvalidConfigurationException( "Invalid database table prefix, too long");
// Validate the database prefix characters, only allow A-Z, a-z, 0-9 and underscore
for ( int i = 0; i < m_dbTablePrefix.length(); i++) {
char curChar = m_dbTablePrefix.charAt( i);
if ( curChar != '_' && Character.isDigit( curChar) == false && Character.isLetter( curChar) == false)
throw new InvalidConfigurationException( "Invalid character in database table prefix, '" + curChar + "'");
}
}
nameVal = params.getChild("FileSystemTable");
if ( nameVal != null)
m_structTable = nameVal.getValue();
else
m_structTable = getTablePrefix() + OraFileSysTable;
nameVal = params.getChild("StreamsTable");
if ( nameVal != null)
m_streamTable = nameVal.getValue();
else
m_streamTable = getTablePrefix() + OraStreamsTable;
nameVal = params.getChild("RetentionTable");
if ( nameVal != null)
m_retentionTable = nameVal.getValue();
else
m_retentionTable = getTablePrefix() + OraRetentionTable;
nameVal = params.getChild("QueueTable");
if ( nameVal != null)
m_queueTable = nameVal.getValue();
else
m_queueTable = getTablePrefix() + OraQueueTable;
nameVal = params.getChild("TransactQueueTable");
if ( nameVal != null)
m_transactTable = nameVal.getValue();
else
m_transactTable = getTablePrefix() + OraTransactQueueTable;
nameVal = params.getChild("DataTable");
if ( nameVal != null)
m_dataTable = nameVal.getValue();
else
m_dataTable = getTablePrefix() + OraDataTable;
nameVal = params.getChild("JarDataTable");
if ( nameVal != null)
m_jarDataTable = nameVal.getValue();
else
m_jarDataTable = getTablePrefix() + OraJarDataTable;
nameVal = params.getChild("ObjectIdTable");
if ( nameVal != null)
m_objectIdTable = nameVal.getValue();
else
m_objectIdTable = getTablePrefix() + OraObjectIdTable;
nameVal = params.getChild( "SymLinksTable");
if ( nameVal != null)
m_symLinkTable = nameVal.getValue();
else
m_symLinkTable = getTablePrefix() + OraSymLinkTable;
// Check if the database connection pool initial and maximum size has been specified
nameVal = params.getChild("ConnectionPool");
if ( nameVal != null) {
try {
// Check for a single value or split initial/maximum values
String numVal = nameVal.getValue();
int pos = numVal.indexOf(':');
if ( pos == -1) {
// Use the same number of read and write worker threads
m_dbMaxConns = Integer.parseInt(numVal);
}
else {
// Split the string value into read and write values, and convert to integers
String val = numVal.substring(0,pos);
m_dbInitConns = Integer.parseInt(val);
val = numVal.substring(pos + 1);
m_dbMaxConns = Integer.parseInt(val);
}
// Range check the initial/maximum connection pool sizes
if ( m_dbInitConns < DBConnectionPool.MinimumConnections ||
m_dbInitConns > m_dbMaxConns)
throw new InvalidConfigurationException("Database interface invalid initial connections value");
if ( m_dbMaxConns > DBConnectionPool.MaximumConnections ||
m_dbMaxConns < m_dbInitConns)
throw new InvalidConfigurationException("Database interface invalid maximum connections value");
}
catch (NumberFormatException ex) {
throw new InvalidConfigurationException("Database interface invalid ConnectionPool value, " + ex.toString());
}
}
// Check if the database online check interval has been specified
nameVal = params.getChild("OnlineCheckInterval");
if ( nameVal != null) {
try {
// Parse the online check interval value
m_onlineCheckInterval = Integer.parseInt( nameVal.getValue());
if ( m_onlineCheckInterval < 1 || m_onlineCheckInterval > 30)
throw new InvalidConfigurationException( "Database online check interval out of valid range (1-30");
}
catch ( NumberFormatException ex) {
throw new InvalidConfigurationException("Database online check interval value invalid, " + nameVal.getValue());
}
}
// Check if debug output is enabled
if ( params.getChild("Debug") != null)
m_debug = true;
// Check if SQL debug output is enabled
if ( params.getChild("SQLDebug") != null)
m_sqlDebug = true;
// Copy the retention period from the context, value will be -1 if not enabled
m_retentionPeriod = dbCtx.getRetentionPeriod();
// Create the database connection pool
try {
createConnectionPool();
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Error creating connection pool, " + ex.toString());
// Rethrow the exception
throw new InvalidConfigurationException("Failed to create connection pool, " + ex.getMessage());
}
// Check if the file system table exists
Connection conn = null;
try {
// Open a connection to the database
conn = getConnection();
DatabaseMetaData dbMeta = conn.getMetaData();
ResultSet rs = dbMeta.getTables(null, null, null, null);
boolean foundStruct = false;
boolean foundStream = false;
boolean foundRetain = false;
boolean foundQueue = false;
boolean foundTrans = false;
boolean foundData = false;
boolean foundJarData = false;
boolean foundObjId = false;
boolean foundSymLink = false;
while (rs.next()) {
// Get the table name
String tblName = rs.getString("TABLE_NAME");
// Check if we found the filesystem structure or streams table
if (tblName.equalsIgnoreCase(getFileSysTableName()))
foundStruct = true;
else if ( hasStreamsTableName() && tblName.equalsIgnoreCase(getStreamsTableName()))
foundStream = true;
else if ( hasRetentionTableName() && tblName.equalsIgnoreCase(getRetentionTableName()))
foundRetain = true;
else if ( hasDataTableName() && tblName.equalsIgnoreCase(getDataTableName()))
foundData = true;
else if ( hasJarDataTableName() && tblName.equalsIgnoreCase(getJarDataTableName()))
foundJarData = true;
else if ( hasQueueTableName() && tblName.equalsIgnoreCase(getQueueTableName()))
foundQueue = true;
else if ( hasTransactionTableName() && tblName.equalsIgnoreCase(getTransactionTableName()))
foundTrans = true;
else if ( hasObjectIdTableName() && tblName.equalsIgnoreCase(getObjectIdTableName()))
foundObjId = true;
else if ( hasSymLinksTableName() && tblName.equalsIgnoreCase( getSymLinksTableName()))
foundSymLink = true;
}
// Check if the file system structure table should be created
if (foundStruct == false) {
// Create the file system structure table
Statement stmt = conn.createStatement();
stmt.execute("CREATE SEQUENCE " + getTablePrefix() + "FileId INCREMENT BY 1 START WITH 1 NOMAXVALUE NOCYCLE");
stmt.execute("CREATE TABLE " + getFileSysTableName()
+ " (FileId NUMBER PRIMARY KEY, DirId NUMBER, FileName VARCHAR2(255) NOT NULL, FileSize NUMBER,"
+ "CreateDate TIMESTAMP, ModifyDate TIMESTAMP, AccessDate TIMESTAMP, ChangeDate TIMESTAMP, ReadOnlyFile NUMBER(1),"
+ "ArchivedFile NUMBER(1), DirectoryFile NUMBER(1), SystemFile NUMBER(1), HiddenFile NUMBER(1), IsSymLink NUMBER(1),"
+ " OwnerUid NUMBER(6), OwnerGid NUMBER(6), FileMode NUMBER(6), IsDeleted NUMBER(1) DEFAULT 0)");
// Create various indexes
stmt.execute("CREATE INDEX " + getTablePrefix() + "IFileDirId ON " + getFileSysTableName() + "(FileName, DirId)");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IDirId ON " + getFileSysTableName() + "(DirId)");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IDir ON " + getFileSysTableName() + "(DirId,DirectoryFile)");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IFileDirIdDir ON " + getFileSysTableName() + "(FileName,DirId,DirectoryFile)");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getFileSysTableName());
}
// Check if the file streams table should be created
if ( isNTFSEnabled() && foundStream == false && getStreamsTableName() != null) {
// Create the file streams table
Statement stmt = conn.createStatement();
stmt.execute("CREATE SEQUENCE " + getTablePrefix() + "StreamId INCREMENT BY 1 START WITH 1 NOMAXVALUE NOCYCLE");
stmt.execute("CREATE TABLE " + getStreamsTableName()
+ " (StreamId NUMBER, FileId NUMBER PRIMARY KEY, StreamName VARCHAR2(255) NOT NULL, StreamSize NUMBER,"
+ "CreateDate TIMESTAMP, ModifyDate TIMESTAMP, AccessDate TIMESTAMP)");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getStreamsTableName());
}
// Check if the retention table should be created
if ( isRetentionEnabled() && foundRetain == false && getRetentionTableName() != null) {
// Create the retention period data table
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE " + getRetentionTableName() + " (FileId NUMBER NOT NULL PRIMARY KEY,"
+ "StartDate TIMESTAMP, EndDate TIMESTAMP, PurgeFlag CHAR(1))");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getRetentionTableName());
}
// Check if the file loader queue table should be created
if ( isQueueEnabled() && foundQueue == false && getQueueTableName() != null) {
// Create the request queue data table
Statement stmt = conn.createStatement();
stmt.execute("CREATE SEQUENCE " + getTablePrefix() + "SeqNo INCREMENT BY 1 START WITH 1 NOMAXVALUE NOCYCLE");
stmt.execute("CREATE TABLE " + getQueueTableName() + " (FileId NUMBER NOT NULL, StreamId INTEGER NOT NULL, ReqType NUMBER(2)," +
"SeqNo NUMBER PRIMARY KEY, TempFile VARCHAR2(512), VirtualPath VARCHAR2(512), QueuedAt TIMESTAMP, Attribs VARCHAR(512))");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IFileId ON " + getQueueTableName() + "(FileId)");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IFileIdType ON " + getQueueTableName() + "(FileId, ReqType)");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getQueueTableName());
}
// Check if the file loader transaction queue table should be created
if ( isQueueEnabled() && foundTrans == false && getTransactionTableName() != null) {
// Create the transaction request queue data table
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE " + getTransactionTableName() + " (FileId NUMBER NOT NULL, StreamId NUMBER NOT NULL," +
"TranId NUMBER NOT NULL, ReqType NUMBER(2), TempFile VARCHAR2(512), VirtualPath VARCHAR2(512), QueuedAt TIMESTAMP," +
"Attribs VARCHAR(512), PRIMARY KEY (FileId,TranId))");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getTransactionTableName());
}
// Check if the file data table should be created
if ( isDataEnabled() && foundData == false && hasDataTableName()) {
// Create the file data table
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE " + getDataTableName() + " (FileId NUMBER NOT NULL, StreamId NUMBER NOT NULL, FragNo NUMBER, FragLen NUMBER, Data BLOB, JarFile CHAR(1), JarId NUMBER)");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IDataFidStid ON " + getDataTableName() + " (FileId,StreamId)");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IDataFileId ON " + getDataTableName() + " (FileId)");
stmt.execute("CREATE INDEX " + getTablePrefix() + "IDataFidFragNo ON " + getDataTableName() + " (FileId,FragNo)");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getDataTableName());
}
// Check if the Jar file data table should be created
if ( isJarDataEnabled() && foundJarData == false && hasJarDataTableName()) {
// Create the Jar file data table
Statement stmt = conn.createStatement();
stmt.execute("CREATE SEQUENCE " + getTablePrefix() + "JarId INCREMENT BY 1 START WITH 1 NOMAXVALUE NOCYCLE");
stmt.execute("CREATE TABLE " + getJarDataTableName() + " (JarId NUMBER PRIMARY KEY, Data BLOB)");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getJarDataTableName());
}
// Check if the file id/object id mapping table should be created
if ( isObjectIdEnabled() && foundObjId == false && hasObjectIdTableName()) {
// Create the file id/object id mapping table
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE " + getObjectIdTableName() + " (FileId NUMBER NOT NULL PRIMARY KEY, StreamId NUMBER NOT NULL, ObjectId VARCHAR2(128))");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getObjectIdTableName());
}
// Check if the symbolic links table should be created
if ( isSymbolicLinksEnabled() && foundSymLink == false && hasSymLinksTableName()) {
// Create the symbolic links table
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE " + getSymLinksTableName() + " (FileId NUMBER NOT NULL PRIMARY KEY, SymLink VARCHAR(2048))");
stmt.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created table " + getSymLinksTableName());
}
}
catch (Exception ex) {
Debug.println("Error: " + ex.toString());
}
finally {
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Check if a file/folder exists
*
* @param dirId int
* @param fname String
* @return FileStatus.NotExist, FileStatus.FileExists or FileStatus.DirectoryExists
* @throws DBException
*/
public int fileExists(int dirId, String fname)
throws DBException {
// Check if the file exists, and whether it is a file or folder
int sts = FileStatus.NotExist;
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database, create a statement for the database lookup
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT FileName,DirectoryFile FROM " + getFileSysTableName() + " WHERE DirId = " + dirId +
" AND FileName = '" + checkNameForSpecialChars(fname) + "'";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] File exists SQL: " + sql);
// Search for the file/folder
ResultSet rs = stmt.executeQuery(sql);
// Check if a file record exists
if (rs.next()) {
// Check if the record is for a file or folder
if ( rs.getBoolean("DirectoryFile") == true)
sts = FileStatus.DirectoryExists;
else
sts = FileStatus.FileExists;
}
// Close the result set
rs.close();
}
catch (Exception ex) {
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the status
return sts;
}
/**
* Create a file record for a new file or folder
*
* @param fname String
* @param dirId int
* @param params FileOpenParams
* @param retain boolean
* @return int
* @exception DBException
* @exception FileExistsException
*/
public int createFileRecord(String fname, int dirId, FileOpenParams params, boolean retain)
throws DBException, FileExistsException {
// Create a new file record for a file/folder and return a unique file id
Connection conn = null;
PreparedStatement pstmt = null;
Statement stmt = null;
int fileId = -1;
try {
// Get a database connection
conn = getConnection();
// Check if the file already exists in the database
stmt = conn.createStatement();
String chkFileName = checkNameForSpecialChars(fname);
String qsql = "SELECT FileName FROM " + getFileSysTableName() + " WHERE FileName = '" + chkFileName + "' AND DirId = " + dirId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Create file SQL: " + qsql);
// Check if the file/folder already exists
ResultSet rs = stmt.executeQuery(qsql);
if (rs.next())
throw new FileExistsException();
// Get a statement
Timestamp timeNow = new Timestamp(System.currentTimeMillis());
pstmt = conn.prepareStatement("INSERT INTO " + getFileSysTableName()
+ "(FileName,CreateDate,ModifyDate,AccessDate,DirId,DirectoryFile,ReadOnlyFile,"
+ "ArchivedFile,SystemFile,HiddenFile,FileSize,OwnerGid,OwnerUid,FileMode,IsSymLink,FileId)"
+ " VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?," + getTablePrefix() + "FileId.NEXTVAL)");
pstmt.setString(1,chkFileName);
pstmt.setTimestamp(2, timeNow);
pstmt.setTimestamp(3, timeNow);
pstmt.setTimestamp(4, timeNow);
pstmt.setInt(5,dirId);
pstmt.setBoolean(6, params.isDirectory());
pstmt.setBoolean(7, FileAttribute.hasAttribute(params.getAttributes(),FileAttribute.ReadOnly));
pstmt.setBoolean(8, FileAttribute.hasAttribute(params.getAttributes(),FileAttribute.Archive));
pstmt.setBoolean(9, FileAttribute.hasAttribute(params.getAttributes(),FileAttribute.System));
pstmt.setBoolean(10, FileAttribute.hasAttribute(params.getAttributes(),FileAttribute.Hidden));
pstmt.setInt(11, 0);
pstmt.setInt(12, params.hasGid() ? params.getGid() : 0);
pstmt.setInt(13, params.hasUid() ? params.getUid() : 0);
pstmt.setInt(14, params.hasMode() ? params.getMode() : 0);
pstmt.setInt(15, params.isSymbolicLink() ? 1 : 0);
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Create file SQL: " + pstmt.toString());
// Create an entry for the new file
if (pstmt.executeUpdate() > 0) {
// Get the last insert id
ResultSet rs2 = stmt.executeQuery("SELECT " + getTablePrefix() + "FileId.CURRVAL FROM DUAL");
if ( rs2.next())
fileId = rs2.getInt(1);
// Check if the returned file id is valid
if ( fileId == -1)
throw new DBException("Failed to get file id for " + fname);
// If retention is enabled then create a retention record for the new file/folder
if ( retain == true && isRetentionEnabled()) {
// Create a retention record for the new file/directory
Timestamp startDate = new Timestamp(System.currentTimeMillis());
Timestamp endDate = new Timestamp(startDate.getTime() + getRetentionPeriod());
String rSql = "INSERT INTO " + getRetentionTableName() + " (FileId,StartDate,EndDate) VALUES (" + fileId + ",'" + startDate.toString() + "','" + endDate.toString() + "')";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Add retention record SQL: " + rSql);
// Add the retention record for the file/folder
stmt.executeUpdate(rSql);
}
// Check if the new file is a symbolic link
if ( params.isSymbolicLink()) {
// Create the symbolic link record
String symSql = "INSERT INTO " + getSymLinksTableName() + " (FileId, SymLink) VALUES (" + fileId + ",'" + params.getSymbolicLinkName() + "')";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Create symbolic link SQL: " + symSql);
// Add the symbolic link record
stmt.executeUpdate( symSql);
}
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Created file name=" + fname + ", dirId=" + dirId + ", fileId=" + fileId);
}
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Create file record error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the prepared statement
if (pstmt != null) {
try {
pstmt.close();
}
catch (Exception ex) {
}
}
// Close the query statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the allocated file id
return fileId;
}
/**
* Create a stream record for a new file stream
*
* @param sname String
* @param fid int
* @return int
* @exception DBException
*/
public int createStreamRecord(String sname, int fid)
throws DBException {
// Create a new file stream attached to the specified file
Connection conn = null;
PreparedStatement stmt = null;
Statement stmt2 = null;
int streamId = -1;
try {
// Get a database connection
conn = getConnection();
// Get a statement
long timeNow = System.currentTimeMillis();
stmt = conn.prepareStatement("INSERT INTO " + getFileSysTableName()
+ "(FileId,StreamName,CreateDate,ModifyDate,AccessDate,StreamSize,StreamId) VALUES (?,?,?,?,?,?," + getTablePrefix() +"StreamId.NEXTVAL)");
stmt.setInt(1, fid);
stmt.setString(2, sname);
stmt.setLong(3, timeNow);
stmt.setLong(4, timeNow);
stmt.setLong(5, timeNow);
stmt.setInt(6, 0);
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Create stream SQL: " + stmt.toString());
// Create an entry for the new stream
if (stmt.executeUpdate() > 0) {
// Get the stream id for the newly created stream
stmt2 = conn.createStatement();
ResultSet rs2 = stmt2.executeQuery("SELECT " + getTablePrefix() + "StreamId.CURRVAL FROM DUAL");
if ( rs2.next())
streamId = rs2.getInt(1);
rs2.close();
}
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Create file stream error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statements
if (stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
if ( stmt2 != null) {
try {
stmt2.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the allocated stream id
return streamId;
}
/**
* Delete a file or folder record
*
* @param dirId int
* @param fid int
* @param markOnly boolean
* @exception DBException
*/
public void deleteFileRecord(int dirId, int fid, boolean markOnly)
throws DBException {
// Delete a file record from the database, or mark the file record as deleted
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
// Delete the file entry from the database
stmt = conn.createStatement();
String sql = null;
if ( markOnly == true)
sql = "UPDATE " + getFileSysTableName() + " SET IsDeleted = 1 WHERE FileId = " + fid;
else
sql = "DELETE FROM " + getFileSysTableName() + " WHERE FileId = " + fid;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Delete file SQL: " + sql);
// Delete the file/folder, or mark as deleted
stmt.executeUpdate(sql);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Delete file error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if (conn != null)
releaseConnection(conn);
}
}
/**
* Delete a file stream record
*
* @param fid int
* @param stid int
* @param markOnly boolean
* @exception DBException
*/
public void deleteStreamRecord(int fid, int stid, boolean markOnly)
throws DBException {
// Delete a file stream from the database, or mark the stream as deleted
Connection conn = null;
Statement stmt = null;
try {
// Get a database connection
conn = getConnection();
// Get a statement
stmt = conn.createStatement();
String sql = "DELETE FROM " + getStreamsTableName() + " WHERE FileId = " + fid +
" AND StreamId = " + stid;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Delete stream SQL: " + sql);
// Delete the stream record
stmt.executeUpdate(sql);
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Delete stream error: " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if (stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Set file information for a file or folder
*
* @param dirId int
* @param fid int
* @param finfo FileInfo
* @exception DBException
*/
public void setFileInformation(int dirId, int fid, FileInfo finfo)
throws DBException {
// Set file information fields
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
// Build the SQL statement to update the file information settings
StringBuffer sql = new StringBuffer(256);
sql.append("UPDATE ");
sql.append(getFileSysTableName());
sql.append(" SET ");
// Check if the file attributes have been updated
if ( finfo.hasSetFlag(FileInfo.SetAttributes)) {
// Update the basic file attributes
sql.append("ReadOnlyFile = ");
sql.append(finfo.isReadOnly() ? "1" : "0");
sql.append(", ArchivedFile =");
sql.append(finfo.isArchived() ? "1" : "0");
sql.append(", SystemFile = ");
sql.append(finfo.isSystem() ? "1" : "0");
sql.append(", HiddenFile = ");
sql.append(finfo.isHidden() ? "1" : "0");
sql.append(",");
}
// Check if the file size should be set
if ( finfo.hasSetFlag(FileInfo.SetFileSize)) {
// Update the file size
sql.append("FileSize = ");
sql.append(finfo.getSize());
sql.append(",");
}
// Merge the group id, user id and mode into the in-memory file information
if ( finfo.hasSetFlag(FileInfo.SetGid)) {
// Update the group id
sql.append("Gid = ");
sql.append(finfo.getGid());
sql.append(",");
}
if ( finfo.hasSetFlag(FileInfo.SetUid)) {
// Update the user id
sql.append("Uid = ");
sql.append(finfo.getUid());
sql.append(",");
}
if ( finfo.hasSetFlag(FileInfo.SetMode)) {
// Update the mode
sql.append("FileMode = ");
sql.append(finfo.getMode());
sql.append(",");
}
// Check if the access date/time has been set
if ( finfo.hasSetFlag(FileInfo.SetAccessDate)) {
// Add the SQL to update the access date/time
sql.append(" AccessDate = TIMESTAMP'");
sql.append(new Timestamp(finfo.getAccessDateTime()));
sql.append("',");
}
// Check if the modify date/time has been set
if ( finfo.hasSetFlag(FileInfo.SetModifyDate)) {
// Add the SQL to update the modify date/time
sql.append(" ModifyDate = TIMESTAMP'");
sql.append(new Timestamp(finfo.getModifyDateTime()));
sql.append("',");
}
// Check if the inode change date/time has been set
if ( finfo.hasSetFlag(FileInfo.SetChangeDate)) {
// Add the SQL to update the change date/time
sql.append(" ChangeDate = TIMESTAMP'");
sql.append(new Timestamp(finfo.getChangeDateTime()));
sql.append("'");
}
// Trim any trailing comma
if ( sql.charAt(sql.length() - 1) == ',')
sql.setLength(sql.length() - 1);
// Complete the SQL request string
sql.append(" WHERE FileId = ");
sql.append( fid);
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Set file info SQL: " + sql.toString());
// Create the SQL statement
stmt = conn.createStatement();
stmt.executeUpdate(sql.toString());
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Set file information error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Set information for a file stream
*
* @param dirId int
* @param fid int
* @param stid int
* @param sinfo StreamInfo
* @exception DBException
*/
public void setStreamInformation(int dirId, int fid, int stid, StreamInfo sinfo)
throws DBException {
// Set file stream information fields
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
// Build the SQL statement to update the file information settings
StringBuffer sql = new StringBuffer(256);
sql.append("UPDATE ");
sql.append(getStreamsTableName());
sql.append(" SET ");
// Check if the access date/time has been set
if ( sinfo.hasSetFlag(StreamInfo.SetAccessDate)) {
// Add the SQL to update the access date/time
sql.append(" AccessDate = TIMESTAMP'");
sql.append(new Timestamp(sinfo.getAccessDateTime()));
sql.append("',");
}
// Check if the modify date/time has been set
if ( sinfo.hasSetFlag(StreamInfo.SetModifyDate)) {
// Add the SQL to update the modify date/time
sql.append(" ModifyDate = TIMESTAMP'");
sql.append(new Timestamp(sinfo.getModifyDateTime()));
sql.append("',");
}
// Check if the stream size should be updated
if ( sinfo.hasSetFlag(StreamInfo.SetStreamSize)) {
// Update the stream size
sql.append(" StreamSize = ");
sql.append(sinfo.getSize());
}
// Trim any trailing comma
if ( sql.charAt(sql.length() - 1) == ',')
sql.setLength(sql.length() - 1);
// Complete the SQL request string
sql.append(" WHERE FileId = ");
sql.append( fid);
sql.append(" AND StreamId = ");
sql.append( stid);
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Set stream info SQL: " + sql.toString());
// Create the SQL statement
stmt = conn.createStatement();
stmt.executeUpdate(sql.toString());
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Set stream information error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Get the id for a file/folder, or -1 if the file/folder does not exist.
*
* @param dirId int
* @param fname String
* @param dirOnly boolean
* @param caseLess boolean
* @return int
* @throws DBException
*/
public int getFileId(int dirId, String fname, boolean dirOnly, boolean caseLess)
throws DBException {
// Get the file id for a file/folder
int fileId = -1;
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database, create a statement for the database lookup
conn = getConnection();
stmt = conn.createStatement();
// Build the SQL for the file lookup
StringBuffer sql = new StringBuffer(128);
sql.append("SELECT FileId FROM ");
sql.append(getFileSysTableName());
sql.append(" WHERE DirId = ");
sql.append(dirId);
sql.append(" AND ");
// Check if the search is for a directory only
if ( dirOnly == true) {
// Search for a directory record
sql.append(" DirectoryFile = 1 AND ");
}
// Check if the file name search should be caseless
String chkFileName = checkNameForSpecialChars(fname);
if ( caseLess == true) {
// Perform a caseless search
sql.append(" UPPER(FileName) = '");
sql.append(chkFileName.toUpperCase());
sql.append("'");
}
else {
// Perform a case sensitive search
sql.append(" FileName = '");
sql.append(chkFileName);
sql.append("'");
}
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Get file id SQL: " + sql.toString());
// Run the database search
ResultSet rs = stmt.executeQuery(sql.toString());
// Check if a file record exists
if (rs.next()) {
// Get the unique file id for the file or folder
fileId = rs.getInt("FileId");
}
// Close the result set
rs.close();
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Get file id error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the file id, or -1 if not found
return fileId;
}
/**
* Get information for a file or folder
*
* @param dirId int
* @param fid int
* @param infoLevel int
* @return FileInfo
* @exception DBException
*/
public DBFileInfo getFileInformation(int dirId, int fid, int infoLevel)
throws DBException {
// Create a SQL select for the required file information
StringBuffer sql = new StringBuffer(128);
sql.append("SELECT ");
// Select fields according to the required information level
switch ( infoLevel) {
// File name only
case DBInterface.FileNameOnly:
sql.append("FileName");
break;
// File ids and name
case DBInterface.FileIds:
sql.append("FileName,FileId,DirId");
break;
// All file information
case DBInterface.FileAll:
sql.append("*");
break;
// Unknown information level
default:
throw new DBException("Invalid information level, " + infoLevel);
}
sql.append(" FROM ");
sql.append(getFileSysTableName());
sql.append(" WHERE FileId = ");
sql.append(fid);
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Get file info SQL: " + sql.toString());
// Load the file record
Connection conn = null;
Statement stmt = null;
DBFileInfo finfo = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Load the file record
ResultSet rs = stmt.executeQuery(sql.toString());
if (rs != null && rs.next()) {
// Create the file informaiton object
finfo = new DBFileInfo();
finfo.setFileId(fid);
// Load the file information
switch ( infoLevel) {
// File name only
case DBInterface.FileNameOnly:
finfo.setFileName(rs.getString("FileName"));
break;
// File ids and name
case DBInterface.FileIds:
finfo.setFileName(rs.getString("FileName"));
finfo.setDirectoryId(rs.getInt("DirId"));
break;
// All file information
case DBInterface.FileAll:
finfo.setFileName(rs.getString("FileName"));
finfo.setSize(rs.getLong("FileSize"));
finfo.setAllocationSize(finfo.getSize());
finfo.setDirectoryId(rs.getInt("DirId"));
// Load the various file date/times
Timestamp ftime = rs.getTimestamp("CreateDate");
finfo.setCreationDateTime( ftime != null ? ftime.getTime() : 0L);
ftime = rs.getTimestamp("ModifyDate");
finfo.setModifyDateTime( ftime != null ? ftime.getTime() : 0L);
ftime = rs.getTimestamp("AccessDate");
finfo.setAccessDateTime( ftime != null ? ftime.getTime() : 0L);
ftime = rs.getTimestamp("ChangeDate");
finfo.setChangeDateTime( ftime != null ? ftime.getTime() : 0L);
// Build the file attributes flags
int attr = 0;
if ( rs.getBoolean("ReadOnlyFile") == true)
attr += FileAttribute.ReadOnly;
if ( rs.getBoolean("SystemFile") == true)
attr += FileAttribute.System;
if ( rs.getBoolean("HiddenFile") == true)
attr += FileAttribute.Hidden;
if ( rs.getBoolean("DirectoryFile") == true) {
attr += FileAttribute.Directory;
finfo.setFileType( FileType.Directory);
}
else
finfo.setFileType( FileType.RegularFile);
if ( rs.getBoolean("ArchivedFile") == true)
attr += FileAttribute.Archive;
finfo.setFileAttributes(attr);
// Get the group/owner id
finfo.setGid(rs.getInt("OwnerGid"));
finfo.setUid(rs.getInt("OwnerUid"));
finfo.setMode(rs.getInt("FileMode"));
// Check if the file is a symbolic link
if ( rs.getBoolean( "IsSymLink") == true)
finfo.setFileType( FileType.SymbolicLink);
break;
}
}
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Get file information error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the file information
return finfo;
}
/**
* Get information for a file stream
*
* @param fid int
* @param stid int
* @param infoLevel int
* @return StreamInfo
* @exception DBException
*/
public StreamInfo getStreamInformation(int fid, int stid, int infoLevel)
throws DBException {
// Create a SQL select for the required stream information
StringBuffer sql = new StringBuffer(128);
sql.append("SELECT ");
// Select fields according to the required information level
switch ( infoLevel) {
// Stream name only.
//
// Also used if ids are requested as we already have the ids
case DBInterface.StreamNameOnly:
case DBInterface.StreamIds:
sql.append("StreamName");
break;
// All file information
case DBInterface.StreamAll:
sql.append("*");
break;
// Unknown information level
default:
throw new DBException("Invalid information level, " + infoLevel);
}
sql.append(" FROM ");
sql.append(getStreamsTableName());
sql.append(" WHERE FileId = ");
sql.append(fid);
sql.append(" AND StreamId = ");
sql.append(stid);
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Get stream info SQL: " + sql.toString());
// Load the stream record
Connection conn = null;
Statement stmt = null;
StreamInfo sinfo = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Load the stream record
ResultSet rs = stmt.executeQuery(sql.toString());
if (rs != null && rs.next()) {
// Create the stream informaiton object
sinfo = new StreamInfo("", fid, stid);
// Load the file information
switch ( infoLevel) {
// Stream name only (or name and ids)
case DBInterface.StreamNameOnly:
case DBInterface.StreamIds:
sinfo.setName(rs.getString("StreamName"));
break;
// All stream information
case DBInterface.FileAll:
sinfo.setName(rs.getString("StreamName"));
sinfo.setSize(rs.getLong("StreamSize"));
// Load the various file date/times
sinfo.setCreationDateTime( rs.getLong("CreateDate"));
sinfo.setModifyDateTime( rs.getLong("ModifyDate"));
sinfo.setAccessDateTime( rs.getLong("AccessDate"));
break;
}
}
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Get stream information error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the stream information
return sinfo;
}
/**
* Return the list of streams for the specified file
*
* @param fid int
* @param infoLevel int
* @return StreamInfoList
* @exception DBException
*/
public StreamInfoList getStreamsList(int fid, int infoLevel)
throws DBException {
// Create a SQL select for the required stream information
StringBuffer sql = new StringBuffer(128);
sql.append("SELECT ");
// Select fields according to the required information level
switch ( infoLevel) {
// Stream name only.
case DBInterface.StreamNameOnly:
sql.append("StreamName");
break;
// Stream name and ids
case DBInterface.StreamIds:
sql.append("StreamName,FileId,StreamId");
break;
// All file information
case DBInterface.StreamAll:
sql.append("*");
break;
// Unknown information level
default:
throw new DBException("Invalid information level, " + infoLevel);
}
sql.append(" FROM ");
sql.append(getStreamsTableName());
sql.append(" WHERE FileId = ");
sql.append(fid);
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Get stream list SQL: " + sql.toString());
// Load the stream record
Connection conn = null;
Statement stmt = null;
StreamInfoList sList = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Load the stream records
ResultSet rs = stmt.executeQuery(sql.toString());
sList = new StreamInfoList();
while ( rs.next()) {
// Create the stream informaiton object
StreamInfo sinfo = new StreamInfo("", fid, -1);
// Load the file information
switch ( infoLevel) {
// Stream name only
case DBInterface.StreamNameOnly:
sinfo.setName(rs.getString("StreamName"));
break;
// Stream name and id
case DBInterface.StreamIds:
sinfo.setName(rs.getString("StreamName"));
sinfo.setStreamId(rs.getInt("StreamId"));
break;
// All stream information
case DBInterface.FileAll:
sinfo.setName(rs.getString("StreamName"));
sinfo.setStreamId(rs.getInt("StreamId"));
sinfo.setSize(rs.getLong("StreamSize"));
// Load the various file date/times
sinfo.setCreationDateTime( rs.getLong("CreateDate"));
sinfo.setModifyDateTime( rs.getLong("ModifyDate"));
sinfo.setAccessDateTime( rs.getLong("AccessDate"));
break;
}
// Add the stream information to the list
sList.addStream(sinfo);
}
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Get stream list error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the streams list
return sList;
}
/**
* Rename a file or folder, may also change the parent directory.
*
* @param dirId int
* @param fid int
* @param newName String
* @param newDir int
* @exception DBException
* @exception FileNotFoundException
*/
public void renameFileRecord(int dirId, int fid, String newName, int newDir)
throws DBException, FileNotFoundException {
// Rename a file/folder
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Update the file record
stmt = conn.createStatement();
String sql = "UPDATE " + getFileSysTableName() + " SET FileName = '" + checkNameForSpecialChars(newName) + "', DirId = " + newDir +
", ChangeDate = TIMESTAMP'" + new Timestamp(System.currentTimeMillis()) + "' WHERE FileId = " + fid;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Rename SQL: " + sql.toString());
// Rename the file/folder
if ( stmt.executeUpdate(sql) == 0) {
// Original file not found
throw new FileNotFoundException("" + fid);
}
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Rename file error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Rename a file stream
*
* @param dirId int
* @param fid int
* @param stid int
* @param newName
* @exception DBException
*/
public void renameStreamRecord(int dirId, int fid, int stid, String newName)
throws DBException {
// TODO Auto-generated method stub
}
/**
* Return the retention period expiry date/time for the specified file, or zero if the file/folder
* is not under retention.
*
* @param dirId int
* @param fid int
* @return RetentionDetails
* @exception DBException
*/
public RetentionDetails getFileRetentionDetails(int dirId, int fid)
throws DBException {
// Check if retention is enabled
if ( isRetentionEnabled() == false)
return null;
// Get the retention record for the file/folder
Connection conn = null;
Statement stmt = null;
RetentionDetails retDetails = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Get the retention record, if any
retDetails = getRetentionExpiryDateTime(conn, stmt, fid);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Get retention error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the retention expiry date/time
return retDetails;
}
/**
* Start a directory search
*
* @param dirId int
* @param searchPath String
* @param attrib int
* @param infoLevel int
* @param maxRecords int
* @return DBSearchContext
* @exception DBException
*/
public DBSearchContext startSearch(int dirId, String searchPath, int attrib, int infoLevel, int maxRecords)
throws DBException {
// Search for files/folders in the specified folder
StringBuffer sql = new StringBuffer(128);
sql.append("SELECT * FROM ");
sql.append(getFileSysTableName());
sql.append(" WHERE DirId = ");
sql.append(dirId);
sql.append(" AND IsDeleted = 0");
// Split the search path
String[] paths = FileName.splitPath(searchPath);
// Check if the file name contains wildcard characters
WildCard wildCard = null;
if ( WildCard.containsWildcards(searchPath)) {
// For the '*.*' and '*' wildcards the SELECT will already return all files/directories that are attached to the
// parent directory. For 'name.*' and '*.ext' type wildcards we can use the LIKE clause to filter the required
// records, for more complex wildcards we will post-process the search using the WildCard class to match the
// file names.
if (searchPath.endsWith("\\*.*") == false && searchPath.endsWith("\\*") == false) {
// Create a wildcard search pattern
wildCard = new WildCard(paths[1], true);
// Check for a 'name.*' type wildcard
if ( wildCard.isType() == WildCard.WILDCARD_EXT) {
// Add the wildcard file extension selection clause to the SELECT
sql.append(" AND FileName LIKE('");
sql.append(checkNameForSpecialChars(wildCard.getMatchPart()));
sql.append("%')");
// Clear the wildcard object, we do not want it to filter the search results
wildCard = null;
}
else if ( wildCard.isType() == WildCard.WILDCARD_NAME) {
// Add the wildcard file name selection clause to the SELECT
sql.append(" AND FileName LIKE('%");
sql.append(checkNameForSpecialChars(wildCard.getMatchPart()));
sql.append("')");
// Clear the wildcard object, we do not want it to filter the search results
wildCard = null;
}
}
}
else {
// Search for a specific file/directory
sql.append(" AND FileName = '");
sql.append(checkNameForSpecialChars(paths[1]));
sql.append("'");
}
// Start the search
ResultSet rs = null;
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Start search SQL: " + sql.toString());
// Start the folder search
rs = stmt.executeQuery(sql.toString());
}
catch (Exception ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Start search error " + ex.getMessage());
// Rethrow the exception
throw new DBException(ex.toString());
}
finally {
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Create the search context, and return
return new OracleSearchContext(rs, stmt, wildCard);
}
/**
* Return the used file space, or -1 if not supported.
*
* @return long
*/
public long getUsedFileSpace() {
// Calculate the total used file space
Connection conn = null;
Statement stmt = null;
long usedSpace = -1L;
try {
// Get a database connection and statement
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT SUM(FileSize) FROM " + getFileSysTableName();
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Get filespace SQL: " + sql);
// Calculate the currently used disk space
ResultSet rs = stmt.executeQuery(sql);
if ( rs.next())
usedSpace = rs.getLong(1);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println("[Oracle] Get used file space error " + ex.getMessage());
}
finally {
// Close the prepared statement
if (stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the used file space
return usedSpace;
}
/**
* Shutdown the database interface, release resources.
*
* @param context DBDeviceContext
*/
public void shutdownDatabase(DBDeviceContext context) {
// Call the base class
super.shutdownDatabase(context);
}
/**
* Get the retention expiry date/time for a file/folder
*
* @param conn Connection
* @param stmt Statement
* @param fid int
* @return RetentionDetails
* @exception SQLException
*/
private final RetentionDetails getRetentionExpiryDateTime(Connection conn, Statement stmt, int fid)
throws SQLException {
// Get the retention expiry date/time for the specified file/folder
RetentionDetails retDetails = null;
String sql = "SELECT StartDate,EndDate FROM " + getRetentionTableName() + " WHERE FileId = " + fid;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Get retention expiry SQL: " + sql);
// Get the retention record, if any
ResultSet rs = stmt.executeQuery(sql);
if ( rs.next()) {
// Get the retention expiry date
Timestamp startDate = rs.getTimestamp("StartDate");
Timestamp endDate = rs.getTimestamp("EndDate");
retDetails = new RetentionDetails(fid, startDate != null ? startDate.getTime() : -1L, endDate.getTime());
}
// Return the retention expiry date/time
return retDetails;
}
/**
* Determine if the specified file/folder is still within an active retention period
*
* @param conn Connection
* @param stmt Statement
* @param fid int
* @return boolean
* @exception SQLException
*/
private final boolean fileHasActiveRetention(Connection conn, Statement stmt, int fid)
throws SQLException {
// Check if retention is enabled
if ( isRetentionEnabled() == false)
return false;
// Check if the file/folder is within the retention period
RetentionDetails retDetails = getRetentionExpiryDateTime(conn, stmt, fid);
if ( retDetails == null)
return false;
// File/folder is within the retention period
return retDetails.isWithinRetentionPeriod(System.currentTimeMillis());
}
/**
* Queue a file request.
*
* @param req FileRequest
* @exception DBException
*/
public void queueFileRequest(FileRequest req)
throws DBException {
// Make sure the associated file state stays in memory for a short time, if the queue is small
// the request may get processed soon.
if ( req instanceof SingleFileRequest) {
// Get the request details
SingleFileRequest fileReq = (SingleFileRequest) req;
Statement stmt = null;
try {
// Check if the file request queue database connection is valid
if ( m_dbConn == null || m_dbConn.isClosed() || m_reqStmt == null || m_reqStmt.getConnection().isClosed())
createQueueStatements();
// Check if the request is part of a transaction, or a standalone request
if ( fileReq.isTransaction() == false) {
// Write the file request record
int recCnt = 0;
synchronized ( m_reqStmt) {
// Write the file request to the queue database
m_reqStmt.clearParameters();
m_reqStmt.setInt(1, fileReq.getFileId());
m_reqStmt.setInt(2, fileReq.getStreamId());
m_reqStmt.setInt(3, fileReq.isType());
m_reqStmt.setString(4, fileReq.getTemporaryFile());
m_reqStmt.setString(5, fileReq.getVirtualPath());
m_reqStmt.setString(6, fileReq.getAttributesString());
recCnt = m_reqStmt.executeUpdate();
// Retrieve the allocated sequence number
if ( recCnt > 0) {
// Get the last insert id
stmt = m_dbConn.createStatement();
ResultSet rs2 = stmt.executeQuery("SELECT " + getTablePrefix() + "SeqNo.CURRVAL FROM DUAL");
if ( rs2.next())
fileReq.setSequenceNumber(rs2.getInt(1));
}
}
}
else {
// Check if the transaction prepared statement is valid, we may have lost the connection to the
// database.
if ( m_tranStmt == null || m_tranStmt.getConnection().isClosed())
createQueueStatements();
// Write the transaction file request to the database
synchronized ( m_tranStmt) {
// Write the request record to the database
m_tranStmt.clearParameters();
m_tranStmt.setInt(1, fileReq.getFileId());
m_tranStmt.setInt(2, fileReq.getStreamId());
m_tranStmt.setInt(3, fileReq.isType());
m_tranStmt.setInt(4, fileReq.getTransactionId());
m_tranStmt.setString(5, fileReq.getTemporaryFile());
m_tranStmt.setString(6, fileReq.getVirtualPath());
m_tranStmt.setString(7, fileReq.getAttributesString());
m_tranStmt.executeUpdate();
}
}
// File request was queued successfully, check for any offline file requests
if ( hasOfflineFileRequests())
databaseOnlineStatus( true);
}
catch (SQLException ex) {
// If the request is a save then add to a pending queue to retry when the database is back online
if ( fileReq.isType() == FileRequest.SAVE || fileReq.isType() == FileRequest.TRANSSAVE)
queueOfflineSaveRequest( fileReq);
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException ( ex.getMessage());
}
finally {
// Release the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
}
}
}
/**
* Perform a queue cleanup deleting temporary cache files that do not have an associated save or transaction
* request.
*
* @param tempDir File
* @param tempDirPrefix String
* @param tempFilePrefix String
* @param jarFilePrefix String
* @return FileRequestQueue
* @throws DBException
*/
public FileRequestQueue performQueueCleanup(File tempDir, String tempDirPrefix, String tempFilePrefix, String jarFilePrefix)
throws DBException {
// Get a connection to the database
Connection conn = null;
PreparedStatement pstmt = null;
Statement stmt = null;
FileRequestQueue reqQueue = new FileRequestQueue();
try {
// Get a connection to the database
conn = getConnection(DBConnectionPool.PermanentLease);
// Delete all load requests from the queue
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM " + getQueueTableName() + " WHERE ReqType = " + FileRequest.LOAD);
while ( rs.next()) {
// Get the path to the cache file
String tempPath = rs.getString( "TempFile");
// Check if the cache file exists, the file load may have been in progress
File tempFile = new File( tempPath);
if ( tempFile.exists()) {
// Delete the cache file for the load request
tempFile.delete();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Deleted load request file " + tempPath);
}
}
// Check if the lock file exists, if so then the server did not shutdown cleanly
File lockFile = new File( tempDir, LockFileName);
setLockFile( lockFile.getAbsolutePath());
boolean cleanShutdown = lockFile.exists() == false;
// Create a crash recovery folder if the server did not shutdown clean
File crashFolder = null;
if ( cleanShutdown == false && hasCrashRecovery()) {
// Create a unique crash recovery sub-folder in the temp area
SimpleDateFormat dateFmt = new SimpleDateFormat( "yyyyMMMdd_HHmmss");
crashFolder = new File( tempDir, "CrashRecovery_" + dateFmt.format(new Date( System.currentTimeMillis())));
if ( crashFolder.mkdir() == true) {
// DEBUG
if (Debug.EnableDbg && hasDebug())
Debug.println("[Oracle] Created crash recovery folder - " + crashFolder.getAbsolutePath());
}
else {
// Use the top level temp area for the crash recovery files
crashFolder = tempDir;
// DEBUG
if (Debug.EnableDbg && hasDebug())
Debug.println("[Oracle] Failed to created crash recovery folder, using folder - " + crashFolder.getAbsolutePath());
}
}
// Delete the file load request records
stmt.execute("DELETE FROM " + getQueueTableName() + " WHERE ReqType = " + FileRequest.LOAD);
// Create a statement to check if a temporary file is part of a save request
pstmt = conn.prepareStatement("SELECT FileId,SeqNo FROM " + getQueueTableName() + " WHERE TempFile = ?");
// Scan all files/sub-directories within the temporary area looking for files that have been saved but not
// deleted due to a server shutdown or crash.
File[] tempFiles = tempDir.listFiles();
if ( tempFiles != null && tempFiles.length > 0) {
// Scan the file loader sub-directories for temporary files
for ( int i = 0; i < tempFiles.length; i++) {
// Get the current file/sub-directory
File curFile = tempFiles[i];
if ( curFile.isDirectory() && curFile.getName().startsWith(tempDirPrefix)) {
// Check if the sub-directory has any loader temporary files
File[] subFiles = curFile.listFiles();
if ( subFiles != null && subFiles.length > 0) {
// Check each file to see if it has a pending save request in the file request database
for ( int j = 0; j < subFiles.length; j++) {
// Get the current file from the list
File ldrFile = subFiles[j];
if ( ldrFile.isFile() && ldrFile.getName().startsWith(tempFilePrefix)) {
try {
// Get the file details from the file system table
pstmt.clearParameters();
pstmt.setString(1, ldrFile.getAbsolutePath());
rs = pstmt.executeQuery();
if ( rs.next()) {
// File save request exists for temp file, nothing to do
}
else {
// Check if the modified date indicates the file may have been updated
if ( ldrFile.lastModified() != 0L) {
// Get the file id from the cache file name
String fname = ldrFile.getName();
int dotPos = fname.indexOf( '.');
String fidStr = fname.substring( tempFilePrefix.length(), dotPos);
if ( fidStr.indexOf( '_') == -1) {
// Convert the file id
int fid = -1;
try {
fid = Integer.parseInt( fidStr);
}
catch (NumberFormatException ex) {
}
// Get the file details from the database
if ( fid != -1) {
// Get the file details for the temp file using the file id
rs = stmt.executeQuery( "SELECT * FROM " + getFileSysTableName() + " WHERE FileId = " + fid);
// If the previous server shutdown was clean then we may be able to queue the file save
if ( cleanShutdown == true) {
if ( rs.next()) {
// Get the currently stored modifed date and file size for the associated file
Timestamp modtime = rs.getTimestamp("ModifyDate");
long dbModDate = modtime != null ? modtime.getTime() : 0L;
long dbFileSize = rs.getLong( "FileSize");
// Check if the temp file requires saving
if ( ldrFile.length() != dbFileSize ||
ldrFile.lastModified() > dbModDate) {
// Build the filesystem path to the file
String filesysPath = buildPathForFileId( fid, stmt);
if ( filesysPath != null) {
// Create a file state for the file
FileState fstate = m_dbCtx.getStateCache().findFileState( filesysPath, true);
FileSegmentInfo fileSegInfo = (FileSegmentInfo) fstate.findAttribute(ObjectIdFileLoader.DBFileSegmentInfo);
FileSegment fileSeg = null;
if ( fileSegInfo == null) {
// Create a new file segment
fileSegInfo = new FileSegmentInfo();
fileSegInfo.setTemporaryFile(ldrFile.getAbsolutePath());
fileSeg = new FileSegment(fileSegInfo, true);
fileSeg.setStatus(FileSegmentInfo.SaveWait, true);
// Add the segment to the file state cache
fstate.addAttribute(ObjectIdFileLoader.DBFileSegmentInfo, fileSegInfo);
// Add a file save request for the temp file to the recovery queue
reqQueue.addRequest(new SingleFileRequest( FileRequest.SAVE, fid, 0, ldrFile.getAbsolutePath(), filesysPath, fstate));
// Update the file size and modified date/time in the filesystem database
stmt.execute( "UPDATE " + getFileSysTableName() + " SET FileSize = " + ldrFile.length() + ", ModifyDate = TIMESTAMP'" + new Timestamp(ldrFile.lastModified()) + "' WHERE FileId = " + fid);
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[Oracle] Queued save request for " + ldrFile.getName() + ", path=" + filesysPath + ", fid=" + fid);
}
}
else {
// Delete the temp file, cannot resolve the path
ldrFile.delete();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[Oracle] Cannot resolve filesystem path for FID " + fid + ", deleted file " + ldrFile.getName());
}
}
}
else {
// Delete the temp file, file does not exist in the filesystem table
ldrFile.delete();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[Oracle] No matching file record for FID " + fid + ", deleted file " + ldrFile.getName());
}
}
else {
// File server did not shutdown cleanly so move any modified files to a holding area as they may be corrupt
if ( rs.next() && hasCrashRecovery()) {
// Get the filesystem file name
String extName = rs.getString( "FileName");
// Generate a file name to rename the cache file into a crash recovery folder
File crashFile = new File ( crashFolder, "" + fid + "_" + extName);
// Rename the cache file into the crash recovery folder
if ( ldrFile.renameTo( crashFile)) {
// DEBUG
if ( Debug.EnableDbg && hasDebug())
Debug.println("[Oracle] Crash recovery file - " + crashFile.getAbsolutePath());
}
}
else {
// DEBUG
if ( Debug.EnableDbg && hasDebug())
Debug.println( "[Oracle] Deleted incomplete cache file - " + ldrFile.getAbsolutePath());
// Delete the incomplete cache file
ldrFile.delete();
}
}
}
else {
// Invalid file id format, delete the temp file
ldrFile.delete();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[Oracle] Bad file id format, deleted file, " + ldrFile.getName());
}
}
else {
// Delete the temp file as it is for an NTFS stream
ldrFile.delete();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[Oracle] Deleted NTFS stream temp file, " + ldrFile.getName());
}
}
else {
// Delete the temp file as it has not been modifed since it was loaded
ldrFile.delete();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[Oracle] Deleted unmodified temp file, " + ldrFile.getName());
}
}
}
catch (SQLException ex) {
Debug.println(ex);
}
}
else {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Deleted temporary file " + ldrFile.getName());
// Delete the temporary file
ldrFile.delete();
}
}
}
}
}
}
// Create the lock file, delete any existing lock file
if ( lockFile.exists())
lockFile.delete();
try {
lockFile.createNewFile();
}
catch (IOException ex) {
// DEBUG
if ( Debug.EnableDbg && hasDebug())
Debug.println("[Oracle] Failed to create lock file - " + lockFile.getAbsolutePath());
}
}
catch (SQLException ex) {
// DEBUG
if (Debug.EnableError && hasDebug())
Debug.println(ex);
}
finally {
// Close the load request statment
if (stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Close the prepared statement
if ( pstmt != null) {
try {
pstmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Cleanup recovered " + reqQueue.numberOfRequests() + " file saves from previous run");
// Return the receovery file request queue
return reqQueue;
}
/**
* Check if the specified temporary file has a queued request.
*
* @param tempFile String
* @param lastFile boolean
* @return boolean
* @exception DBException
*/
public boolean hasQueuedRequest(String tempFile, boolean lastFile)
throws DBException {
Connection conn = null;
Statement stmt = null;
boolean queued = false;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT FileId FROM " + getQueueTableName() + " WHERE TempFile = '" + tempFile + "'";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Has queued req SQL: " + sql);
// Check if there is a queued request using the temporary file
ResultSet rs = stmt.executeQuery(sql);
if ( rs.next())
queued = true;
else {
// Check if there is a transaction using the temporary file
sql = "SELECT FileId FROM " + getTransactionTableName() + " WHERE TempFile = '" + tempFile + "'";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Has queued req SQL: " + sql);
// Check the transaction table
rs = stmt.executeQuery(sql);
if ( rs.next())
queued = true;
}
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the queued status
return queued;
}
/**
* Delete a file request from the pending queue.
*
* @param fileReq FileRequest
* @exception DBException
*/
public void deleteFileRequest(FileRequest fileReq)
throws DBException {
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Delete the file request queue entry from the request table or multiple records from the
// transaction table
if ( fileReq instanceof SingleFileRequest) {
// Get the single file request details
SingleFileRequest singleReq = (SingleFileRequest) fileReq;
// Delete the request record
stmt.executeUpdate("DELETE FROM " + getQueueTableName() + " WHERE SeqNo = " + singleReq.getSequenceNumber());
}
else {
// Delete the transaction records
stmt.executeUpdate("DELETE FROM " + getTransactionTableName() + " WHERE TranId = " + fileReq.getTransactionId());
}
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Load a block of file request from the database into the specified queue.
*
* @param fromSeqNo int
* @param reqType int
* @param reqQueue FileRequestQueue
* @param recLimit int
* @return int
* @exception DBException
*/
public int loadFileRequests(int fromSeqNo, int reqType, FileRequestQueue reqQueue, int recLimit)
throws DBException {
// Load a block of file requests from the loader queue
Connection conn = null;
Statement stmt = null;
int recCnt = 0;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Build the SQL to load the queue records
String sql = "SELECT * FROM " + getQueueTableName() + " WHERE SeqNo > " + fromSeqNo + " AND ReqType = " + reqType + " AND rownum <= " + recLimit + " ORDER BY SeqNo";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Load file requests - " + sql);
// Get a block of file request records
ResultSet rs = stmt.executeQuery(sql);
while ( rs.next()) {
// Get the file request details
int fid = rs.getInt("FileId");
int stid = rs.getInt("StreamId");
int reqTyp = rs.getInt("ReqType");
int seqNo = rs.getInt("SeqNo");
String tempPath = rs.getString("TempFile");
String virtPath = rs.getString("VirtualPath");
String attribs = rs.getString("Attribs");
// Recreate the file request for the in-memory queue
SingleFileRequest fileReq = new SingleFileRequest(reqTyp, fid, stid, tempPath, virtPath, seqNo, null);
fileReq.setAttributes( attribs);
// Add the request to the callers queue
reqQueue.addRequest(fileReq);
// Update the count of loaded requests
recCnt++;
}
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the count of file requests loaded
return recCnt;
}
/**
* Load a transaction request from the queue.
*
* @param tranReq MultiplFileRequest
* @return MultipleFileRequest
* @exception DBException
*/
public MultipleFileRequest loadTransactionRequest(MultipleFileRequest tranReq)
throws DBException {
// Load a transaction request from the transaction loader queue
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT * FROM " + getTransactionTableName() + " WHERE TranId = " + tranReq.getTransactionId();
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Load trans request - " + sql);
// Get the block of file request records for the current transaction
ResultSet rs = stmt.executeQuery(sql);
while ( rs.next()) {
// Get the file request details
int fid = rs.getInt("FileId");
int stid = rs.getInt("StreamId");
String tempPath = rs.getString("TempFile");
String virtPath = rs.getString("VirtualPath");
// Create the cached file information and add to the request
CachedFileInfo finfo = new CachedFileInfo(fid,stid,tempPath,virtPath);
tranReq.addFileInfo(finfo);
}
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the updated file request
return tranReq;
}
/**
* Create the prepared statements used by the file request queueing database
*
* @exception SQLException
*/
protected final void createQueueStatements()
throws SQLException {
// Check if the database connection is valid
if ( m_dbConn != null) {
// Close the existing statements
if ( m_reqStmt != null)
m_reqStmt.close();
if ( m_tranStmt != null)
m_tranStmt.close();
// Release the current database connection
releaseConnection(m_dbConn);
m_dbConn = null;
}
if ( m_dbConn == null)
m_dbConn = getConnection(DBConnectionPool.PermanentLease);
// Create the prepared statements for accessing the file request queue database
m_reqStmt = m_dbConn.prepareStatement("INSERT INTO " + getQueueTableName() + " (FileId,StreamId,ReqType,TempFile,VirtualPath,Attribs,SeqNo) VALUES (?,?,?,?,?,?," + getTablePrefix() + "SeqNo.NEXTVAL)");
// Create the prepared statements for accessing the transaction request queue database
m_tranStmt = m_dbConn.prepareStatement("INSERT INTO " + getTransactionTableName() + " (FileId,StreamId,ReqType,TranId,TempFile,VirtualPath,Attribs) VALUES (?,?,?,?,?,?,?)");
}
/**
* Return the file data details for the specified file or stream.
*
* @param fileId
* @param streamId
* @return DBDataDetails
* @throws DBException
*/
public DBDataDetails getFileDataDetails(int fileId, int streamId)
throws DBException {
// Load the file details from the data table
Connection conn = null;
Statement stmt = null;
DBDataDetails dbDetails = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT * FROM " + getDataTableName() + " WHERE FileId = " + fileId +
" AND StreamId = " + streamId + " AND FragNo = 1";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Get file data details SQL: " + sql);
// Load the file details
ResultSet rs = stmt.executeQuery(sql);
if ( rs.next()) {
// Create the file details
dbDetails = new DBDataDetails(fileId, streamId);
if ( rs.getBoolean("JarFile") == true)
dbDetails.setJarId(rs.getInt("JarId"));
}
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// If the file details are not valid throw an exception
if ( dbDetails == null)
throw new DBException("Failed to load file details for " + fileId + ":" + streamId);
// Return the file data details
return dbDetails;
}
/**
* Return the maximum data fragment size supported
*
* @return long
*/
public long getMaximumFragmentSize() {
return 20 * MemorySize.MEGABYTE;
}
/**
* Load file data from the database into a temporary/local file
*
* @param fileId int
* @param streamId int
* @param fileSeg FileSegment
* @throws DBException
* @throws IOException
*/
public void loadFileData(int fileId, int streamId, FileSegment fileSeg)
throws DBException, IOException {
// Open the temporary file
FileOutputStream fileOut = new FileOutputStream(fileSeg.getTemporaryFile());
// Update the segment status
fileSeg.setStatus(FileSegmentInfo.Loading);
// DEBUG
long startTime = 0L;
if ( Debug.EnableInfo && hasDebug())
startTime = System.currentTimeMillis();
// Load the file data fragments
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database, create a statement
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT * FROM " + getDataTableName() + " WHERE FileId = " + fileId +
" AND StreamId = " + streamId + " ORDER BY FragNo";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Load file data SQL: " + sql);
// Find the data fragments for the file, check if the file is stored in a Jar
ResultSet rs = stmt.executeQuery(sql);
// Load the file data from the main file record(s)
byte[] inbuf = null;
int buflen = 0;
int fragNo = -1;
int fragSize = -1;
long totLen = 0L;
while ( rs.next()) {
// Access the file data
Blob dataBlob = rs.getBlob("Data");
fragNo = rs.getInt("FragNo");
fragSize = rs.getInt("FragLen");
InputStream dataFrag = dataBlob.getBinaryStream();
// Allocate the read buffer, if not already allocated
if ( inbuf == null) {
buflen = (int) Math.min(dataBlob.length(), MaxMemoryBuffer);
inbuf = new byte[buflen];
}
// Read the data from the database record and write to the output file
int rdLen = dataFrag.read(inbuf, 0, inbuf.length);
while ( rdLen > 0) {
// Write a block of data to the temporary file segment
fileOut.write(inbuf, 0, rdLen);
totLen += rdLen;
// Read another block of data
rdLen = dataFrag.read(inbuf, 0, inbuf.length);
}
// Signal to waiting threads that data is available
fileSeg.setReadableLength(totLen);
fileSeg.signalDataAvailable();
// Renew the lease on the database connection
getConnectionPool().renewLease(conn);
}
// Close the resultset
rs.close();
// DEBUG
if ( Debug.EnableInfo && hasDebug()) {
long endTime = System.currentTimeMillis();
Debug.println("[Oracle] Loaded fid=" + fileId + ", stream=" + streamId + ", frags=" + fragNo + ", time=" + (endTime-startTime) + "ms");
}
}
catch ( SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Check if a statement was allocated
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
// Close the output file
if ( fileOut != null) {
try {
fileOut.close();
}
catch (Exception ex) {
Debug.println(ex);
}
}
}
// Signal that the file data is available
fileSeg.signalDataAvailable();
}
/**
* Load Jar file data from the database into a temporary file
*
* @param jarId int
* @param jarSeg FileSegment
* @throws DBException
* @throws IOException
*/
public void loadJarData(int jarId, FileSegment jarSeg)
throws DBException, IOException {
// Load the Jar file data
Connection conn = null;
Statement stmt = null;
FileOutputStream outJar = null;
try {
// Get a connection to the database, create a statement
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT * FROM " + getJarDataTableName() + " WHERE JarId = " + jarId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Load Jar data SQL: " + sql);
// Create the temporary Jar file
outJar = new FileOutputStream(jarSeg.getTemporaryFile());
// Get the Jar data record
ResultSet rs = stmt.executeQuery(sql);
if ( rs.next()) {
// Access the Jar file data
Blob dataBlob = rs.getBlob("Data");
InputStream dataFrag = dataBlob.getBinaryStream();
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Load Jar BLOB, length=" + dataBlob.length());
// Allocate the read buffer
byte[] inbuf = new byte[(int) Math.min(dataBlob.length(), MaxMemoryBuffer)];
// Read the Jar data from the database record and write to the output file
int rdLen = dataFrag.read(inbuf, 0, inbuf.length);
long totLen = 0L;
while ( rdLen > 0) {
// Write a block of data to the temporary file segment
outJar.write(inbuf, 0, rdLen);
totLen += rdLen;
// Read another block of data
rdLen = dataFrag.read(inbuf, 0, inbuf.length);
}
}
// Close the output Jar file
outJar.close();
// Set the Jar file segment status to indicate that the data has been loaded
jarSeg.setStatus(FileSegmentInfo.Available, false);
}
catch ( SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Check if a statement was allocated
if ( stmt != null) {
try {
stmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
// Close the output file
if ( outJar != null) {
try {
outJar.close();
}
catch (Exception ex) {
Debug.println(ex);
}
}
}
}
/**
* Save the file data from the temporary/local file to the database
*
* @param fileId int
* @param streamId int
* @param fileSeg FileSegment
* @return int
* @throws DBException
* @throws IOException
*/
public int saveFileData(int fileId, int streamId, FileSegment fileSeg)
throws DBException, IOException {
// Use a memory buffer to copy the file data fragments
byte[] memBuf = new byte[(int) getDataFragmentSize()];
// Get the temporary file size
File tempFile = new File( fileSeg.getTemporaryFile());
// Save the file data
Connection conn = null;
Statement delStmt = null;
PreparedStatement insStmt = null;
PreparedStatement blobStmt = null;
int fragNo = 1;
boolean autoCommitState = false;
FileInputStream inFile = null;
try {
// Open the temporary file
inFile = new FileInputStream(tempFile);
// Get a connection to the database. Auto commit must be disabled as we will use SELECT ... FOR UPDATE.
conn = getConnection();
autoCommitState = conn.getAutoCommit();
conn.setAutoCommit(false);
// Create the delete statement to delete the existing data records
delStmt = conn.createStatement();
String sql = "DELETE FROM " + getDataTableName() + " WHERE FileId = " + fileId +
" AND StreamId = " + streamId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Save file data SQL: " + sql);
// Delete any existing file data records for this file
int recCnt = delStmt.executeUpdate(sql);
delStmt.close();
delStmt = null;
// Add the file data to the database
sql = "INSERT INTO " + getDataTableName() + " (FileId,StreamId,FragNo,FragLen,Data) VALUES (?,?,?,?,EMPTY_BLOB())";
insStmt = conn.prepareStatement(sql);
blobStmt = conn.prepareStatement("SELECT Data FROM " + getDataTableName() + " WHERE FileId = ? AND StreamId = ? AND FragNo = ? FOR UPDATE");
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Save file data SQL: " + sql);
long saveSize = tempFile.length();
long fragSize = 0;
while ( saveSize > 0) {
// Copy a block of data to the memory buffer
fragSize = inFile.read(memBuf);
// Store the current fragment details with an empty BLOB field
insStmt.clearParameters();
insStmt.setInt(1, fileId);
insStmt.setInt(2, streamId);
insStmt.setInt(3, fragNo);
insStmt.setInt(4, (int) fragSize);
if ( insStmt.executeUpdate() < 1 && hasDebug())
Debug.println("## Oracle Failed to update file data, fid=" + fileId + ", stream=" + streamId + ", fragNo=" + fragNo);
// Update the BLOB field for the record just written
blobStmt.clearParameters();
blobStmt.setInt(1, fileId);
blobStmt.setInt(2, streamId);
blobStmt.setInt(3, fragNo);
ResultSet blobRs = blobStmt.executeQuery();
if ( blobRs.next()) {
// Get an output stream to write the data to the BLOB field
Blob dataBlob = blobRs.getBlob(1);
OutputStream blobOut = ((oracle.sql.BLOB) dataBlob).getBinaryOutputStream();
blobOut.write( memBuf, 0, (int) fragSize);
blobOut.close();
}
else if ( hasDebug())
Debug.println("## Oracle Failed to get BLOB record, fid=" + fileId + ", stream=" + streamId + ", fragNo=" + fragNo);
// Commit the data
conn.commit();
// Update the fragment index
fragNo++;
// Update the remaining data size to be saved
saveSize -= fragSize;
// Renew the lease on the database connection so that it does not expire
getConnectionPool().renewLease(conn);
}
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the delete statement
if (delStmt != null) {
try {
delStmt.close();
}
catch (Exception ex) {
}
}
// Close the insert statement
if ( insStmt != null) {
try {
insStmt.close();
}
catch (Exception ex) {
}
}
// Close the BLOB update statement
if ( blobStmt != null) {
try {
blobStmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection, restore the auto commit state
if ( conn != null) {
try {
conn.setAutoCommit(autoCommitState);
}
catch (Exception ex) {
}
releaseConnection(conn);
}
// Close the input file
if ( inFile != null) {
try {
inFile.close();
}
catch (Exception ex) {
}
}
}
// Return the number of data fragments used to save the file data
return fragNo;
}
/**
* Save the file data from a Jar file to the database
*
* @param jarPath String
* @param fileList DBDataDetailsList
* @return int
* @throws DBException
* @throws IOException
*/
public int saveJarData( String jarPath, DBDataDetailsList fileList)
throws DBException, IOException {
// Write the Jar file to the blob field in the Jar data table
Connection conn = null;
Statement stmt = null;
String sql = null;
boolean autoCommitState = false;
int jarId = -1;
FileInputStream inJar = null;
try {
// Get a connection to the database. Auto commit must be disabled as we will use SELECT ... FOR UPDATE.
conn = getConnection();
autoCommitState = conn.getAutoCommit();
conn.setAutoCommit(false);
// Open the Jar file
File jarFile = new File(jarPath);
inJar = new FileInputStream(jarFile);
// Allocate a buffer to read the Jar data into
long bufSize = jarFile.length();
if ( bufSize > MaxMemoryBuffer)
bufSize = MaxMemoryBuffer;
byte[] buf = new byte[( int) bufSize];
// Add the Jar file data to the database
sql = "INSERT INTO " + getJarDataTableName() + " (JarId,Data) VALUES (" + getTablePrefix() + "JarId.NEXTVAL,EMPTY_BLOB())";
stmt = conn.createStatement();
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Save Jar data SQL: " + sql);
// Insert the Jar record
if ( stmt.executeUpdate( sql) > 0) {
// Get the unique jar id allocated to the new Jar record
ResultSet rs = stmt.executeQuery("SELECT " + getTablePrefix() + "JarId.CURRVAL FROM DUAL");
if ( rs.next())
jarId = rs.getInt(1);
// Select the Jar record just inserted and write the blob data
sql = "SELECT Data FROM " + getJarDataTableName() + " WHERE JarId = " + jarId + " FOR UPDATE";
rs = stmt.executeQuery( sql);
if ( rs.next()) {
Blob dataBlob = rs.getBlob(1);
OutputStream blobOut = ((oracle.sql.BLOB) dataBlob).getBinaryOutputStream();
// Read data from the Jar file and write to the blob stream
long totLen = 0L;
int rdsize = 0;
while ( totLen < jarFile.length()) {
// Read a block of data from the Jar file
rdsize = inJar.read(buf, 0, buf.length);
if ( rdsize > 0)
blobOut.write(buf, 0, rdsize);
// Update the total read length
totLen += rdsize;
}
// Close the blob stream
blobOut.close();
// Close the Jar file
inJar.close();
inJar = null;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Wrote " + totLen + " bytes to BLOB for Jar");
// Commit the blob data
conn.commit();
}
// Re-enable auto commit
conn.setAutoCommit(true);
// Update the jar id record for each file in the Jar
for ( int i = 0; i < fileList.numberOfFiles(); i++) {
// Get the current file details
DBDataDetails dbDetails = fileList.getFileAt(i);
// Add the file data record(s) to the database
stmt.executeUpdate("DELETE FROM " + getDataTableName() + " WHERE FileId = " + dbDetails.getFileId() + " AND StreamId = " + dbDetails.getStreamId());
if ( stmt.executeUpdate("INSERT INTO " + getDataTableName() + " (FileId,StreamId,FragNo,JarId,JarFile) VALUES (" + dbDetails.getFileId() + "," + dbDetails.getStreamId() +
", 1," + jarId + ",1)") < 1) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[Oracle] Failed to insert data record for fid=" + dbDetails.getFileId() + ", jarId=" + jarId);
}
}
}
else if ( hasDebug())
Debug.println("## Oracle Failed to store Jar data");
}
catch ( SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null) {
// Reset the auto commit state
try {
conn.setAutoCommit(autoCommitState);
}
catch ( Exception ex) {
}
// Release the database connection
releaseConnection(conn);
}
// Close the Jar file
if ( inJar != null) {
try {
inJar.close();
}
catch (IOException ex) {
}
}
}
// Return the allocated Jar id
return jarId;
}
/**
* Delete the file data for the specified file/stream
*
* @param fileId int
* @param streamId int
* @throws DBException
* @throws IOException
*/
public void deleteFileData(int fileId, int streamId)
throws DBException, IOException {
// Delete the file data records for the file or stream
Connection conn = null;
Statement delStmt = null;
try {
// Get a connection to the database
conn = getConnection();
// Need to delete the existing data
delStmt = conn.createStatement();
String sql = null;
// Check if the main file stream is being deleted, if so then delete all stream data too
if ( streamId == 0)
sql = "DELETE FROM " + getDataTableName() + " WHERE FileId = " + fileId;
else
sql = "DELETE FROM " + getDataTableName() + " WHERE FileId = " + fileId + " AND StreamId = " + streamId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Delete file data SQL: " + sql);
// Delete the file data records
int recCnt = delStmt.executeUpdate(sql);
// Debug
if ( Debug.EnableInfo && hasDebug() && recCnt > 0)
Debug.println("[Oracle] Deleted file data fid=" + fileId + ", stream=" + streamId + ", records=" + recCnt);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(ex);
}
finally {
// Close the delete statement
if (delStmt != null) {
try {
delStmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Delete the file data for the specified Jar file
*
* @param jarId int
* @throws DBException
* @throws IOException
*/
public void deleteJarData(int jarId)
throws DBException, IOException {
// Delete the data records for the Jar file data
Connection conn = null;
Statement delStmt = null;
try {
// Get a connection to the database
conn = getConnection();
// Need to delete the existing data
delStmt = conn.createStatement();
String sql = "DELETE FROM " + getJarDataTableName() + " WHERE JarId = " + jarId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Delete Jar data SQL: " + sql);
// Delete the Jar data records
int recCnt = delStmt.executeUpdate(sql);
// Debug
if ( Debug.EnableInfo && hasDebug() && recCnt > 0)
Debug.println("[Oracle] Deleted Jar data jarId=" + jarId + ", records=" + recCnt);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(ex);
}
finally {
// Close the delete statement
if (delStmt != null) {
try {
delStmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
//***** DBObjectIdInterface Methods *****
/**
* Create a file id to object id mapping
*
* @param fileId int
* @param streamId int
* @param objectId String
* @exception DBException
*/
public void saveObjectId(int fileId, int streamId, String objectId)
throws DBException {
// Create a new file id/object id mapping record
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
// Delete any current mapping record for the object
String sql = "DELETE FROM " + getObjectIdTableName() + " WHERE FileId = " + fileId + " AND StreamId = " + streamId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Save object id SQL: " + sql);
// Delete any current mapping record
stmt.executeUpdate( sql);
// Insert the new mapping record
sql = "INSERT INTO " + getObjectIdTableName() + " (FileId,StreamId,ObjectID) VALUES(" + fileId +
"," + streamId + ",'" + objectId + "')";
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Save object id SQL: " + sql);
// Create the mapping record
if ( stmt.executeUpdate(sql) == 0)
throw new DBException("Failed to add object id record, fid=" + fileId + ", objId=" + objectId);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Load the object id for the specified file id
*
* @param fileId int
* @param streamId int
* @return String
* @exception DBException
*/
public String loadObjectId(int fileId, int streamId)
throws DBException {
// Load the object id for the specified file id
Connection conn = null;
Statement stmt = null;
String objectId = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT ObjectId FROM " + getObjectIdTableName() + " WHERE FileId = " + fileId + " AND StreamId = " + streamId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Load object id SQL: " + sql);
// Load the mapping record
ResultSet rs = stmt.executeQuery(sql);
if ( rs.next())
objectId = rs.getString("ObjectId");
else
throw new DBException("Failed to load object id for " + fileId);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the object id
return objectId;
}
/**
* Delete a file id/object id mapping
*
* @param fileId int
* @param streamId int
* @param objectId String
* @exception DBException
*/
public void deleteObjectId(int fileId, int streamId, String objectId)
throws DBException {
// Delete a file id/object id mapping record
Connection conn = null;
Statement stmt = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
String sql = "DELETE FROM " + getObjectIdTableName() + " WHERE FileId = " + fileId + " AND StreamId = " + streamId;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Delete object id SQL: " + sql);
// Delete the mapping record
stmt.executeUpdate(sql);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Return the data for a symbolic link
*
* @param dirId int
* @param fid int
* @return String
* @exception DBException
*/
public String readSymbolicLink( int dirId, int fid)
throws DBException {
// Read the symbolic link data
Connection conn = null;
Statement stmt = null;
String symLink = null;
try {
// Get a connection to the database
conn = getConnection();
stmt = conn.createStatement();
String sql = "SELECT SymLink FROM " + getSymLinksTableName() + " WHERE FileId = " + fid;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Read symbolic link: " + sql);
// Load the mapping record
ResultSet rs = stmt.executeQuery(sql);
if ( rs.next())
symLink = rs.getString("SymLink");
else
throw new DBException("Failed to load symbolic link data for " + fid);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println(ex);
// Rethrow the exception
throw new DBException(ex.getMessage());
}
finally {
// Close the statement
if ( stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
// Return the symbolic link data
return symLink;
}
/**
* Delete a symbolic link record
*
* @param dirId int
* @param fid int
* @exception DBException
*/
public void deleteSymbolicLinkRecord( int dirId, int fid)
throws DBException {
// Delete the symbolic link record for a file
Connection conn = null;
Statement delStmt = null;
try {
// Get a connection to the database
conn = getConnection();
delStmt = conn.createStatement();
String sql = "DELETE FROM " + getSymLinksTableName() + " WHERE FileId = " + fid;
// DEBUG
if ( Debug.EnableInfo && hasSQLDebug())
Debug.println("[Oracle] Delete symbolic link SQL: " + sql);
// Delete the symbolic link record
int recCnt = delStmt.executeUpdate(sql);
// Debug
if ( Debug.EnableInfo && hasDebug() && recCnt > 0)
Debug.println("[Oracle] Deleted symbolic link fid=" + fid);
}
catch (SQLException ex) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println(ex);
}
finally {
// Close the delete statement
if (delStmt != null) {
try {
delStmt.close();
}
catch (Exception ex) {
}
}
// Release the database connection
if ( conn != null)
releaseConnection(conn);
}
}
/**
* Check for special characters within a file/directory name and escape the characters to return
* a string which can be used in a SQL statement.
*
* @param name String
* @return String
*/
protected String checkNameForSpecialChars(String name) {
// Check for a null string
if ( name == null || name.length() == 0)
return name;
// Check if the file/directory name contains special characters
int idx = 0;
boolean specChars = false;
while ( idx < m_specialChars.length() && specChars == false) {
// Check if the name contains the current special character
if ( name.indexOf( m_specialChars.charAt(idx++)) != -1)
specChars = true;
}
// Check if any special characters were found
if ( specChars == false)
return name;
// Escape any special characters within the file/directory name
StringBuffer nameBuf = new StringBuffer(name.length() * 2);
for ( int i = 0; i < name.length(); i++) {
// Get the current character and check if it needs to be escaped
char curChar = name.charAt(i);
// Append the character to the escape string
if ( curChar == '\'')
nameBuf.append('\'');
else if ( m_specialChars.indexOf(curChar) != -1)
nameBuf.append("\\");
nameBuf.append(curChar);
}
// Return the escaped file/directory name
return nameBuf.toString();
}
/**
* Convert a file id to a share relative path
*
* @param fileid int
* @param stmt Statement
* @return String
*/
private String buildPathForFileId(int fileid, Statement stmt) {
// Build an array of folder names working back from the files id
StringList names = new StringList();
try {
// Loop, walking backwards up the tree until we hit root
int curFid = fileid;
do {
// Search for the current file record in the database
ResultSet rs = stmt.executeQuery("SELECT DirId,FileName FROM " + getFileSysTableName() + " WHERE FileId = " + curFid);
if ( rs.next()) {
// Get the filename
names.addString( rs.getString( "FileName"));
// The directory id becomes the next file id to search for
curFid = rs.getInt( "DirId");
// Close the resultset
rs.close();
}
else
return null;
} while ( curFid > 0);
}
catch ( SQLException ex) {
// DEBUG
if ( hasDebug())
Debug.println( ex);
return null;
}
// Build the path string
StringBuffer pathStr = new StringBuffer (256);
pathStr.append(FileName.DOS_SEPERATOR_STR);
for ( int i = names.numberOfStrings() - 1; i >= 0; i--) {
pathStr.append(names.getStringAt(i));
pathStr.append(FileName.DOS_SEPERATOR_STR);
}
// Remove the trailing slash from the path
if ( pathStr.length() > 0)
pathStr.setLength(pathStr.length() - 1);
// Return the path string
return pathStr.toString();
}
}