/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.db;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DatabaseNotFoundException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.PreloadConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.slamd.admin.AdminServlet;
import com.slamd.asn1.ASN1Element;
import com.slamd.asn1.ASN1Exception;
import com.slamd.asn1.ASN1OctetString;
import com.slamd.asn1.ASN1Reader;
import com.slamd.asn1.ASN1Sequence;
import com.slamd.asn1.ASN1Writer;
import com.slamd.common.Constants;
import com.slamd.common.JobClassLoader;
import com.slamd.dslogplay.LogPlaybackJobClass;
import com.slamd.jobs.BurnCPUJobClass;
import com.slamd.jobs.DSADMImportRateJobClass;
import com.slamd.jobs.DSCFGImportRateJobClass;
import com.slamd.jobs.ExecJobClass;
import com.slamd.jobs.HTTPGetRateJobClass;
import com.slamd.jobs.IMAPCheckRateJobClass;
import com.slamd.jobs.LDAPAddAndDeleteRateJobClass;
import com.slamd.jobs.LDAPAsynchronousModRateJobClass;
import com.slamd.jobs.LDAPAsynchronousSearchRateJobClass;
import com.slamd.jobs.LDAPAuthRateJobClass;
import com.slamd.jobs.LDAPCompareRateJobClass;
import com.slamd.jobs.LDAPFileBasedModRateJobClass;
import com.slamd.jobs.LDAPFileBasedSearchRateJobClass;
import com.slamd.jobs.LDAPMixedLoadJobClass;
import com.slamd.jobs.LDAPModDNRateJobClass;
import com.slamd.jobs.LDAPModRateJobClass;
import com.slamd.jobs.LDAPMultiConnectionSearchRateJobClass;
import com.slamd.jobs.LDAPPrimeJobClass;
import com.slamd.jobs.LDAPSearchRateJobClass;
import com.slamd.jobs.LDAPSearchAndModRateJobClass;
import com.slamd.jobs.LDAPWaitForDirectoryJobClass;
import com.slamd.jobs.LDIF2DBImportRateJobClass;
import com.slamd.jobs.MultiSearchLDAPLoadJobClass;
import com.slamd.jobs.NullJobClass;
import com.slamd.jobs.POPCheckRateJobClass;
import com.slamd.jobs.SMTPSendRateJobClass;
import com.slamd.jobs.SQLModRateJobClass;
import com.slamd.jobs.SQLSearchRateJobClass;
import com.slamd.jobs.SiteMinderJobClass;
import com.slamd.jobs.SolarisLDAPAuthRateJobClass;
import com.slamd.jobs.TCPReplayJobClass;
import com.slamd.jobs.ThroughputTestJobClass;
import com.slamd.jobs.WeightedSiteMinderJobClass;
import com.slamd.job.Job;
import com.slamd.job.JobClass;
import com.slamd.job.OptimizingJob;
import com.slamd.job.SingleStatisticOptimizationAlgorithm;
import com.slamd.job.SingleStatisticWithCPUUtilizationOptimizationAlgorithm;
import com.slamd.job.SingleStatisticWithConstraintOptimizationAlgorithm;
import com.slamd.job.SingleStatisticWithReplicationLatencyOptimizationAlgorithm;
import com.slamd.jobgroup.JobGroup;
import com.slamd.loadvariance.LoadVarianceTestJobClass;
import com.slamd.report.HTMLReportGenerator;
import com.slamd.report.PDFReportGenerator;
import com.slamd.report.TextReportGenerator;
import com.slamd.scripting.BSFJobClass;
import com.slamd.scripting.ScriptedJobClass;
import com.slamd.server.ConfigSubscriber;
import com.slamd.server.SLAMDServer;
import com.slamd.server.SLAMDServerException;
import com.slamd.server.UploadedFile;
/**
* This class provides the main interface to the SLAMD database used for storing
* user accounts, configuration information, and job data.
*
*
* @author Neil A. Wilson
*/
public class SLAMDDB
{
// The set of transactions that are currently active in the database.
ArrayList<Transaction> activeTransactions;
// The list of configuration subscribers that have been registered with this
// configuration database.
ArrayList<ConfigSubscriber> configSubscribers;
// Indicates whether the databases are currently open.
boolean dbsOpen;
// Indicates whether the database environment is currently open.
boolean environmentOpen;
// Indicates whether the database is currently operating in read-only mode.
boolean readOnly;
// The handle to the configuration database.
Database configDB;
// The handle to the uploaded file database.
Database fileDB;
// The handle to the folder database.
Database folderDB;
// The handle to the group database.
Database groupDB;
// The handle to the job database.
Database jobDB;
// The handle to the job group database.
Database jobGroupDB;
// The handle to the optimizing job database.
Database optimizingJobDB;
// The handle to the user database.
Database userDB;
// The handle to the virtual folder database.
Database virtualFolderDB;
// The database environment with which all the databases are associated.
Environment dbEnv;
// The path to the directory containing the database files.
File dbDir;
// The hash map that is used to hold information about the configuration
// parameters.
HashMap<String,String> configHash;
// The set of jobs that are currently disabled and not eligible for execution.
HashSet<String> disabledJobs;
// The set of jobs that have been scheduled but have not yet started running
// and are not disabled.
HashSet<String> pendingJobs;
// The set of jobs that are currently running.
HashSet<String> runningJobs;
// The mutex used to provide threadsafe access to the database.
private final Object dbMutex;
// The SLAMD server instance with which this database is associated.
SLAMDServer slamdServer;
/**
* Initializes the SLAMD database and opens the environment, creating a new
* one if necessary.
*
* @param slamdServer The SLAMD server instance with which this
* database is associated.
* @param dbDirectory The path to the directory in which the database
* files should be held.
* @param readOnly Indicates whether the database should be opened
* in read-only mode.
* @param createIfNecessary Indicates whether a new database environment
* should be created if one does not already
* exist.
*
* @throws DatabaseException If a problem occurs while initializing the
* database environment.
*/
public SLAMDDB(SLAMDServer slamdServer, String dbDirectory, boolean readOnly,
boolean createIfNecessary)
throws DatabaseException
{
this.readOnly = readOnly;
this.slamdServer = slamdServer;
// First make sure that the specified directory exists and is actually a
// directory. If it does not exist, then create it if appropriate.
dbDir = new File(dbDirectory);
if (dbDir.exists())
{
if (! dbDir.isDirectory())
{
String message = "Specified database directory \"" + dbDirectory +
"\" exists but is not a directory.";
slamdServer.logMessage(Constants.LOG_LEVEL_ANY, message);
throw new DatabaseException(message);
}
}
else
{
if (createIfNecessary)
{
try
{
dbDir.mkdirs();
}
catch (Exception e)
{
String message = "Unable to create database directory \"" +
dbDirectory + "\" -- " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_ANY, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
else
{
String message = "Database directory \"" + dbDirectory +
"\" does not exist and the SLAMD server has been " +
"configured to not create it.";
slamdServer.logMessage(Constants.LOG_LEVEL_ANY, message);
throw new DatabaseException(message);
}
}
// Define the properties that should be used to open the DB environment.
EnvironmentConfig config = new EnvironmentConfig();
config.setAllowCreate(createIfNecessary);
config.setReadOnly(readOnly);
config.setTransactional(true);
config.setTxnNoSync(readOnly);
config.setCachePercent(25);
// Open the database environment with the specified configuration.
dbEnv = new Environment(dbDir, config);
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Opened the configuration database environment.");
// Initialize the remaining instance variables.
dbMutex = new Object();
activeTransactions = new ArrayList<Transaction>();
environmentOpen = true;
dbsOpen = false;
configSubscribers = new ArrayList<ConfigSubscriber>();
// Open the individual databases.
try
{
openDatabases(! readOnly);
}
catch (DatabaseException de)
{
try
{
dbEnv.close();
} catch (Exception e) {}
throw de;
}
catch (Exception e)
{
try
{
dbEnv.close();
} catch (Exception e2) {}
throw new DatabaseException("Unable to open the SLAMD databases: " + e,
e);
}
// Get the lists of disabled, pending, and running jobs.
disabledJobs = new HashSet<String>();
byte[] disabledBytes = get(null, configDB, Constants.PARAM_DISABLED_JOBS,
false);
if ((disabledBytes != null) && (disabledBytes.length > 0))
{
String disabledStr;
try
{
disabledStr = new String(disabledBytes, "UTF-8");
}
catch (UnsupportedEncodingException uee)
{
disabledStr = new String(disabledBytes);
}
StringTokenizer tokenizer = new StringTokenizer(disabledStr, "\n");
while (tokenizer.hasMoreTokens())
{
disabledJobs.add(tokenizer.nextToken());
}
}
pendingJobs = new HashSet<String>();
byte[] pendingBytes = get(null, configDB, Constants.PARAM_PENDING_JOBS,
false);
if ((pendingBytes != null) && (pendingBytes.length > 0))
{
String pendingStr;
try
{
pendingStr = new String(pendingBytes, "UTF-8");
}
catch (UnsupportedEncodingException uee)
{
pendingStr = new String(pendingBytes);
}
StringTokenizer tokenizer = new StringTokenizer(pendingStr, "\n");
while (tokenizer.hasMoreTokens())
{
pendingJobs.add(tokenizer.nextToken());
}
}
runningJobs = new HashSet<String>();
byte[] runningBytes = get(null, configDB, Constants.PARAM_RUNNING_JOBS,
false);
if ((runningBytes != null) && (runningBytes.length > 0))
{
String runningStr;
try
{
runningStr = new String(runningBytes, "UTF-8");
}
catch (UnsupportedEncodingException uee)
{
runningStr = new String(runningBytes);
}
StringTokenizer tokenizer = new StringTokenizer(runningStr, "\n");
while (tokenizer.hasMoreTokens())
{
runningJobs.add(tokenizer.nextToken());
}
}
}
/**
* Checks to see if a valid SLAMD database exists in the specified location.
*
* @param dbDirectory The path to the directory containing the database
* files.
*
* @return <CODE>true</CODE> if the database exists, or <CODE>false</CODE>
* false if not.
*
* @throws DatabaseException If a problem occurs while attempting to make
* the determination.
*/
public static boolean dbExists(String dbDirectory)
throws DatabaseException
{
// First make sure that the specified directory exists and is actually a
// directory. If it does not exist, then create it if appropriate.
File dbDir = new File(dbDirectory);
if (dbDir.exists())
{
if (! dbDir.isDirectory())
{
throw new DatabaseException("Specified database directory \"" +
dbDirectory +
"\" exists but is not a directory.");
}
}
else
{
return false;
}
// Define the properties that should be used to open the DB environment.
EnvironmentConfig config = new EnvironmentConfig();
config.setAllowCreate(false);
config.setReadOnly(true);
config.setTransactional(true);
// Open the database environment with the specified configuration.
Environment dbEnv = new Environment(dbDir, config);
// Try to open the configuration database.
try
{
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(false);
dbConfig.setReadOnly(true);
Database configDB = dbEnv.openDatabase(null, Constants.DB_NAME_CONFIG,
dbConfig);
configDB.close();
dbEnv.close();
return true;
}
catch (DatabaseNotFoundException dnfe)
{
try
{
dbEnv.close();
} catch (Exception e) {}
return false;
}
catch (DatabaseException de)
{
try
{
dbEnv.close();
} catch (Exception e) {}
throw de;
}
}
/**
* Creates a new SLAMD database if one does not already exist.
*
* @param dbDirectory The path to the directory in which to create the
* database.
*
* @throws DatabaseException If a problem occurs while trying to create the
* database.
*/
public static void createDB(String dbDirectory)
throws DatabaseException
{
// First make sure that the specified directory exists and is actually a
// directory. If it does not exist, then create it if appropriate.
File dbDir = new File(dbDirectory);
if (dbDir.exists())
{
if (! dbDir.isDirectory())
{
throw new DatabaseException("Specified database directory \"" +
dbDirectory +
"\" exists but is not a directory.");
}
}
else
{
try
{
dbDir.mkdirs();
}
catch (Exception e)
{
throw new DatabaseException("Unable to create database directory \"" +
dbDirectory + "\" -- " + e, e);
}
}
// Define the properties that should be used to open the DB environment.
EnvironmentConfig config = new EnvironmentConfig();
config.setAllowCreate(true);
config.setReadOnly(false);
config.setTransactional(true);
config.setTxnNoSync(true);
// Open the database environment with the specified configuration.
Environment dbEnv = new Environment(dbDir, config);
// Open and close the individual DBs, creating them if necessary.
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
dbConfig.setReadOnly(false);
dbConfig.setSortedDuplicates(false);
dbConfig.setTransactional(true);
try
{
dbEnv.openDatabase(null, Constants.DB_NAME_CONFIG, dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_FILE, dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_FOLDER, dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_GROUP, dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_JOB, dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_JOB_GROUP, dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_OPTIMIZING_JOB,
dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_USER, dbConfig).close();
dbEnv.openDatabase(null, Constants.DB_NAME_VIRTUAL_FOLDER,
dbConfig).close();
}
catch (DatabaseException de)
{
try
{
dbEnv.close();
} catch (Exception e) {}
throw de;
}
// Re-open the config DB and Populate it with the most important initial
// configuration.
Database configDB = dbEnv.openDatabase(null, Constants.DB_NAME_CONFIG,
dbConfig);
String[] jobClasses =
{
BurnCPUJobClass.class.getName(),
DSADMImportRateJobClass.class.getName(),
DSCFGImportRateJobClass.class.getName(),
ExecJobClass.class.getName(),
HTTPGetRateJobClass.class.getName(),
IMAPCheckRateJobClass.class.getName(),
LDAPAddAndDeleteRateJobClass.class.getName(),
LDAPAsynchronousModRateJobClass.class.getName(),
LDAPAsynchronousSearchRateJobClass.class.getName(),
LDAPAuthRateJobClass.class.getName(),
LDAPCompareRateJobClass.class.getName(),
LDAPFileBasedModRateJobClass.class.getName(),
LDAPFileBasedSearchRateJobClass.class.getName(),
LDAPMixedLoadJobClass.class.getName(),
LDAPModDNRateJobClass.class.getName(),
LDAPModRateJobClass.class.getName(),
LDAPMultiConnectionSearchRateJobClass.class.getName(),
LDAPPrimeJobClass.class.getName(),
LDAPSearchRateJobClass.class.getName(),
LDAPSearchAndModRateJobClass.class.getName(),
LDAPWaitForDirectoryJobClass.class.getName(),
LDIF2DBImportRateJobClass.class.getName(),
MultiSearchLDAPLoadJobClass.class.getName(),
NullJobClass.class.getName(),
POPCheckRateJobClass.class.getName(),
SiteMinderJobClass.class.getName(),
SMTPSendRateJobClass.class.getName(),
SolarisLDAPAuthRateJobClass.class.getName(),
SQLModRateJobClass.class.getName(),
SQLSearchRateJobClass.class.getName(),
TCPReplayJobClass.class.getName(),
ThroughputTestJobClass.class.getName(),
WeightedSiteMinderJobClass.class.getName(),
LoadVarianceTestJobClass.class.getName(),
BSFJobClass.class.getName(),
ScriptedJobClass.class.getName(),
LogPlaybackJobClass.class.getName()
};
String[] optimizationAlgorithms =
{
SingleStatisticOptimizationAlgorithm.class.getName(),
SingleStatisticWithConstraintOptimizationAlgorithm.class.getName(),
SingleStatisticWithCPUUtilizationOptimizationAlgorithm.class.getName(),
SingleStatisticWithReplicationLatencyOptimizationAlgorithm.class.getName()
};
String[] reportGenerators =
{
HTMLReportGenerator.class.getName(),
PDFReportGenerator.class.getName(),
TextReportGenerator.class.getName()
};
try
{
StringBuilder buffer = new StringBuilder();
for (int i=0; i < jobClasses.length; i++)
{
if (i > 0)
{
buffer.append('\n');
}
buffer.append(jobClasses[i]);
}
put(configDB, Constants.PARAM_JOB_CLASSES, buffer.toString());
buffer = new StringBuilder();
for (int i=0; i < optimizationAlgorithms.length; i++)
{
if (i > 0)
{
buffer.append('\n');
}
buffer.append(optimizationAlgorithms[i]);
}
put(configDB, Constants.PARAM_OPTIMIZATION_ALGORITHMS, buffer.toString());
buffer = new StringBuilder();
for (int i=0; i < reportGenerators.length; i++)
{
if (i > 0)
{
buffer.append('\n');
}
buffer.append(reportGenerators[i]);
}
put(configDB, Constants.PARAM_REPORT_GENERATOR_CLASSES,
buffer.toString());
String logFilename = AdminServlet.getWebInfPath() + '/' +
Constants.DEFAULT_LOG_FILENAME;
put(configDB, Constants.PARAM_LOG_FILENAME, logFilename);
}
catch (DatabaseException de)
{
try
{
configDB.close();
dbEnv.close();
} catch (Exception e) {}
throw de;
}
// Create an "Unclassified" job folder.
Database folderDB = dbEnv.openDatabase(null, Constants.DB_NAME_FOLDER,
dbConfig);
JobFolder folder = new JobFolder(Constants.FOLDER_NAME_UNCLASSIFIED, false,
false, null, null, "Unclassified jobs.",
null, null, null, null);
try
{
put(folderDB, Constants.FOLDER_NAME_UNCLASSIFIED, folder.encode());
}
catch (DatabaseException de)
{
try
{
folderDB.close();
dbEnv.close();
} catch (Exception e) {}
throw de;
}
folderDB.close();
// Close the database environment.
try
{
configDB.close();
} catch (Exception e) {}
dbEnv.close();
}
/**
* Opens all the databases used in the SLAMD database environment. If the
* databases are already open, then no action will be taken.
*
* @param createIfNecessary Indicates whether any database(s) that do not
* exist should be created.
*
* @throws DatabaseException If a problem occurs while opening the
* databases. Note that if an exception is thrown,
* then all databases will be closed but the
* environment will still be open.
*/
public void openDatabases(boolean createIfNecessary)
throws DatabaseException
{
// See if the database is already open. If so, then don't do anything.
synchronized (dbMutex)
{
if (dbsOpen)
{
return;
}
// The database environment must be open in order to proceed.
if (! environmentOpen)
{
String message = "Unable to open SLAMD databases because the " +
"database environment is not open.";
slamdServer.logMessage(Constants.LOG_LEVEL_ANY, message);
throw new DatabaseException(message);
}
// Create the database configuration that will be used to open the
// databases.
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(createIfNecessary);
dbConfig.setReadOnly(readOnly);
dbConfig.setSortedDuplicates(false);
dbConfig.setTransactional(true);
// Iterate through each of the databases and open them.
try
{
configDB = openDB(Constants.DB_NAME_CONFIG, dbConfig);
fileDB = openDB(Constants.DB_NAME_FILE, dbConfig);
folderDB = openDB(Constants.DB_NAME_FOLDER, dbConfig);
groupDB = openDB(Constants.DB_NAME_GROUP, dbConfig);
jobDB = openDB(Constants.DB_NAME_JOB, dbConfig);
jobGroupDB = openDB(Constants.DB_NAME_JOB_GROUP, dbConfig);
optimizingJobDB = openDB(Constants.DB_NAME_OPTIMIZING_JOB, dbConfig);
userDB = openDB(Constants.DB_NAME_USER, dbConfig);
virtualFolderDB = openDB(Constants.DB_NAME_VIRTUAL_FOLDER, dbConfig);
}
catch (DatabaseException dbe)
{
closeDatabases(true);
throw dbe;
}
// Populate the configuration map.
configHash = new HashMap<String,String>();
Cursor configCursor = configDB.openCursor(null, new CursorConfig());
DatabaseEntry keyEntry = new DatabaseEntry();
DatabaseEntry valueEntry = new DatabaseEntry();
OperationStatus status = configCursor.getFirst(keyEntry, valueEntry,
LockMode.DEFAULT);
while (status == OperationStatus.SUCCESS)
{
try
{
String key = new String(keyEntry.getData(), "UTF-8");
String value = new String(valueEntry.getData(), "UTF-8");
configHash.put(key, value);
}
catch (UnsupportedEncodingException uee)
{
String key = new String(keyEntry.getData());
String value = new String(valueEntry.getData());
configHash.put(key, value);
}
status = configCursor.getNext(keyEntry, valueEntry, LockMode.DEFAULT);
}
configCursor.close();
// At this point, all the databases should be open.
dbsOpen = true;
}
}
/**
* Opens the specified database with the given configuration.
*
* @param dbName The name of the database to open.
* @param dbConfig The configuration to use to open the database.
*
* @return The database that was opened.
*
* @throws DatabaseException If a problem occurs while opening the database.
*/
private Database openDB(String dbName, DatabaseConfig dbConfig)
throws DatabaseException
{
Database db = dbEnv.openDatabase(null, dbName, dbConfig);
PreloadConfig preloadConfig = new PreloadConfig();
preloadConfig.setLoadLNs(false);
db.preload(preloadConfig);
return db;
}
/**
* Closes all databases in the environment. If the databases are already
* closed then this method will do anything.
*
* @param abortTransactions Indicates whether any transactions in progress
* should be aborted. If not and there are active
* transactions, then this method will fail.
*
* @throws DatabaseException If there are active transactions and this
* method is configured to not abort them.
*/
public void closeDatabases(boolean abortTransactions)
throws DatabaseException
{
synchronized (dbMutex)
{
// If the databases aren't open then don't do anything.
if (! dbsOpen)
{
return;
}
// If there are active transactions, then either abort them or fail.
if (! activeTransactions.isEmpty())
{
if (abortTransactions)
{
for (Transaction txn : activeTransactions)
{
try
{
txn.abort();
} catch (Exception e) {}
}
activeTransactions.clear();
}
else
{
String message = "Not closing databases because there are active " +
"transactions and this method has been configured " +
"to not abort them.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
}
// Close all the databases.
closeDB(configDB);
closeDB(fileDB);
closeDB(folderDB);
closeDB(groupDB);
closeDB(jobDB);
closeDB(jobGroupDB);
closeDB(optimizingJobDB);
closeDB(userDB);
closeDB(virtualFolderDB);
dbsOpen = false;
}
}
/**
* Closes the provided database.
*
* @param db The database to be closed.
*/
private void closeDB(Database db)
{
if (db != null)
{
try
{
db.close();
} catch (Exception e) {}
}
}
/**
* Closes the database environment. This will only be allowed if the
* databases have already been closed.
*
* @throws DatabaseException If the databases are still open.
*/
public void closeEnvironment()
throws DatabaseException
{
synchronized (dbMutex)
{
// Make sure that there are no databases open.
if (dbsOpen)
{
String message = "Unable to close the environment because the " +
"databases are still open.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
// Ensure that the environment is synchronized.
try
{
dbEnv.sync();
} catch (Exception e) {}
// Close the database environment.
try
{
dbEnv.close();
} catch (Exception e) {}
environmentOpen = false;
}
}
/**
* Writes data from the specified job folder(s) in a form that is suitable for
* importing into another SLAMD server instance.
*
* @param realFolderNames The names of the real job folders to include in
* the export.
* @param virtualFolderNames The names of the virtual job folders to include
* in the export.
* @param jobGroupNames The names of the job groups to include in the
* export.
* @param outputStream The output stream to which the export data
* should be written.
*
* @throws DatabaseException If a problem occurs while interacting with the
* database.
*
* @throws IOException If a problem occurs while writing to the provided
* output stream.
*/
public void exportFolderData(String[] realFolderNames,
String[] virtualFolderNames,
String[] jobGroupNames,
OutputStream outputStream)
throws DatabaseException, IOException
{
if ((realFolderNames.length == 0) && (virtualFolderNames.length == 0) &&
(jobGroupNames.length == 0))
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"No folders or job groups specified to include " +
"in the export.");
return;
}
ASN1Writer asn1Writer = new ASN1Writer(outputStream);
// First, the real job folders. We have to keep everything consistent
// within a folder, so we'll use a transaction to protect it.
for (int i=0; i < realFolderNames.length; i++)
{
Transaction txn = getTransaction();
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Beginning export for folder \"" +
realFolderNames[i] + '"');
try
{
// Retrieve the entry for the folder itself.
byte[] folderBytes = get(txn, folderDB, realFolderNames[i], false);
if (folderBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find job folder " +
realFolderNames[i] +
" in the configuration database.");
continue;
}
JobFolder folder = JobFolder.decode(folderBytes);
ASN1Element[] elements =
{
new ASN1OctetString(Constants.DB_NAME_FOLDER),
new ASN1OctetString(realFolderNames[i]),
new ASN1OctetString(folderBytes)
};
asn1Writer.writeElement(new ASN1Sequence(elements));
// Get the set of jobs contained in that folder.
String[] jobIDs = folder.getJobIDs();
for (int j=0; j < jobIDs.length; j++)
{
try
{
byte[] jobBytes = get(txn, jobDB, jobIDs[j], false);
if (jobBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find job " + jobIDs[j] +
" referenced in folder " +
realFolderNames[i]);
continue;
}
elements = new ASN1Element[]
{
new ASN1OctetString(Constants.DB_NAME_JOB),
new ASN1OctetString(jobIDs[j]),
new ASN1OctetString(jobBytes)
};
asn1Writer.writeElement(new ASN1Sequence(elements));
}
catch (IOException ioe)
{
throw ioe;
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to retrieve information about job " +
jobIDs[j] + " from the configuration " +
"database: " + e);
}
}
// Get the set of optimizing jobs contained in the folder.
String[] optimizingJobIDs = folder.getOptimizingJobIDs();
for (int j=0; j < optimizingJobIDs.length; j++)
{
try
{
byte[] optimizingJobBytes = get(txn, optimizingJobDB,
optimizingJobIDs[j], false);
if (optimizingJobBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find optimizing job " +
optimizingJobIDs[j] + " referenced in " +
"folder " + realFolderNames[i]);
continue;
}
elements = new ASN1Element[]
{
new ASN1OctetString(Constants.DB_NAME_OPTIMIZING_JOB),
new ASN1OctetString(optimizingJobIDs[j]),
new ASN1OctetString(optimizingJobBytes)
};
asn1Writer.writeElement(new ASN1Sequence(elements));
}
catch (IOException ioe)
{
throw ioe;
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to retrieve information about " +
"optimizing job " + optimizingJobIDs[j] +
" from the configuration database: " + e);
}
}
// Get the set of uploaded files contained in the folder.
String[] fileNames = folder.getFileNames();
for (int j=0; j < fileNames.length; j++)
{
try
{
String key = realFolderNames[i] + '\t' + fileNames[j];
byte[] fileBytes = get(txn, fileDB, key, false);
if (fileBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find uploaded file " +
fileNames[j] + " referenced in folder " +
realFolderNames[i]);
continue;
}
elements = new ASN1Element[]
{
new ASN1OctetString(Constants.DB_NAME_FILE),
new ASN1OctetString(fileNames[j]),
new ASN1OctetString(fileBytes)
};
asn1Writer.writeElement(new ASN1Sequence(elements));
}
catch (IOException ioe)
{
throw ioe;
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to retrieve information about " +
"uploaded " + "file " + fileNames[j] +
" from the configuration database: " + e);
}
}
}
catch (DatabaseException de)
{
throw de;
}
catch (IOException ioe)
{
throw ioe;
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException("Unexpected error occurred while " +
"performing the export: " + e, e);
}
finally
{
abortTransaction(txn);
}
outputStream.flush();
}
// Next, iterate through the virtual folders. In this case, we just need
// the folder itself since the jobs are contained elsewhere and were
// hopefully included in the export of the real folders, so there is no need
// for a transaction.
for (int i=0; i < virtualFolderNames.length; i++)
{
try
{
// Retrieve the entry for the virtual folder itself.
byte[] virtualFolderBytes = get(null, virtualFolderDB,
virtualFolderNames[i], false);
if (virtualFolderBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find virtual job folder " +
virtualFolderNames[i] +
" in the configuration database.");
continue;
}
ASN1Element[] elements =
{
new ASN1OctetString(Constants.DB_NAME_VIRTUAL_FOLDER),
new ASN1OctetString(virtualFolderNames[i]),
new ASN1OctetString(virtualFolderBytes)
};
asn1Writer.writeElement(new ASN1Sequence(elements));
}
catch (DatabaseException de)
{
throw de;
}
catch (IOException ioe)
{
throw ioe;
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException("Unexpected error occurred while " +
"performing the export: " + e, e);
}
outputStream.flush();
}
// Next, iterate through the job groups. In this case, a job group is a
// single entity in the database, so there is no need to protect this with a
// transaction.
for (int i=0; i < jobGroupNames.length; i++)
{
try
{
// Retrieve the entry for the job group itself.
byte[] jobGroupBytes = get(null, jobGroupDB, jobGroupNames[i], false);
if (jobGroupBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find job group " +
jobGroupNames[i] +
" in the configuration database.");
continue;
}
ASN1Element[] elements =
{
new ASN1OctetString(Constants.DB_NAME_JOB_GROUP),
new ASN1OctetString(jobGroupNames[i]),
new ASN1OctetString(jobGroupBytes)
};
asn1Writer.writeElement(new ASN1Sequence(elements));
}
catch (DatabaseException de)
{
throw de;
}
catch (IOException ioe)
{
throw ioe;
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException("Unexpected error occurred while " +
"performing the export: " + e, e);
}
outputStream.flush();
}
}
/**
* Reads import data from the provided input stream and imports it into the
* database.
*
* @param inputStream The input stream from which to read the data to
* import.
* @param progressWriter The print writer that should be used to write
* progress about the import to the end user.
* @param writeHTML Indicates whether the progress information should
* be written in HTML format. If not, then it will be
* plain text.
*
* @return <CODE>true</CODE> if the import was completely successful, or
* <CODE>false</CODE> if one or more problems were encountered.
*/
public boolean importFolderData(InputStream inputStream,
PrintWriter progressWriter, boolean writeHTML)
{
ASN1Reader asn1Reader = new ASN1Reader(inputStream);
ASN1Element element;
try
{
element = nextElement(asn1Reader, progressWriter, writeHTML);
}
catch (Exception e)
{
return false;
}
boolean completeSuccess = true;
int numRecords = 0;
long startTime = System.currentTimeMillis();
while (element != null)
{
numRecords++;
byte[] data;
String dbName;
String keyName;
try
{
ASN1Element[] elements = element.decodeAsSequence().getElements();
dbName = elements[0].decodeAsOctetString().getStringValue();
keyName = elements[1].decodeAsOctetString().getStringValue();
data = elements[2].decodeAsOctetString().getValue();
}
catch (Exception e)
{
progressWriter.println("Unable to decode ASN.1 element read from the " +
"input stream as a sequence of three " +
"elements:");
if (writeHTML)
{
progressWriter.println("<BR>");
progressWriter.println("<PRE>");
}
progressWriter.println(JobClass.stackTraceToString(e));
if (writeHTML)
{
progressWriter.println("</PRE>");
progressWriter.println("<BR>");
}
try
{
element = nextElement(asn1Reader, progressWriter, writeHTML);
}
catch (Exception e2)
{
return false;
}
completeSuccess = false;
continue;
}
Database db = getDB(dbName);
if (db == null)
{
progressWriter.println("Unable to retrieve a reference to database " +
dbName + " from the DB environment.");
if (writeHTML)
{
progressWriter.println("<BR><BR>");
}
else
{
progressWriter.println();
}
try
{
element = nextElement(asn1Reader, progressWriter, writeHTML);
}
catch (Exception e)
{
return false;
}
completeSuccess = false;
continue;
}
try
{
// See if the specified record exists. If so, then see if it's a folder
// and merge the contents together. Otherwise, refuse to overwrite the
// existing record.
byte[] existingData;
if ((existingData = get(null, db, keyName, false)) != null)
{
if (dbName.equals(Constants.DB_NAME_FOLDER))
{
JobFolder existingFolder = getFolder(keyName);
JobFolder newFolder = JobFolder.decode(existingData);
String[] newJobIDs = newFolder.getJobIDs();
for (int i=0; i < newJobIDs.length; i++)
{
if (! existingFolder.containsJobID(newJobIDs[i]))
{
existingFolder.addJobID(newJobIDs[i]);
}
}
String[] newOptimizingJobIDs = newFolder.getOptimizingJobIDs();
for (int i=0; i < newOptimizingJobIDs.length; i++)
{
if (! existingFolder.containsOptimizingJobID(
newOptimizingJobIDs[i]))
{
existingFolder.addOptimizingJobID(newOptimizingJobIDs[i]);
}
}
String[] newChildNames = newFolder.getChildNames();
for (int i=0; i < newChildNames.length; i++)
{
if (! existingFolder.containsChildName(newChildNames[i]))
{
existingFolder.addChildName(newChildNames[i]);
}
}
String[] newFileNames = newFolder.getFileNames();
for (int i=0; i < newFileNames.length; i++)
{
if (! existingFolder.containsFileName(newFileNames[i]))
{
existingFolder.addFileName(newFileNames[i]);
}
}
byte[] updatedEntry = existingFolder.encode();
put(null, db, keyName, updatedEntry);
if (writeHTML)
{
progressWriter.println("<SPAN CLASS=\"" +
Constants.STYLE_WARNING_TEXT +
"\">Updated existing job folder \"" +
keyName + "\" in database \"" + dbName +
"\".</SPAN>");
}
else
{
progressWriter.println("Updated existing job folder \"" +
keyName + "\" in database \"" + dbName +
"\".");
}
}
else
{
if (writeHTML)
{
progressWriter.println("<SPAN CLASS=\"" +
Constants.STYLE_WARNING_TEXT +
"\">Refusing to overwrite existing " +
"record with key \"" + keyName +
"\" in database \"" + dbName +
"\".</SPAN>");
}
else
{
progressWriter.println("Refusing to overwrite existing record " +
"with key \"" + keyName +
"\" in database \"" + dbName + "\".");
}
}
}
else
{
put(null, db, keyName, data);
progressWriter.println("Successfully wrote record with key \"" +
keyName + "\" to database \"" + dbName +
"\".");
}
if (writeHTML)
{
progressWriter.println("<BR><BR>");
}
else
{
progressWriter.println();
}
}
catch (Exception e)
{
progressWriter.println("Unable to write entry with key \"" + keyName +
"\" to database \"" + dbName + "\":");
if (writeHTML)
{
progressWriter.println("<BR>");
progressWriter.println("<PRE>");
}
progressWriter.println(JobClass.stackTraceToString(e));
if (writeHTML)
{
progressWriter.println("</PRE>");
progressWriter.println("<BR>");
}
try
{
element = nextElement(asn1Reader, progressWriter, writeHTML);
}
catch (Exception e2)
{
return false;
}
completeSuccess = false;
continue;
}
try
{
element = nextElement(asn1Reader, progressWriter, writeHTML);
}
catch (Exception e)
{
return false;
}
completeSuccess = false;
continue;
}
long endTime = System.currentTimeMillis();
progressWriter.println("<BR><BR>");
progressWriter.println("Import complete.<BR><BR>");
progressWriter.println("Processed " + numRecords + " records in " +
(endTime - startTime) + " milliseconds.<BR>");
return completeSuccess;
}
/**
* Retrieves the next ASN.1 element from the provided reader.
*
* @param reader The ASN.1 reader from which to read the next
* element.
* @param progressWriter The print writer to which progress information may
* be written.
* @param writeHTML Indicates whether information written to the print
* writer should be in HTML form.
*
* @return The next ASN.1 element read from the provided reader.
*
* @throws IOException If a problem occurs while reading from the provided
* reader.
*
* @throws ASN1Exception If a problem occurs while trying to decode the data
* read as an ASN.1 element.
*/
private static ASN1Element nextElement(ASN1Reader reader,
PrintWriter progressWriter,
boolean writeHTML)
throws IOException, ASN1Exception
{
try
{
return reader.readElement();
}
catch (IOException ioe)
{
progressWriter.println("I/O error encountered while attempting to read " +
"from the provided input stream:");
if (writeHTML)
{
progressWriter.println("<BR>");
progressWriter.println("<PRE>");
}
progressWriter.println(JobClass.stackTraceToString(ioe));
if (writeHTML)
{
progressWriter.println("</PRE>");
progressWriter.println("<BR>");
}
throw ioe;
}
catch (ASN1Exception ae)
{
progressWriter.println("Unable to decode data read from input stream " +
"as an ASN.1 element:");
if (writeHTML)
{
progressWriter.println("<BR>");
progressWriter.println("<PRE>");
}
progressWriter.println(JobClass.stackTraceToString(ae));
if (writeHTML)
{
progressWriter.println("</PRE>");
progressWriter.println("<BR>");
}
throw ae;
}
catch (Exception e)
{
String message = "Unexpected error while reading ASN.1 element from " +
"input stream:";
progressWriter.println(message);
if (writeHTML)
{
progressWriter.println("<BR>");
progressWriter.println("<PRE>");
}
progressWriter.println(JobClass.stackTraceToString(e));
if (writeHTML)
{
progressWriter.println("</PRE>");
progressWriter.println("<BR>");
}
throw new ASN1Exception(message + ": " + e, e);
}
}
/**
* Retrieves the names of the databases contained in the DB environment.
*
* @return The names of the databases contained in the DB environment.
*
* @throws DatabaseException If a problem occurs while obtaining the list of
* database names.
*/
public String[] getDBNames()
throws DatabaseException
{
List<String> list = dbEnv.getDatabaseNames();
String[] dbNames = new String[list.size()];
list.toArray(dbNames);
return dbNames;
}
/**
* Retrieves a handle to the database with the specified name.
*
* @param dbName The name of the database to retrieve.
*
* @return The requested database, or <CODE>null</CODE> if there is no such
* database.
*/
private Database getDB(String dbName)
{
Database db = null;
if (dbName.equals(Constants.DB_NAME_CONFIG))
{
db = configDB;
}
else if (dbName.equals(Constants.DB_NAME_FILE))
{
db = fileDB;
}
else if (dbName.equals(Constants.DB_NAME_FOLDER))
{
db = folderDB;
}
else if (dbName.equals(Constants.DB_NAME_GROUP))
{
db = groupDB;
}
else if (dbName.equals(Constants.DB_NAME_JOB))
{
db = jobDB;
}
else if (dbName.equals(Constants.DB_NAME_JOB_GROUP))
{
db = jobGroupDB;
}
else if (dbName.equals(Constants.DB_NAME_OPTIMIZING_JOB))
{
db = optimizingJobDB;
}
else if (dbName.equals(Constants.DB_NAME_USER))
{
db = userDB;
}
else if (dbName.equals(Constants.DB_NAME_VIRTUAL_FOLDER))
{
db = virtualFolderDB;
}
return db;
}
/**
* Retrieves a list of all the keys in the specified database.
*
* @param dbName The name of the database for which to retrieve the list of
* keys.
*
* @return A list of all the keys in the specified database.
*
* @throws DatabaseException If a problem occurs while obtaining the list of
* database keys.
*/
public String[] getDBKeys(String dbName)
throws DatabaseException
{
Database db = getDB(dbName);
if (db == null)
{
throw new DatabaseException("No database found with a name of \"" +
dbName + '"');
}
CursorConfig cursorConfig = new CursorConfig();
cursorConfig.setReadUncommitted(true);
Cursor cursor = db.openCursor(null, cursorConfig);
ArrayList<String> keyList = new ArrayList<String>();
try
{
DatabaseEntry keyEntry = new DatabaseEntry();
DatabaseEntry dataEntry = new DatabaseEntry();
OperationStatus status = cursor.getFirst(keyEntry, dataEntry,
LockMode.READ_UNCOMMITTED);
while (status == OperationStatus.SUCCESS)
{
keyList.add(new String(keyEntry.getData(), "UTF-8"));
status = cursor.getNext(keyEntry, dataEntry, LockMode.READ_UNCOMMITTED);
}
String[] keys = new String[keyList.size()];
keyList.toArray(keys);
return keys;
}
catch (DatabaseException de)
{
throw de;
}
catch (Exception e)
{
throw new DatabaseException("Unexpected exception caught while " +
"retrieving DB keys: " + e, e);
}
finally
{
cursor.close();
}
}
/**
* Retrieves the data associated with the specified key in the given database.
*
* @param dbName The name of the database from which the data should be
* retrieved.
* @param dbKey The key associated with the data to retrieve.
*
* @return The data associated with the specified key in the given database,
* or <CODE>null</CODE> if there is no such database key.
*
* @throws DatabaseException If a problem occurs while interacting with the
* database.
*/
public byte[] getDBData(String dbName, String dbKey)
throws DatabaseException
{
Database db = getDB(dbName);
if (db == null)
{
return null;
}
return get(null, db, dbKey, false);
}
/**
* Retrieves the value of the specified configuration parameter from the
* configuration database.
*
* @param parameterName The name of the configuration parameter to
* retrieve.
*
* @return The value of the specified configuration parameter, or
* <CODE>null</CODE> if there is no such parameter.
*/
public String getConfigParameter(String parameterName)
{
return configHash.get(parameterName);
}
/**
* Retrieves the value of the specified configuration parameter from the
* configuration database.
*
* @param parameterName The name of the configuration parameter to
* retrieve.
* @param defaultValue The value to use for the parameter if the specified
* key is not present in the database.
*
* @return The value of the specified configuration parameter, or the
* provided default value if there is no such parameter.
*/
public String getConfigParameter(String parameterName, String defaultValue)
{
String value = configHash.get(parameterName);
if (value == null)
{
return defaultValue;
}
return value;
}
/**
* Retrieves the value of the specified configuration parameter from the
* configuration database.
*
* @param parameterName The name of the configuration parameter to
* retrieve.
* @param defaultValue The value to use for the parameter if the specified
* key is not present in the database.
*
* @return The value of the specified configuration parameter, or the
* provided default value if there is no such parameter.
*/
public boolean getConfigParameter(String parameterName, boolean defaultValue)
{
String value = configHash.get(parameterName);
if (value == null)
{
return defaultValue;
}
if (value.equals("true") || value.equals("yes") || value.equals("on") ||
value.equals("1"))
{
return true;
}
else if (value.equals("false") || value.equals("no") ||
value.equals("off") || value.equals("0"))
{
return false;
}
return defaultValue;
}
/**
* Retrieves the value of the specified configuration parameter from the
* configuration database.
*
* @param parameterName The name of the configuration parameter to
* retrieve.
* @param defaultValue The value to use for the parameter if the specified
* key is not present in the database.
*
* @return The value of the specified configuration parameter, or the
* provided default value if there is no such parameter.
*/
public int getConfigParameter(String parameterName, int defaultValue)
{
String value = configHash.get(parameterName);
if (value == null)
{
return defaultValue;
}
try
{
return Integer.parseInt(value);
}
catch (Exception e)
{
return defaultValue;
}
}
/**
* Sets the value of the specified parameter in the configuration database.
*
* @param parameterName The name of the parameter to set.
* @param parameterValue The value to use for the parameter.
* @param notifySubscribers Indicates whether the configuration subscribers
* registered with the SLAMD server should be
* notified of the change.
*
* @throws DatabaseException If a problem occurs while attempting to store
* the configuration parameter.
*/
public void putConfigParameter(String parameterName, String parameterValue,
boolean notifySubscribers)
throws DatabaseException
{
OperationStatus status = put(null, configDB, parameterName,
ASN1Element.getBytes(parameterValue));
if (status != OperationStatus.SUCCESS)
{
throw new DatabaseException("Unexpected status returned from put: " +
status);
}
configHash.put(parameterName, parameterValue);
if (! notifySubscribers)
{
return;
}
synchronized (configSubscribers)
{
for (int i=0; i < configSubscribers.size(); i++)
{
ConfigSubscriber s = configSubscribers.get(i);
try
{
s.refreshSubscriberConfiguration(parameterName);
}
catch (SLAMDServerException sse)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Error while notifying subscriber " +
s.getSubscriberName() + " of change to " +
" configuration parameter " + parameterName +
" with value " + parameterValue + " -- " +
sse);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(sse));
}
}
}
}
/**
* Sets the value of the specified parameter in the configuration database.
*
* @param parameterName The name of the parameter to set.
* @param parameterValue The value to use for the parameter.
* @param notifySubscribers Indicates whether the configuration subscribers
* registered with the SLAMD server should be
* notified of the change.
*
* @throws DatabaseException If a problem occurs while attempting to store
* the configuration parameter.
*/
public void putConfigParameter(String parameterName, boolean parameterValue,
boolean notifySubscribers)
throws DatabaseException
{
OperationStatus status =
put(null, configDB, parameterName,
ASN1Element.getBytes(String.valueOf(parameterValue)));
if (status != OperationStatus.SUCCESS)
{
throw new DatabaseException("Unexpected status returned from put: " +
status);
}
configHash.put(parameterName, String.valueOf(parameterValue));
if (! notifySubscribers)
{
return;
}
synchronized (configSubscribers)
{
for (int i=0; i < configSubscribers.size(); i++)
{
ConfigSubscriber s = configSubscribers.get(i);
try
{
s.refreshSubscriberConfiguration(parameterName);
}
catch (SLAMDServerException sse)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Error while notifying subscriber " +
s.getSubscriberName() + " of change to " +
" configuration parameter " + parameterName +
" with value " + parameterValue + " -- " +
sse);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(sse));
}
}
}
}
/**
* Sets the value of the specified parameter in the configuration database.
*
* @param parameterName The name of the parameter to set.
* @param parameterValue The value to use for the parameter.
* @param notifySubscribers Indicates whether the configuration subscribers
* registered with the SLAMD server should be
* notified of the change.
*
* @throws DatabaseException If a problem occurs while attempting to store
* the configuration parameter.
*/
public void putConfigParameter(String parameterName, int parameterValue,
boolean notifySubscribers)
throws DatabaseException
{
OperationStatus status =
put(null, configDB, parameterName,
ASN1Element.getBytes(String.valueOf(parameterValue)));
if (status != OperationStatus.SUCCESS)
{
throw new DatabaseException("Unexpected status returned from put: " +
status);
}
configHash.put(parameterName, String.valueOf(parameterValue));
if (! notifySubscribers)
{
return;
}
synchronized (configSubscribers)
{
for (int i=0; i < configSubscribers.size(); i++)
{
ConfigSubscriber s = configSubscribers.get(i);
try
{
s.refreshSubscriberConfiguration(parameterName);
}
catch (SLAMDServerException sse)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Error while notifying subscriber " +
s.getSubscriberName() + " of change to " +
" configuration parameter " + parameterName +
" with value " + parameterValue + " -- " +
sse);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(sse));
}
}
}
}
/**
* Removes information about the specified parameter from the configuration
* database.
*
* @param parameterName The name of the configuration parameter to remove
* from the configuration database.
*
* @throws DatabaseException If problem occurs while interacting with the
* configuration database.
*/
public void removeConfigParameter(String parameterName)
throws DatabaseException
{
delete(null, configDB, parameterName);
configHash.remove(parameterName);
synchronized (configSubscribers)
{
for (int i=0; i < configSubscribers.size(); i++)
{
ConfigSubscriber s = configSubscribers.get(i);
try
{
s.refreshSubscriberConfiguration(parameterName);
}
catch (SLAMDServerException sse)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Error while notifying subscriber " +
s.getSubscriberName() + " of removal of " +
" configuration parameter " + parameterName +
" -- " + sse);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(sse));
}
}
}
}
/**
* Retrieves the specified folder from the configuration database.
*
* @param folderName The name of the folder to retrieve.
*
* @return The requested folder, or <CODE>null</CODE> if no such folder
* exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while trying to decode the
* folder from the configuration database.
*/
public JobFolder getFolder(String folderName)
throws DatabaseException, DecodeException
{
byte[] folderBytes = get(null, folderDB, folderName, false);
if (folderBytes == null)
{
return null;
}
return JobFolder.decode(folderBytes);
}
/**
* Retrieves a list of all job folders defined in the configuration database.
*
* @return A list of all job folders defined in the configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public JobFolder[] getFolders()
throws DatabaseException
{
synchronized (dbMutex)
{
ArrayList<JobFolder> folderList = new ArrayList<JobFolder>();
CursorConfig cursorConfig = new CursorConfig();
cursorConfig.setReadUncommitted(false);
Cursor cursor = folderDB.openCursor(null, cursorConfig);
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry value = new DatabaseEntry();
OperationStatus status = cursor.getFirst(key, value, LockMode.DEFAULT);
while (status == OperationStatus.SUCCESS)
{
try
{
JobFolder folder = JobFolder.decode(value.getData());
if (folder.getFolderName().equals(Constants.FOLDER_NAME_UNCLASSIFIED))
{
folderList.add(0, folder);
}
else
{
folderList.add(folder);
}
} catch (Exception e) {}
status = cursor.getNext(key, value, LockMode.DEFAULT);
}
cursor.close();
JobFolder[] folders = new JobFolder[folderList.size()];
folderList.toArray(folders);
return folders;
}
}
/**
* Writes information about the provided job folder into the configuration
* database. If the specified folder already exists, then it will be
* replaced.
*
* @param jobFolder The folder to write to the configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void writeFolder(JobFolder jobFolder)
throws DatabaseException
{
put(null, folderDB, jobFolder.getFolderName(), jobFolder.encode());
}
/**
* Removes information about the specified job folder from the configuration
* database. If the specified folder does not exist, then no action will be
* taken.
*
* @param folderName The name of the job folder to remove from the
* configuration database.
* @param deleteContents Indicates whether to remove the jobs, optimizing
* jobs, and uploaded files associated with the
* folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void removeFolder(String folderName, boolean deleteContents)
throws DatabaseException
{
Transaction txn = getTransaction();
JobFolder folder;
try
{
byte[] folderBytes = get(txn, folderDB, folderName, true);
folder = JobFolder.decode(folderBytes);
}
catch (Exception e)
{
txn.abort();
throw new DatabaseException("Unable to retrieve job folder " +
folderName + " -- " + e, e);
}
String[] childNames = folder.getChildNames();
if ((childNames != null) && (childNames.length > 0))
{
abortTransaction(txn);
throw new DatabaseException("Unable to delete job folder " + folderName +
" because it contains one or more child " +
"folders.");
}
String[] jobIDs = folder.getJobIDs();
if ((jobIDs != null) && (jobIDs.length > 0))
{
if (deleteContents)
{
try
{
for (int i=0; i < jobIDs.length; i++)
{
delete(txn, jobDB, jobIDs[i]);
}
}
catch (Exception e)
{
abortTransaction(txn);
throw new DatabaseException("Unable to delete jobs contained in " +
"folder " + folderName + " -- " + e, e);
}
}
else
{
abortTransaction(txn);
throw new DatabaseException("Cannot delete job folder " + folderName +
" because it still contains one or more " +
"jobs.");
}
}
String[] optimizingJobIDs = folder.getOptimizingJobIDs();
if ((optimizingJobIDs != null) && (optimizingJobIDs.length > 0))
{
if (deleteContents)
{
try
{
for (int i=0; i < optimizingJobIDs.length; i++)
{
delete(txn, optimizingJobDB, optimizingJobIDs[i]);
}
}
catch (Exception e)
{
abortTransaction(txn);
throw new DatabaseException("Unable to delete optimizing jobs " +
"contained in folder " + folderName +
" -- " + e, e);
}
}
else
{
abortTransaction(txn);
throw new DatabaseException("Cannot delete job folder " + folderName +
" because it still contains one or more " +
"optimizing jobs.");
}
}
String[] fileNames = folder.getFileNames();
if ((fileNames != null) && (fileNames.length > 0))
{
if (deleteContents)
{
try
{
for (int i=0; i < fileNames.length; i++)
{
delete(txn, fileDB, folderName + '\t' + fileNames[i]);
}
}
catch (Exception e)
{
abortTransaction(txn);
throw new DatabaseException("Unable to delete uploaded files " +
"contained in job folder " + folderName +
" -- " + e, e);
}
}
else
{
abortTransaction(txn);
throw new DatabaseException("Cannot delete job folder " + folderName +
" because it still contains one or more " +
"uploaded files.");
}
}
try
{
delete(txn, folderDB, folderName);
commitTransaction(txn);
}
catch (Exception e)
{
abortTransaction(txn);
throw new DatabaseException("Cannot delete job folder " + folderName +
" -- " + e, e);
}
}
/**
* Retrieves the specified virtual folder from the configuration database.
*
* @param folderName The name of the virtual folder to retrieve.
*
* @return The requested virtual folder, or <CODE>null</CODE> if no such
* folder exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while trying to decode the
* virtual folder from the configuration database.
*/
public JobFolder getVirtualFolder(String folderName)
throws DatabaseException, DecodeException
{
byte[] folderBytes = get(null, virtualFolderDB, folderName, false);
if (folderBytes == null)
{
return null;
}
return JobFolder.decode(folderBytes);
}
/**
* Retrieves a list of all virtual job folders defined in the configuration
* database.
*
* @return A list of all virtual job folders defined in the configuration
* database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public JobFolder[] getVirtualFolders()
throws DatabaseException
{
synchronized (dbMutex)
{
ArrayList<JobFolder> folderList = new ArrayList<JobFolder>();
CursorConfig cursorConfig = new CursorConfig();
cursorConfig.setReadUncommitted(false);
Cursor cursor = virtualFolderDB.openCursor(null, cursorConfig);
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry value = new DatabaseEntry();
OperationStatus status = cursor.getFirst(key, value, LockMode.DEFAULT);
while (status == OperationStatus.SUCCESS)
{
try
{
folderList.add(JobFolder.decode(value.getData()));
} catch (Exception e) {}
status = cursor.getNext(key, value, LockMode.DEFAULT);
}
cursor.close();
JobFolder[] folders = new JobFolder[folderList.size()];
folderList.toArray(folders);
return folders;
}
}
/**
* Writes information about the provided virtual job folder into the
* configuration database. If the specified folder already exists, then it
* will be replaced.
*
* @param jobFolder The virtual folder to write to the configuration
* database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void writeVirtualFolder(JobFolder jobFolder)
throws DatabaseException
{
put(null, virtualFolderDB, jobFolder.getFolderName(), jobFolder.encode());
}
/**
* Removes information about the specified virtual job folder from the
* configuration database. If the specified folder does not exist, then no
* action will be taken.
*
* @param folderName The name of the virtual job folder to remove from the
* configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void removeVirtualFolder(String folderName)
throws DatabaseException
{
delete(null, virtualFolderDB, folderName);
}
/**
* Retrieves the specified job from the configuration database.
*
* @param jobID The job ID of the job to retrieve from the database.
*
* @return The requested job from the configuration database, or
* <CODE>null</CODE> if no such job exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while decoding the job
* information.
*/
public Job getJob(String jobID)
throws DatabaseException, DecodeException
{
byte[] jobBytes = get(null, jobDB, jobID, false);
if (jobBytes == null)
{
return null;
}
return Job.decode(slamdServer, jobBytes);
}
/**
* Retrieves summary information for the specified job from the configuration
* database.
*
* @param jobID The job ID of the job to retrieve from the database.
*
* @return Summary information for the requested job from the configuration
* database, or <CODE>null</CODE> if no such job exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while decoding the job
* information.
*/
public Job getSummaryJob(String jobID)
throws DatabaseException, DecodeException
{
byte[] jobBytes = get(null, jobDB, jobID, false);
if (jobBytes == null)
{
return null;
}
return Job.decodeSummaryJob(slamdServer, jobBytes);
}
/**
* Retrieves the set of jobs contained in the specified folder of the
* configuration database.
*
* @param folderName The name of the folder for which to retrieve the
* associated jobs.
*
* @return The set of jobs contained in the specified folder of the
* configuration database, or <CODE>null</CODE> if there is no such
* folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified folder.
*/
public Job[] getJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getFolder(folderName);
if (folder == null)
{
return null;
}
String[] jobIDs = folder.getJobIDs();
ArrayList<Job> jobList = new ArrayList<Job>(jobIDs.length);
for (int i=0; i < jobIDs.length; i++)
{
try
{
Job job = getJob(jobIDs[i]);
if (job != null)
{
jobList.add(job);
}
} catch (Exception e) {}
}
Job[] jobs = new Job[jobList.size()];
jobList.toArray(jobs);
Arrays.sort(jobs);
return jobs;
}
/**
* Retrieves the set of completed jobs contained in the specified folder of
* the configuration database.
*
* @param folderName The name of the folder for which to retrieve the
* associated jobs.
*
* @return The set of completed jobs contained in the specified folder of the
* configuration database, or <CODE>null</CODE> if there is no such
* folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified folder.
*/
public Job[] getCompletedJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getFolder(folderName);
if (folder == null)
{
return null;
}
String[] jobIDs = folder.getJobIDs();
ArrayList<Job> jobList = new ArrayList<Job>(jobIDs.length);
for (int i=0; i < jobIDs.length; i++)
{
try
{
Job job = getJob(jobIDs[i]);
if (job.doneRunning())
{
jobList.add(job);
}
} catch (Exception e) {}
}
Job[] jobs = new Job[jobList.size()];
jobList.toArray(jobs);
Arrays.sort(jobs);
return jobs;
}
/**
* Retrieves the set of jobs contained in the specified virtual folder of the
* configuration database.
*
* @param folderName The name of the virtual folder for which to retrieve
* the associated jobs.
*
* @return The set of jobs contained in the specified virtual folder of the
* configuration database, or <CODE>null</CODE> if there is no such
* folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified virtual folder.
*/
public Job[] getVirtualJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getVirtualFolder(folderName);
if (folder == null)
{
return null;
}
String[] jobIDs = folder.getJobIDs();
ArrayList<Job> jobList = new ArrayList<Job>(jobIDs.length);
for (int i=0; i < jobIDs.length; i++)
{
try
{
Job job = getJob(jobIDs[i]);
if (job != null)
{
jobList.add(job);
}
} catch (Exception e) {}
}
Job[] jobs = new Job[jobList.size()];
jobList.toArray(jobs);
Arrays.sort(jobs);
return jobs;
}
/**
* Retrieves summary information for the set of jobs contained in the
* specified folder of the configuration database.
*
* @param folderName The name of the folder for which to retrieve the
* associated jobs.
*
* @return Summary information about the set of jobs contained in the
* specified folder of the configuration database, or
* <CODE>null</CODE> if there is no such folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified folder.
*/
public Job[] getSummaryJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getFolder(folderName);
if (folder == null)
{
return null;
}
String[] jobIDs = folder.getJobIDs();
ArrayList<Job> jobList = new ArrayList<Job>(jobIDs.length);
for (int i=0; i < jobIDs.length; i++)
{
try
{
Job job = getSummaryJob(jobIDs[i]);
if (job != null)
{
jobList.add(job);
}
} catch (Exception e) {}
}
Job[] jobs = new Job[jobList.size()];
jobList.toArray(jobs);
Arrays.sort(jobs);
return jobs;
}
/**
* Retrieves summary information for the set of completed jobs contained in
* the specified folder of the configuration database.
*
* @param folderName The name of the folder for which to retrieve the
* associated jobs.
*
* @return Summary information about the set of completed jobs contained in
* the specified folder of the configuration database, or
* <CODE>null</CODE> if there is no such folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified folder.
*/
public Job[] getCompletedSummaryJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getFolder(folderName);
if (folder == null)
{
return null;
}
String[] jobIDs = folder.getJobIDs();
ArrayList<Job> jobList = new ArrayList<Job>(jobIDs.length);
for (int i=0; i < jobIDs.length; i++)
{
try
{
Job job = getSummaryJob(jobIDs[i]);
if (job.doneRunning())
{
jobList.add(job);
}
} catch (Exception e) {}
}
Job[] jobs = new Job[jobList.size()];
jobList.toArray(jobs);
Arrays.sort(jobs);
return jobs;
}
/**
* Retrieves summary information for the set of jobs contained in the
* specified virtual folder of the configuration database.
*
* @param folderName The name of the virtual folder for which to retrieve
* the associated jobs.
*
* @return Summary information about the set of jobs contained in the
* specified virtual folder of the configuration database, or
* <CODE>null</CODE> if there is no such folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified virtual folder.
*/
public Job[] getSummaryVirtualJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getVirtualFolder(folderName);
if (folder == null)
{
return null;
}
String[] jobIDs = folder.getJobIDs();
ArrayList<Job> jobList = new ArrayList<Job>(jobIDs.length);
for (int i=0; i < jobIDs.length; i++)
{
try
{
jobList.add(getSummaryJob(jobIDs[i]));
} catch (Exception e) {}
}
Job[] jobs = new Job[jobList.size()];
jobList.toArray(jobs);
Arrays.sort(jobs);
return jobs;
}
/**
* Writes information about the provided job into the configuration database.
* If the specified job already exists, it will be overwritten. Otherwise, it
* will be added.
*
* @param job The job to write to the database.
*
* @throws DatabaseException If a problem occurs while attempting to write
* the job information.
*/
public void writeJob(Job job)
throws DatabaseException
{
// First, see if the job already exists in the configuration database.
Job j = null;
try
{
j = getJob(job.getJobID());
}
catch (DecodeException de)
{
// The job exists but could not be decoded for some reason. That's fine,
// since we're going to overwrite it anyway.
j = null;
}
// If the job exists, then just overwrite it. If not, then store it and
// update the folder in which the job is stored.
if (j != null)
{
put(null, jobDB, job.getJobID(), job.encode());
}
else
{
Transaction txn = getTransaction();
try
{
byte[] folderBytes = get(txn, folderDB, job.getFolderName(), true);
JobFolder folder = JobFolder.decode(folderBytes);
folder.addJobID(job.getJobID());
put(txn, folderDB, job.getFolderName(), folder.encode());
put(txn, jobDB, job.getJobID(), job.encode());
commitTransaction(txn);
}
catch (DatabaseException de)
{
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
abortTransaction(txn);
String message = "Unexpected exception caught while adding job " +
"data: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_ANY, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
// Check to see if the job is in the disabled, pending, or running job lists
// and update them as necessary.
boolean isDisabled = (job.getJobState() == Constants.JOB_STATE_DISABLED);
boolean isPending =
(job.getJobState() == Constants.JOB_STATE_NOT_YET_STARTED);
boolean isRunning = (job.getJobState() == Constants.JOB_STATE_RUNNING);
if (disabledJobs.contains(job.getJobID()))
{
if (! isDisabled)
{
synchronized (disabledJobs)
{
disabledJobs.remove(job.getJobID());
writeDisabledJobList();
}
}
}
else if (isDisabled)
{
synchronized (disabledJobs)
{
disabledJobs.add(job.getJobID());
writeDisabledJobList();
}
}
if (pendingJobs.contains(job.getJobID()))
{
if (! isPending)
{
synchronized (pendingJobs)
{
pendingJobs.remove(job.getJobID());
writePendingJobList();
}
}
}
else if (isPending)
{
synchronized (pendingJobs)
{
pendingJobs.add(job.getJobID());
writePendingJobList();
}
}
if (runningJobs.contains(job.getJobID()))
{
if (! isRunning)
{
synchronized (runningJobs)
{
runningJobs.remove(job.getJobID());
writeRunningJobList();
}
}
}
else if (isRunning)
{
synchronized (runningJobs)
{
runningJobs.add(job.getJobID());
writeRunningJobList();
}
}
}
/**
* Writes the list of disabled jobs to the configuration database.
*
* @throws DatabaseException If a problem occurs while attempting to write
* the disabled job list.
*/
private void writeDisabledJobList()
throws DatabaseException
{
StringBuilder buffer = new StringBuilder();
if (! disabledJobs.isEmpty())
{
Iterator iterator = disabledJobs.iterator();
buffer.append((String) iterator.next());
while (iterator.hasNext())
{
buffer.append('\n');
buffer.append((String) iterator.next());
}
}
byte[] disabledJobBytes = ASN1Element.getBytes(buffer.toString());
put(null, configDB, Constants.PARAM_DISABLED_JOBS, disabledJobBytes);
}
/**
* Retrieves an array containing the job IDs of the jobs that are currently
* disabled.
*
* @return An array containing the job IDs of the jobs that are currently
* disabled.
*/
public String[] getDisabledJobIDs()
{
synchronized (disabledJobs)
{
String[] disabledJobIDs = new String[disabledJobs.size()];
return disabledJobs.toArray(disabledJobIDs);
}
}
/**
* Retrieves an array containing the set of jobs that are currently disabled.
*
* @return An array containing the set of jobs that are currently disabled.
*
* @throws DatabaseException If a problem occurs while interacting with the
* database.
*/
public Job[] getDisabledJobs()
throws DatabaseException
{
synchronized (disabledJobs)
{
ArrayList<Job> disabledJobList = new ArrayList<Job>();
Iterator iterator = disabledJobs.iterator();
while (iterator.hasNext())
{
String jobID = (String) iterator.next();
byte[] jobBytes = get(null, jobDB, jobID, false);
if (jobBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find disabled job " + jobID);
continue;
}
try
{
disabledJobList.add(Job.decode(slamdServer, jobBytes));
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to decode job " + jobID + " -- " + e);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
}
}
Job[] disabledJobs = new Job[disabledJobList.size()];
disabledJobList.toArray(disabledJobs);
Arrays.sort(disabledJobs);
return disabledJobs;
}
}
/**
* Writes the list of pending jobs to the configuration database.
*
* @throws DatabaseException If a problem occurs while attempting to write
* the pending job list.
*/
private void writePendingJobList()
throws DatabaseException
{
StringBuilder buffer = new StringBuilder();
if (! pendingJobs.isEmpty())
{
Iterator<String> iterator = pendingJobs.iterator();
buffer.append(iterator.next());
while (iterator.hasNext())
{
buffer.append('\n');
buffer.append(iterator.next());
}
}
byte[] pendingJobBytes = ASN1Element.getBytes(buffer.toString());
put(null, configDB, Constants.PARAM_PENDING_JOBS, pendingJobBytes);
}
/**
* Retrieves an array containing the job IDs of the jobs that are currently
* pending execution.
*
* @return An array containing the job IDs of the jobs that are currently
* pending execution.
*/
public String[] getPendingJobIDs()
{
synchronized (pendingJobs)
{
String[] pendingJobIDs = new String[pendingJobs.size()];
return pendingJobs.toArray(pendingJobIDs);
}
}
/**
* Retrieves an array containing the set of jobs that are currently pending
* execution.
*
* @return An array containing the set of jobs that are currently pending
* execution.
*
* @throws DatabaseException If a problem occurs while interacting with the
* database.
*/
public Job[] getPendingJobs()
throws DatabaseException
{
synchronized (pendingJobs)
{
ArrayList<Job> pendingJobList = new ArrayList<Job>();
Iterator<String> iterator = pendingJobs.iterator();
while (iterator.hasNext())
{
String jobID = iterator.next();
byte[] jobBytes = get(null, jobDB, jobID, false);
if (jobBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find pending job " + jobID);
continue;
}
try
{
pendingJobList.add(Job.decode(slamdServer, jobBytes));
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to decode job " + jobID + " -- " + e);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
}
}
Job[] pendingJobs = new Job[pendingJobList.size()];
pendingJobList.toArray(pendingJobs);
Arrays.sort(pendingJobs);
return pendingJobs;
}
}
/**
* Writes the list of running jobs to the configuration database.
*
* @throws DatabaseException If a problem occurs while attempting to write
* the running job list.
*/
private void writeRunningJobList()
throws DatabaseException
{
StringBuilder buffer = new StringBuilder();
if (! runningJobs.isEmpty())
{
Iterator<String> iterator = runningJobs.iterator();
buffer.append(iterator.next());
while (iterator.hasNext())
{
buffer.append('\n');
buffer.append(iterator.next());
}
}
byte[] runningJobBytes = ASN1Element.getBytes(buffer.toString());
put(null, configDB, Constants.PARAM_RUNNING_JOBS, runningJobBytes);
}
/**
* Retrieves an array containing the job IDs of the jobs that are currently
* running.
*
* @return An array containing the job IDs of the jobs that are currently
* running.
*/
public String[] getRunningJobIDs()
{
synchronized (runningJobs)
{
String[] runningJobIDs = new String[runningJobs.size()];
return runningJobs.toArray(runningJobIDs);
}
}
/**
* Retrieves an array containing the set of jobs that are currently running.
*
* @return An array containing the set of jobs that are currently running.
*
* @throws DatabaseException If a problem occurs while interacting with the
* database.
*/
public Job[] getRunningJobs()
throws DatabaseException
{
synchronized (runningJobs)
{
ArrayList<Job> runningJobList = new ArrayList<Job>();
Iterator<String> iterator = runningJobs.iterator();
while (iterator.hasNext())
{
String jobID = iterator.next();
byte[] jobBytes = get(null, jobDB, jobID, false);
if (jobBytes == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Could not find running job " + jobID);
continue;
}
try
{
runningJobList.add(Job.decode(slamdServer, jobBytes));
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to decode job " + jobID + " -- " + e);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
}
}
Job[] runningJobs = new Job[runningJobList.size()];
runningJobList.toArray(runningJobs);
Arrays.sort(runningJobs);
return runningJobs;
}
}
/**
* Removes information about the specified job from the configuration
* database.
*
* @param jobID The job ID of the job to remove from the configuration
* database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void removeJob(String jobID)
throws DatabaseException
{
Job j = null;
try
{
j = getJob(jobID);
}
catch (DecodeException de)
{
// This means that the job exists but can't be decoded for some reason.
// In this case, just remove the job.
delete(null, jobDB, jobID);
return;
}
// At this point, we need to remove the job as well as the reference to the
// job in its corresponding folder. First, create a transaction to use to
// protect the entire operation.
Transaction txn = getTransaction();
try
{
byte[] folderBytes = get(txn, folderDB, j.getFolderName(), true);
JobFolder folder = JobFolder.decode(folderBytes);
folder.removeJobID(jobID);
put(txn, folderDB, j.getFolderName(), folder.encode());
delete(txn, jobDB, jobID);
commitTransaction(txn);
}
catch (DatabaseException de)
{
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
abortTransaction(txn);
String message = "Unexpected exception caught while removing job: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
/**
* Moves the specified job from its current folder to the new folder.
*
* @param jobID The job ID of the job to move.
* @param folderName The name of the new folder in which to place the job.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void moveJob(String jobID, String folderName)
throws DatabaseException
{
Transaction txn = getTransaction();
try
{
Job j = getJob(jobID);
byte[] currentFolderBytes = get(txn, folderDB, j.getFolderName(), true);
JobFolder currentFolder = JobFolder.decode(currentFolderBytes);
currentFolder.removeJobID(jobID);
put(txn, folderDB, j.getFolderName(), currentFolder.encode());
byte[] newFolderBytes = get(txn, folderDB, folderName, true);
JobFolder newFolder = JobFolder.decode(newFolderBytes);
newFolder.addJobID(jobID);
put(txn, folderDB, folderName, newFolder.encode());
j.setFolderName(folderName);
put(txn, jobDB, jobID, j.encode());
commitTransaction(txn);
}
catch (DatabaseException de)
{
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
abortTransaction(txn);
String message = "Unexpected exception caught while attempting to move " +
"job: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
/**
* Retrieves the specified optimizing job from the configuration database.
*
* @param optimizingJobID The ID of the optimizing job to retrieve from the
* database.
*
* @return The requested optimizing job from the configuration database, or
* <CODE>null</CODE> if no such job exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while decoding the optimizing
* job information.
*/
public OptimizingJob getOptimizingJob(String optimizingJobID)
throws DatabaseException, DecodeException
{
byte[] jobBytes = get(null, optimizingJobDB, optimizingJobID, false);
if (jobBytes == null)
{
return null;
}
return OptimizingJob.decode(slamdServer, jobBytes);
}
/**
* Retrieves summary information for the specified optimizing job from the
* configuration database.
*
* @param optimizingJobID The ID of the optimizing job to retrieve from the
* database.
*
* @return Summary information for the requested optimizing job from the
* configuration database, or <CODE>null</CODE> if no such job
* exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while decoding the optimizing
* job information.
*/
public OptimizingJob getSummaryOptimizingJob(String optimizingJobID)
throws DatabaseException, DecodeException
{
byte[] jobBytes = get(null, optimizingJobDB, optimizingJobID, false);
if (jobBytes == null)
{
return null;
}
return OptimizingJob.decodeSummary(slamdServer, jobBytes);
}
/**
* Retrieves the set of optimizing jobs contained in the specified folder of
* the configuration database.
*
* @param folderName The name of the folder for which to retrieve the
* associated optimizing jobs.
*
* @return The set of optimizing jobs contained in the specified folder of
* the configuration database, or <CODE>null</CODE> if there is no
* such folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified folder.
*/
public OptimizingJob[] getOptimizingJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getFolder(folderName);
if (folder == null)
{
return null;
}
String[] optimizingJobIDs = folder.getOptimizingJobIDs();
ArrayList<OptimizingJob> optimizingJobList =
new ArrayList<OptimizingJob>(optimizingJobIDs.length);
for (int i=0; i < optimizingJobIDs.length; i++)
{
try
{
OptimizingJob oj = getOptimizingJob(optimizingJobIDs[i]);
if (oj != null)
{
optimizingJobList.add(oj);
}
} catch (Exception e) {}
}
OptimizingJob[] optimizingJobs =
new OptimizingJob[optimizingJobList.size()];
optimizingJobList.toArray(optimizingJobs);
Arrays.sort(optimizingJobs);
return optimizingJobs;
}
/**
* Retrieves summary information for the set of optimizing jobs contained in
* the specified folder of the configuration database.
*
* @param folderName The name of the folder for which to retrieve the
* associated optimizing jobs.
*
* @return Summary information for the set of optimizing jobs contained in
* the specified folder of the configuration database, or
* <CODE>null</CODE> if there is no such folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while attempting to decode
* the specified folder.
*/
public OptimizingJob[] getSummaryOptimizingJobs(String folderName)
throws DatabaseException, DecodeException
{
JobFolder folder = getFolder(folderName);
if (folder == null)
{
return null;
}
String[] optimizingJobIDs = folder.getOptimizingJobIDs();
ArrayList<OptimizingJob> optimizingJobList =
new ArrayList<OptimizingJob>(optimizingJobIDs.length);
for (int i=0; i < optimizingJobIDs.length; i++)
{
try
{
OptimizingJob oj = getSummaryOptimizingJob(optimizingJobIDs[i]);
if (oj != null)
{
optimizingJobList.add(oj);
}
} catch (Exception e) {}
}
OptimizingJob[] optimizingJobs =
new OptimizingJob[optimizingJobList.size()];
optimizingJobList.toArray(optimizingJobs);
Arrays.sort(optimizingJobs);
return optimizingJobs;
}
/**
* Writes information about the provided optimizing job into the configuration
* database. If the specified optimizing job already exists, it will be
* overwritten. Otherwise, it will be added.
*
* @param optimizingJob The optimizing job to write to the database.
*
* @throws DatabaseException If a problem occurs while attempting to write
* the optimizing job information.
*/
public void writeOptimizingJob(OptimizingJob optimizingJob)
throws DatabaseException
{
// First, see if the optimizing job already exists in the configuration
// database.
OptimizingJob oj = null;
try
{
oj = getOptimizingJob(optimizingJob.getOptimizingJobID());
}
catch (DecodeException de)
{
// The job exists but could not be decoded for some reason. That's fine,
// since we're going to overwrite it anyway.
oj = null;
}
// If the job exists, then just overwrite it. If not, then store it and
// update the folder in which the job is stored.
if (oj != null)
{
put(null, optimizingJobDB, optimizingJob.getOptimizingJobID(),
optimizingJob.encode());
}
else
{
Transaction txn = getTransaction();
try
{
byte[] folderBytes = get(txn, folderDB, optimizingJob.getFolderName(),
true);
JobFolder folder = JobFolder.decode(folderBytes);
folder.addOptimizingJobID(optimizingJob.getOptimizingJobID());
put(txn, folderDB, optimizingJob.getFolderName(), folder.encode());
put(txn, optimizingJobDB, optimizingJob.getOptimizingJobID(),
optimizingJob.encode());
commitTransaction(txn);
}
catch (DatabaseException de)
{
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
abortTransaction(txn);
String message = "Unexpected exception caught while adding " +
"optimizing job data: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_ANY, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
}
/**
* Removes information about the specified optimizing job from the
* configuration database.
*
* @param optimizingJobID The ID of the optimizing job to remove from the
* configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void removeOptimizingJob(String optimizingJobID)
throws DatabaseException
{
OptimizingJob oj = null;
try
{
oj = getOptimizingJob(optimizingJobID);
}
catch (DecodeException de)
{
// This means that the job exists but can't be decoded for some reason.
// In this case, just remove the job.
delete(null, optimizingJobDB, optimizingJobID);
return;
}
// At this point, we need to remove the job as well as the reference to the
// job in its corresponding folder. First, create a transaction to use to
// protect the entire operation.
Transaction txn = getTransaction();
try
{
byte[] folderBytes = get(txn, folderDB, oj.getFolderName(), true);
JobFolder folder = JobFolder.decode(folderBytes);
folder.removeOptimizingJobID(optimizingJobID);
put(txn, folderDB, oj.getFolderName(), folder.encode());
delete(txn, optimizingJobDB, optimizingJobID);
commitTransaction(txn);
}
catch (DatabaseException de)
{
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
abortTransaction(txn);
String message = "Unexpected exception caught while removing " +
"optimizing job: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
/**
* Moves the specified optimizing job from its current folder to the new
* folder.
*
* @param optimizingJobID The job ID of the job to move.
* @param folderName The name of the new folder in which to place the
* optimizing job.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void moveOptimizingJob(String optimizingJobID, String folderName)
throws DatabaseException
{
Transaction txn = getTransaction();
try
{
OptimizingJob oj = getOptimizingJob(optimizingJobID);
byte[] currentFolderBytes = get(txn, folderDB, oj.getFolderName(), true);
JobFolder currentFolder = JobFolder.decode(currentFolderBytes);
currentFolder.removeOptimizingJobID(optimizingJobID);
put(txn, folderDB, oj.getFolderName(), currentFolder.encode());
byte[] newFolderBytes = get(txn, folderDB, folderName, true);
JobFolder newFolder = JobFolder.decode(newFolderBytes);
newFolder.addOptimizingJobID(optimizingJobID);
put(txn, folderDB, folderName, newFolder.encode());
oj.setFolderName(folderName);
put(txn, optimizingJobDB, optimizingJobID, oj.encode());
commitTransaction(txn);
}
catch (DatabaseException de)
{
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
abortTransaction(txn);
String message = "Unexpected exception caught while attempting to move " +
"optimizing job: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
/**
* Retrieves a list of all job groups defined in the configuration database.
*
* @return A list of all job groups defined in the configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public JobGroup[] getJobGroups()
throws DatabaseException
{
synchronized (dbMutex)
{
ArrayList<JobGroup> jobGroupList = new ArrayList<JobGroup>();
CursorConfig cursorConfig = new CursorConfig();
cursorConfig.setReadUncommitted(false);
Cursor cursor = jobGroupDB.openCursor(null, cursorConfig);
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry value = new DatabaseEntry();
OperationStatus status = cursor.getFirst(key, value, LockMode.DEFAULT);
while (status == OperationStatus.SUCCESS)
{
try
{
jobGroupList.add(JobGroup.decode(slamdServer, value.getData()));
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to decode job the job group with " +
"name " + (new String(key.getData())) + ": " +
e.getMessage());
}
status = cursor.getNext(key, value, LockMode.DEFAULT);
}
cursor.close();
JobGroup[] jobGroups = new JobGroup[jobGroupList.size()];
jobGroupList.toArray(jobGroups);
return jobGroups;
}
}
/**
* Retrieves a list of all job groups defined in the configuration database.
* The returned list will only contain summary information for each group.
*
* @return A list of all job groups defined in the configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public JobGroup[] getSummaryJobGroups()
throws DatabaseException
{
synchronized (dbMutex)
{
ArrayList<JobGroup> jobGroupList = new ArrayList<JobGroup>();
CursorConfig cursorConfig = new CursorConfig();
cursorConfig.setReadUncommitted(false);
Cursor cursor = jobGroupDB.openCursor(null, cursorConfig);
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry value = new DatabaseEntry();
OperationStatus status = cursor.getFirst(key, value, LockMode.DEFAULT);
while (status == OperationStatus.SUCCESS)
{
try
{
jobGroupList.add(JobGroup.decodeSummary(value.getData()));
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to decode job the job group with " +
"name " + (new String(key.getData())) + ": " +
e.getMessage());
}
status = cursor.getNext(key, value, LockMode.DEFAULT);
}
cursor.close();
JobGroup[] jobGroups = new JobGroup[jobGroupList.size()];
jobGroupList.toArray(jobGroups);
return jobGroups;
}
}
/**
* Retrieves the requested job group from the configuration database.
*
* @param jobGroupName The name of the job group to retrieve.
*
* @return The requested job group, or <CODE>null</CODE> if no such job group
* exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If a problem occurs while trying to decode the
* requested job group information.
*/
public JobGroup getJobGroup(String jobGroupName)
throws DatabaseException, DecodeException
{
byte[] jobGroupBytes = get(null, jobGroupDB, jobGroupName, false);
if (jobGroupBytes == null)
{
return null;
}
return JobGroup.decode(slamdServer, jobGroupBytes);
}
/**
* Writes information about the provided job group to the configuration
* database. If the specified job group already exists, it will be
* overwritten. Otherwise, a new record will be created.
*
* @param jobGroup The job group to be written to the configuration
* database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void writeJobGroup(JobGroup jobGroup)
throws DatabaseException
{
put(null, jobGroupDB, jobGroup.getName(), jobGroup.encode());
}
/**
* Removes information about the specified job group from the configuration
* database. If it does not exist, then no action will be taken.
*
* @param jobGroupName The name of the job group to remove from the
* configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void removeJobGroup(String jobGroupName)
throws DatabaseException
{
delete(null, jobGroupDB, jobGroupName);
}
/**
* Retrieves the specified uploaded file from the configuration database.
*
* @param folderName The name of the folder in which the specified file is
* located.
* @param fileName The name of the uploaded file to retrieve.
*
* @return The requested uploaded file, or <CODE>null</CODE> if no such file
* exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If the uploaded file cannot be decoded for some
* reason.
*/
public UploadedFile getUploadedFile(String folderName, String fileName)
throws DatabaseException, DecodeException
{
String key = folderName + '\t' + fileName;
byte[] fileBytes = get(null, fileDB, key, false);
if (fileBytes == null)
{
return null;
}
return UploadedFile.decode(fileBytes);
}
/**
* Retrieves the specified uploaded file from the configuration database. The
* file returned will not include the actual file data.
*
* @param folderName The name of the folder in which the specified file is
* located.
* @param fileName The name of the uploaded file to retrieve.
*
* @return The requested uploaded file, or <CODE>null</CODE> if no such file
* exists.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*
* @throws DecodeException If the uploaded file cannot be decoded for some
* reason.
*/
public UploadedFile getUploadedFileWithoutData(String folderName,
String fileName)
throws DatabaseException, DecodeException
{
String key = folderName + '\t' + fileName;
byte[] fileBytes = get(null, fileDB, key, false);
if (fileBytes == null)
{
return null;
}
return UploadedFile.decodeWithoutData(fileBytes);
}
/**
* Retrieves the set of uploaded files associated with the specified folder.
* The files returned will not include the actual file data.
*
* @param folderName The name of the folder for which to retrieve the
* uploaded files.
*
* @return The set of uploaded files associated with the specified folder, or
* <CODE>null</CODE> if there is no such folder.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public UploadedFile[] getUploadedFiles(String folderName)
throws DatabaseException
{
JobFolder folder = null;
try
{
folder = getFolder(folderName);
}
catch (DecodeException de)
{
String message = "Unable to decode job folder: " + de;
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(de));
throw new DatabaseException(message, de);
}
if (folder == null)
{
return null;
}
String[] fileNames = folder.getFileNames();
ArrayList<UploadedFile> fileList = new ArrayList<UploadedFile>();
for (int i=0; i < fileNames.length; i++)
{
try
{
fileList.add(getUploadedFileWithoutData(folderName, fileNames[i]));
} catch (Exception e) {}
}
UploadedFile[] uploadedFiles = new UploadedFile[fileList.size()];
fileList.toArray(uploadedFiles);
return uploadedFiles;
}
/**
* Writes information about the provided uploaded file into the configuration
* database. If the file already exists, then it will be overwritten.
* Otherwise, it will be added and the corresponding folder will be updated.
*
* @param uploadedFile The uploaded file to be written to the configuration
* database.
* @param folderName The name of the folder in which the specified file
* should be placed.
*
* @throws DatabaseException If a problem occurs while writing information
* about the specified file to the configuration
* database.
*/
public void writeUploadedFile(UploadedFile uploadedFile, String folderName)
throws DatabaseException
{
String key = folderName + '\t' + uploadedFile.getFileName();
UploadedFile currentFile;
try
{
currentFile = getUploadedFileWithoutData(folderName,
uploadedFile.getFileName());
}
catch (DecodeException de)
{
// This means that the file exists but cannot be decoded for some reason.
// In this case, just overwrite that file.
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(de));
put(null, fileDB, key, uploadedFile.encode());
return;
}
if (currentFile == null)
{
// The file does not exist, so add it and also update the corresponding
// folder. Use a transaction to wrap all these operations.
Transaction txn = getTransaction();
try
{
byte[] folderBytes = get(txn, folderDB, folderName, true);
JobFolder folder = JobFolder.decode(folderBytes);
folder.addFileName(uploadedFile.getFileName());
put(txn, folderDB, folderName, folder.encode());
put(txn, fileDB, key, uploadedFile.encode());
commitTransaction(txn);
}
catch (DatabaseException de)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(de));
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
abortTransaction(txn);
String message = "Unexpected exception caught while adding uploaded " +
"file to DB: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
else
{
// The file already exists, so just update it in the file database.
put(null, fileDB, key, uploadedFile.encode());
}
}
/**
* Removes information about the specified uploaded file from the
* configuration database.
*
* @param folderName The name of the folder from which to remove the file.
* @param fileName The name of the uploaded file to remove.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void removeUploadedFile(String folderName, String fileName)
throws DatabaseException
{
// We need to both remove the file and update the associated folder. Do
// this under a transaction.
Transaction txn = getTransaction();
try
{
byte[] folderBytes = get(txn, folderDB, folderName, true);
JobFolder folder = JobFolder.decode(folderBytes);
folder.removeFileName(fileName);
put(txn, folderDB, folderName, folder.encode());
delete(txn, fileDB, folderName + '\t' + fileName);
commitTransaction(txn);
}
catch (DatabaseException de)
{
abortTransaction(txn);
throw de;
}
catch (Exception e)
{
abortTransaction(txn);
String message = "Unexpected exception caught while removing uploaded " +
"file: " + e;
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
throw new DatabaseException(message, e);
}
}
/**
* Retrieves the set of job classes defined in the configuration database.
*
* @return The set of job classes defined in the configuration database.
*
* @throws DatabaseException If a problem occurs while obtaining the set of
* job classes.
*/
public JobClass[] getJobClasses()
throws DatabaseException
{
boolean useCustomClassLoader =
getConfigParameter(Constants.PARAM_USE_CUSTOM_CLASS_LOADER, false);
JobClassLoader classLoader = null;
if (useCustomClassLoader)
{
classLoader = new JobClassLoader(getClass().getClassLoader(),
slamdServer.getClassPath());
}
String classListStr = getConfigParameter(Constants.PARAM_JOB_CLASSES, "");
StringTokenizer tokenizer = new StringTokenizer(classListStr, "\n");
ArrayList<JobClass> jobClassList = new ArrayList<JobClass>();
while (tokenizer.hasMoreTokens())
{
String className = tokenizer.nextToken();
JobClass jobClass;
if (useCustomClassLoader)
{
try
{
jobClassList.add(classLoader.getJobClass(className));
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to load job class " + className +
": " + e);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
}
}
else
{
try
{
jobClass = (JobClass) Constants.classForName(className).newInstance();
jobClassList.add(jobClass);
}
catch (Exception e)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG,
"Unable to load job class " + className +
": " + e);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(e));
}
}
}
JobClass[] jobClasses = new JobClass[jobClassList.size()];
jobClassList.toArray(jobClasses);
return jobClasses;
}
/**
* Adds information about the specified job class to the configuration
* database.
*
* @param className The name of the job class to add to the configuration
* database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void addJobClass(String className)
throws DatabaseException
{
String classListStr = getConfigParameter(Constants.PARAM_JOB_CLASSES, "");
putConfigParameter(Constants.PARAM_JOB_CLASSES,
classListStr + '\n' + className, false);
}
/**
* Removes information about the specified job class from the configuration
* database.
*
* @param className The job class to remove from the configuration database.
*
* @throws DatabaseException If a problem occurs while interacting with the
* configuration database.
*/
public void removeJobClass(String className)
throws DatabaseException
{
String classListStr = getConfigParameter(Constants.PARAM_JOB_CLASSES, "");
StringTokenizer tokenizer = new StringTokenizer(classListStr, "\n");
StringBuilder classNameBuffer = new StringBuilder();
while (tokenizer.hasMoreTokens())
{
String jobClassName = tokenizer.nextToken();
if (! jobClassName.equals(className))
{
if (classNameBuffer.length() > 0)
{
classNameBuffer.append('\n');
}
classNameBuffer.append(className);
}
}
putConfigParameter(Constants.PARAM_JOB_CLASSES, classNameBuffer.toString(),
false);
}
/**
* Retrieves the set of config subscribers that have been registered with the
* configuration database.
*
* @return The set of config subscribers that have been registered with the
* configuration database.
*/
public ConfigSubscriber[] getConfigSubscribers()
{
synchronized (configSubscribers)
{
ConfigSubscriber[] subscribers =
new ConfigSubscriber[configSubscribers.size()];
configSubscribers.toArray(subscribers);
return subscribers;
}
}
/**
* Retrieves a "safe" name for the provided configuration subscriber, which is
* suitable for submission in an HTML form.
*
* @param subscriber The configuration subscriber for which to retrieve the
* safe name.
*
* @return The safe name for the provided configuration subscriber.
*/
public static String getSafeName(ConfigSubscriber subscriber)
{
return subscriber.getSubscriberName().toLowerCase().replace(' ', '_');
}
/**
* Retrieves the configuration subscriber that corresponds to the provided
* "safe" name. The safe name will be generated by converting the subscriber
* name to all lowercase characters and replacing any spaces with underscores.
*
* @param name The safe name for which to retrieve the corresponding config
* subscriber.
*
* @return The config subscriber for the provided safe name, or
* <CODE>null</CODE> if there is no such subscriber.
*/
public ConfigSubscriber subscriberForSafeName(String name)
{
synchronized (configSubscribers)
{
Iterator<ConfigSubscriber> iterator = configSubscribers.iterator();
while (iterator.hasNext())
{
ConfigSubscriber subscriber = iterator.next();
if (subscriber.getSubscriberName().toLowerCase().replace(' ', '_').
equals(name))
{
return subscriber;
}
}
}
return null;
}
/**
* Registers the provided configuration subscriber to be notified of changes
* to configuration parameters in the database.
*
* @param subscriber The configuration subscriber with which to register.
*/
public void registerAsSubscriber(ConfigSubscriber subscriber)
{
synchronized (configSubscribers)
{
for (int i=0; i < configSubscribers.size(); i++)
{
ConfigSubscriber s = configSubscribers.get(i);
if (s.getSubscriberName().equals(subscriber.getSubscriberName()))
{
return;
}
}
configSubscribers.add(subscriber);
}
}
/**
* Creates a new transaction that may be used to protect operations impacting
* multiple databases.
*
* @return The transaction that has been created.
*
* @throws DatabaseException If a problem occurs while trying to create the
* transaction.
*/
private Transaction getTransaction()
throws DatabaseException
{
synchronized (dbMutex)
{
// Make sure that the environment is open before trying to create the
// transaction.
if (! environmentOpen)
{
String message = "Cannot create a transaction when the database " +
"environment is not open.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
// Specify the configuration to use for the transaction.
TransactionConfig txnConfig = new TransactionConfig();
txnConfig.setReadUncommitted(false);
txnConfig.setNoWait(false);
// Try to create the transaction.
Transaction txn = dbEnv.beginTransaction(null, txnConfig);
activeTransactions.add(txn);
return txn;
}
}
/**
* Commits the provided transaction.
*
* @param txn The transaction to commit.
*
* @throws DatabaseException If a problem occurs while trying to commit the
* transaction.
*/
private void commitTransaction(Transaction txn)
throws DatabaseException
{
synchronized (dbMutex)
{
// Make sure that the environment is open before trying to perform the
// commit.
if (! environmentOpen)
{
String message = "Cannot commit a transaction when the database " +
"environment is not open.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
// Commit the transaction.
txn.commit();
// Remove the transaction from the list of active transactions.
activeTransactions.remove(txn);
}
}
/**
* Aborts the provided transaction.
*
* @param txn The transaction to abort.
*
* @throws DatabaseException If a problem occurs while trying to abort the
* transaction.
*/
private void abortTransaction(Transaction txn)
throws DatabaseException
{
synchronized (dbMutex)
{
// Make sure that the environment is open before trying to abort the
// transaction.
if (! environmentOpen)
{
String message = "Cannot abort a transaction when the database " +
"environment is not open.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
// No matter what, we will remove the transaction from the list of active
// transactions.
activeTransactions.remove(txn);
// Abort the transaction.
txn.abort();
}
}
/**
* Retrieves a byte array containing the contents of the record with the
* provided key from the specified database.
*
* @param txn The transaction to use to protect the read. This may be
* <CODE>null</CODE> if no transaction is needed.
* @param db The database in which to perform the get.
* @param key The key for the record to retrieve.
* @param writeLock Indicates whether to acquire a write lock on the
* specified key.
*
* @return A byte array containing the contents of the record with the
* provided key from the specified database, or <CODE>null</CODE> if
* the specified key does not exist.
*
* @throws DatabaseException If a problem occurs while trying to perform
* the get.
*/
private byte[] get(Transaction txn, Database db, String key,
boolean writeLock)
throws DatabaseException
{
synchronized (dbMutex)
{
// Make sure that the databases are open before trying to perform the get.
if (! dbsOpen)
{
String message = "Cannot perform the get because the databases are " +
"not open.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
// Perform the get and return the associated data.
DatabaseEntry keyEntry = new DatabaseEntry(ASN1Element.getBytes(key));
DatabaseEntry dataEntry = new DatabaseEntry();
LockMode lockMode = (writeLock ? LockMode.RMW : LockMode.DEFAULT);
if (db.get(txn, keyEntry, dataEntry, lockMode) == OperationStatus.SUCCESS)
{
return dataEntry.getData();
}
else
{
return null;
}
}
}
/**
* Updates the specified database with the provided information. If a record
* exists with the specified key, then it will be overwritten. Otherwise, a
* new record will be created.
*
* @param txn The transaction to use to protect the update. This may be
* <CODE>null</CODE> if no transaction is needed.
* @param db The database into which to perform the update.
* @param key The key to use for the provided data.
* @param data The byte array containing the data to use for the record.
*
* @return An <CODE>OperationStatus</CODE> instance providing information
* about the status of the update.
*
* @throws DatabaseException If a problem occurs while trying to update the
* database.
*/
private OperationStatus put(Transaction txn, Database db, String key,
byte[] data)
throws DatabaseException
{
synchronized (dbMutex)
{
// Make sure that the databases are open before trying to perform the put.
if (! dbsOpen)
{
String message = "Cannot perform the put because the databases are " +
"not open.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
// Perform the put and return the status.
DatabaseEntry keyEntry = new DatabaseEntry(ASN1Element.getBytes(key));
DatabaseEntry dataEntry = new DatabaseEntry(data);
return db.put(txn, keyEntry, dataEntry);
}
}
/**
* Updates the provided database with the given information. If a record
* exists with the specified key, then it will be overwritten. Otherwise, a
* new record will be created. Note that this should only be used in a
* single-threaded mode when this update will be the only interaction with
* the database at that time.
*
* @param db The database into which to perform the update.
* @param key The key to use for the provided data.
* @param data The data to use for the record.
*
* @return An <CODE>OperationStatus</CODE> instance providing information
* about the status of the update.
*
* @throws DatabaseException If a problem occurs while trying to update the
* database.
*/
private static OperationStatus put(Database db, String key, String data)
throws DatabaseException
{
try
{
DatabaseEntry keyEntry = new DatabaseEntry(ASN1Element.getBytes(key));
DatabaseEntry dataEntry = new DatabaseEntry(ASN1Element.getBytes(data));
return db.put(null, keyEntry, dataEntry);
}
catch (DatabaseException de)
{
throw de;
}
catch (Exception e)
{
throw new DatabaseException("Unable to update the database: " + e, e);
}
}
/**
* Updates the provided database with the given information. If a record
* exists with the specified key, then it will be overwritten. Otherwise, a
* new record will be created. Note that this should only be used in a
* single-threaded mode when this update will be the only interaction with
* the database at that time.
*
* @param db The database into which to perform the update.
* @param key The key to use for the provided data.
* @param data The data to use for the record.
*
* @return An <CODE>OperationStatus</CODE> instance providing information
* about the status of the update.
*
* @throws DatabaseException If a problem occurs while trying to update the
* database.
*/
private static OperationStatus put(Database db, String key, byte[] data)
throws DatabaseException
{
try
{
DatabaseEntry keyEntry = new DatabaseEntry(ASN1Element.getBytes(key));
DatabaseEntry dataEntry = new DatabaseEntry(data);
return db.put(null, keyEntry, dataEntry);
}
catch (DatabaseException de)
{
throw de;
}
catch (Exception e)
{
throw new DatabaseException("Unable to update the database: " + e, e);
}
}
/**
* Removes the specified key from the given database.
*
* @param txn The transaction to use to protect the delete.
* @param db The database from which to remove the specified key.
* @param key The key to remove from the database.
*
* @return An <CODE>OperationStatus</CODE> instance providing information
* about the status of the delete.
*
* @throws DatabaseException If a problem occurs while interacting with the
* database.
*/
private OperationStatus delete(Transaction txn, Database db, String key)
throws DatabaseException
{
synchronized (dbMutex)
{
// Make sure that the databases are open before trying to perform the
// delete.
if (! dbsOpen)
{
String message = "Cannot perform the delete because the databases " +
"are not open.";
slamdServer.logMessage(Constants.LOG_LEVEL_CONFIG, message);
throw new DatabaseException(message);
}
// Perform the put and return the status.
DatabaseEntry keyEntry = new DatabaseEntry(ASN1Element.getBytes(key));
return db.delete(txn, keyEntry);
}
}
}