package edu.hawaii.ics.yucheng;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* An immutable class that contains the configuration information of a node.
*
* @author Cheng Jade
* @assignment ICS 421 Assignment 2-2
* @date Feb 29, 2010
* @bugs None
*/
public final class ConfigurationNode {
/** The driver name used for the database connection. */
public final String driver;
/** The host name URI for the database server. */
public final String hostname;
/** A name used for identifying the thread responsible for this instance. */
public final String name;
/** The password for the database connection. */
public final String password;
/** The user name for the database connection. */
public final String username;
/**
* Initializes a new instance of the ConfigurationNode class.
*
* @param path
* The path to the properties file.
*
* @param name
* The name used for identifying the thread responsible for this
* instance.
*
* @throws NullPointerException
* @throws ProgramException
* Thrown if the reading properties goes wrong.
*/
public ConfigurationNode(
final String path,
final String name) throws ProgramException {
this(getProperties(path), name);
}
/**
* Initializes a new instance of the ConfigurationNode class.
*
* @param properties
* A properties object that the configuration node will be loaded
* from.
* @param name
* The name used for identifying the thread responsible for this
* instance.
*
* @throws NullPointerException
* @throws ProgramException
* Thrown if the reading properties goes wrong.
*/
public ConfigurationNode(
final Properties properties,
final String name) throws ProgramException {
// Read the corresponding lines from the properties object, and use the
// values as the parameters to call the other constructor, which assigns
// the class fields to those values.
this(
name,
read(properties, name, "driver"),
read(properties, name, "hostname"),
read(properties, name, "username"),
read(properties, name, "passwd"));
}
/**
* Initializes a new instance of the ConfigurationNode class.
*
* @param name
* The name used for identifying the thread responsible for this
* instance.
* @param driver
* The driver name used for the database connection.
* @param hostname
* The host name URI for the database server.
* @param username
* The user name for the database connection.
* @param password
* The password for the database connection.
*
* @throws NullPointerException
* @throws ProgramException
* Thrown if any parameter is null.
*/
public ConfigurationNode(
final String name,
final String driver,
final String hostname,
final String username,
final String password) throws ProgramException {
// Check if any parameter is null, and throw exception in that case.
if (null == name)
throw new NullPointerException("name");
if (null == driver)
throw new NullPointerException("driver");
if (null == hostname)
throw new NullPointerException("hostname");
if (null == username)
throw new NullPointerException("username");
if (null == password)
throw new NullPointerException("password");
// Assign the parameter values to the class fields.
this.name = name;
this.driver = driver;
this.hostname = hostname;
this.username = username;
this.password = password;
}
/**
* Logs a message to a specified output stream.
*
* @param stream
* The output stream, e.g. System.out.
*
* @param message
* The message to log.
*/
public void log(final PrintStream stream, final String message) {
assert null != stream;
stream.println("[" + this.hostname + "]: " + message);
}
/**
* Creates a new statement and executes it through the specified statement
* runner.
*
* @param runner
* The statement runner.
*
* @throws NullPointerException
* @throws ProgramException
* Thrown if there is an error executing the statement.
*/
public void runStatement(final StatementRunner runner) throws ProgramException {
if (null == runner)
throw new NullPointerException("runner");
// Open the connection.
try {
final Connection connection = this.getConnection();
// Create the statement object.
try {
final Statement statement = connection.createStatement();
try {
// Execute the statement.
runner.run(statement);
} finally {
// Always close the statement.
statement.close();
}
} finally {
// Always close the connection.
connection.close();
}
} catch (final SQLException e) {
// Wrap and re-throw SQL exceptions.
throw new ProgramException(e);
}
}
/**
* Returns a readable version of the contents of the Configuration Node.
*
* @return A readable version of the contents of the Configuration Node.
*/
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(" Driver: ");
builder.append(this.driver);
builder.append("\n Hostname: ");
builder.append(this.hostname);
builder.append("\n Username: ");
builder.append(this.username);
builder.append("\n Password: ");
builder.append(this.password);
return builder.toString();
}
/**
* Open a file, check for errors, and return a loaded properties object.
*
* @param path
* The path to the properties file.
*
* @return A loaded properties object.
*
* @throws ProgramException
* Thrown if there is a problem reading the configuration file.
*/
private static Properties getProperties(final String path)
throws ProgramException {
assert null != path;
// Open the file for reading.
try {
final FileInputStream file = new FileInputStream(path);
// Read and return the properties.
try {
final Properties properties = new Properties();
properties.load(file);
return properties;
} finally {
// Always close the file before returning.
file.close();
}
} catch (final IOException e) {
// Wrap and re-throw IO exceptions.
throw new ProgramException("Cannot read configuration file.", e);
}
}
/**
* Checks the DBMS driver is available.
*
* @throws ProgramException
* Thrown if the DBMS driver is not available.
*/
private void checkDriver() throws ProgramException {
// Create an instance of the driver if possible.
try {
Class.forName(this.driver).newInstance();
return;
} catch (final InstantiationException e) {
} catch (final IllegalAccessException e) {
} catch (final ClassNotFoundException e) {
}
// In any other case, throw an exception indicating the failure.
throw new ProgramException("Cannot load driver '" + this.driver + "'.");
}
/**
* Gets a connection to the DBMS database.
*
* @throws ProgramException
* Thrown if the connection cannot be made.
*/
private Connection getConnection() throws ProgramException {
// First, check the driver is available.
this.checkDriver();
// Next, create and return a new connection based on the instance data.
try {
return DriverManager.getConnection(
this.hostname,
this.username,
this.password);
} catch (final SQLException e) {
// Wrap and re-throw SQL exceptions.
final String message = "Cannot connect to '" + this.hostname + "'.";
throw new ProgramException(message, e);
}
}
/**
* Creates a key and looks up a value in the properties object. The value is
* returned if it is found; otherwise, an exception is thrown.
*
* @return The key word to search for in the properties file.
*
* @throws NullPointerException
* @throws ProgramException
* Thrown if the key is not found in the properties object.
*/
private static String read(
final Properties properties,
final String prefix,
final String name) throws ProgramException {
assert null != name;
// Check if the properties and prefix are not nulls.
if (null == properties)
throw new NullPointerException("properties");
if (null == prefix)
throw new NullPointerException("prefix");
// Create the key to search for according to the prefix and name.
final String key = prefix + "." + name;
// Search for the key and return the corresponding value.
final String value = properties.getProperty(key);
if (null == value)
throw new ProgramException(
"Property '" + key + "' not found in configuration file.");
return value;
}
/**
* The entry point for a test for this class.
*
* @param args
* The command line arguments (not used).
*/
public static void main(final String[] args) {
// Declare, initialize, and print a ConfigurationNode object.
try {
System.out.println(new ConfigurationNode("cfgtest.txt", "catalog"));
} catch (final ProgramException e) {
System.err.println(e.getMessage());
System.exit(1);
return;
}
// Exit cleanly while debugging from Eclipse.
System.exit(0);
}
}