/* * 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.scripting.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import com.slamd.common.Constants; import com.slamd.scripting.engine.Argument; import com.slamd.scripting.engine.Method; import com.slamd.scripting.engine.ScriptException; import com.slamd.scripting.engine.Variable; import com.slamd.scripting.general.BooleanVariable; import com.slamd.scripting.general.IntegerVariable; import com.slamd.scripting.general.StringVariable; /** * This class defines a variable that provides an interface to the JDBC * <CODE>Connection</CODE> object that can be used to interact with relational * databases. A JDBC connection variable provides the following methods: * * <UL> * <LI>commit() -- Attempts to perform a commit in the underlying database, * provided that autocommit is disabled. * This method returns a Boolean value that indicates whether the commit * was successful.</LI> * <LI>connect(string jdbcURL) -- Attempts to establish a connection to the * database using the provided URL. This method returns a Boolean value * that indicates whether the connection was established * successfully.</LI> * <LI>connect(string jdbcURL, string username, string password) -- Attempts * to establish a connection to the database using the provided * information. This method returns a Boolean value that indicates * whether the connection was established successfully.</LI> * <LI>disconnect() -- Closes the connection to the database. This method * does not return a value.</LI> * <LI>executeQuery(string sql) -- Executes the query specified in the * provided SQL and returns a result set containing the results of the * query.</LI> * <LI>executeUpdate(string sql) -- Executes the update specified in the * provided SQL and returns an integer value that contains the row count * for the update.</LI> * <LI>getAutoCommit() -- Retrieves a Boolean value that indicates whether * autocommit is currently enabled.</LI> * <LI>getFailureReason() -- Retrieves the a string that provides information * about the reason that the last operation failed.</LI> * <LI>getLoginTimeout() -- Retrieves in integer that indicates the maximum * length of time in seconds that the client will wait while trying to * establish a connection to the database.</LI> * <LI>getQueryTimeout() -- Retrieves an integer that indicates the maximum * length of time in seconds that the client will wait while trying to * execute a query or an update.</LI> * <LI>isConnected() -- Retrieves a Boolean value that indicates whether the * connection to the database is currently established.</LI> * <LI>prepareStatement(string sql) -- Retrieves a prepared statement that * may be used to perform the same kind of operation multiple times with * certain fields replaced.</LI> * <LI>rollBack() -- Attempts to perform a rollback to the last commit, * provided that autocommit is disabled. This method returns a Boolean * value that indicates whether the rollback was successful.</LI> * <LI>setAutoCommit(Boolean autoCommit) -- Specifies whether operations * performed on this connection will be automatically committed. This * method returns a Boolean value that indicates whether the change * was successful.</LI> * <LI>setDriverClass(string class) -- Specifies the fully-qualified Java * class name of the JDBC driver to use. This method returns a Boolean * value that indicates whether the driver class was loaded * successfully.</LI> * <LI>setLoginTimeout(int seconds) -- Specifies the maximum length of time in * seconds that the client should wait while trying to establish a * connection to the database. This method returns a Boolean value that * indicates whether the timeout was set successfully.</LI> * <LI>setQueryTimeout(int seconds) -- Specifies the maximum length of time in * seconds that the client should wait while trying to execute a query or * an update. This method returns a Boolean value that indicates whether * the timeout was set successfully.</LI> * </UL> * * * @author Neil A. Wilson */ public class JDBCConnectionVariable extends Variable { /** * The name that will be used for the data type of JDBC connection variables. */ public static final String JDBC_CONNECTION_VARIABLE_TYPE = "jdbcconnection"; /** * The name of the method that can be used to commit outstanding changes to * the database. */ public static final String COMMIT_METHOD_NAME = "commit"; /** * The method number for the "commit" method. */ public static final int COMMIT_METHOD_NUMBER = 0; /** * The name of the method that can be used to establish connections to a * database. */ public static final String CONNECT_METHOD_NAME = "connect"; /** * The method number for the first "connect" method. */ public static final int CONNECT_1_METHOD_NUMBER = 1; /** * The method number for the second "connect" method. */ public static final int CONNECT_2_METHOD_NUMBER = 2; /** * The name of the method that can be used to disconnect from the database. */ public static final String DISCONNECT_METHOD_NAME = "disconnect"; /** * The method number for the "close" method. */ public static final int DISCONNECT_METHOD_NUMBER = 3; /** * The name of the method that can be used to execute a query in the database. */ public static final String EXECUTE_QUERY_METHOD_NAME = "executequery"; /** * The method number for the "executeQuery" method. */ public static final int EXECUTE_QUERY_METHOD_NUMBER = 4; /** * The name of the method that can be used to execute an update in the * database. */ public static final String EXECUTE_UPDATE_METHOD_NAME = "executeupdate"; /** * The method number for the "executeUpdate" method. */ public static final int EXECUTE_UPDATE_METHOD_NUMBER = 5; /** * The name of the method that can be used to determine whether autocommit is * enabled in the database. */ public static final String GET_AUTO_COMMIT_METHOD_NAME = "getautocommit"; /** * The method number for the "getAutoCommit" method. */ public static final int GET_AUTO_COMMIT_METHOD_NUMBER = 6; /** * The name of the method that can be used to retrieve information about the * reason that the last operation failed. */ public static final String GET_FAILURE_REASON_METHOD_NAME = "getfailurereason"; /** * The method number for the "getFailureReason" method. */ public static final int GET_FAILURE_REASON_METHOD_NUMBER = 7; /** * The name of the method that can be used to retrieve the login timeout that * has been configured. */ public static final String GET_LOGIN_TIMEOUT_METHOD_NAME = "getlogintimeout"; /** * The method number for the "getLoginTimeout" method. */ public static final int GET_LOGIN_TIMEOUT_METHOD_NUMBER = 8; /** * The name of the method that can be used to retrieve the query timeout that * has been configured. */ public static final String GET_QUERY_TIMEOUT_METHOD_NAME = "getquerytimeout"; /** * The method number for the "getQueryTimeout" method. */ public static final int GET_QUERY_TIMEOUT_METHOD_NUMBER = 9; /** * The name of the method that can be used to determine whether this * connection is currently established. */ public static final String IS_CONNECTED_METHOD_NAME = "isconnected"; /** * The method number for the "isConnected" method. */ public static final int IS_CONNECTED_METHOD_NUMBER = 10; /** * The name of the method that can be used to create a prepared statement. */ public static final String PREPARE_STATEMENT_METHOD_NAME = "preparestatement"; /** * The method number for the "prepareStatement" method. */ public static final int PREPARE_STATEMENT_METHOD_NUMBER = 11; /** * The name of the method that can be used to roll back any outstanding * changes. */ public static final String ROLL_BACK_METHOD_NAME = "rollback"; /** * The method number for the "rollBack" method. */ public static final int ROLL_BACK_METHOD_NUMBER = 12; /** * The name of the method that can be used to enable or disable autocommit in * the database. */ public static final String SET_AUTO_COMMIT_METHOD_NAME = "setautocommit"; /** * The method number for the "setAutoCommit" method. */ public static final int SET_AUTO_COMMIT_METHOD_NUMBR = 13; /** * The name of the method that can be used to specify the JDBC driver class to * use. */ public static final String SET_DRIVER_CLASS_METHOD_NAME = "setdriverclass"; /** * The method number for the "setDriverClass" method. */ public static final int SET_DRIVER_CLASS_METHOD_NUMBER = 14; /** * The name of the method that can be used to specify the login timeout. */ public static final String SET_LOGIN_TIMEOUT_METHOD_NAME = "setlogintimeout"; /** * The method number for the "setLoginTimeout" method. */ public static final int SET_LOGIN_TIMEOUT_METHOD_NUMBER = 15; /** * The name of the method that can be used to specify the query timeout. */ public static final String SET_QUERY_TIMEOUT_METHOD_NAME = "setquerytimeout"; /** * The method number for the "setQueryTimeout" method. */ public static final int SET_QUERY_TIMEOUT_METHOD_NUMBER = 16; /** * The set of methods associated with JDBC connection variables. */ public static final Method[] JDBC_CONNECTION_VARIABLE_METHODS = new Method[] { new Method(COMMIT_METHOD_NAME, new String[0], BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(CONNECT_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(CONNECT_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE, StringVariable.STRING_VARIABLE_TYPE, StringVariable.STRING_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(DISCONNECT_METHOD_NAME, new String[0], null), new Method(EXECUTE_QUERY_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE }, JDBCResultSetVariable.JDBC_RESULT_SET_VARIABLE_TYPE), new Method(EXECUTE_UPDATE_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE }, IntegerVariable.INTEGER_VARIABLE_TYPE), new Method(GET_AUTO_COMMIT_METHOD_NAME, new String[0], BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(GET_FAILURE_REASON_METHOD_NAME, new String[0], StringVariable.STRING_VARIABLE_TYPE), new Method(GET_LOGIN_TIMEOUT_METHOD_NAME, new String[0], IntegerVariable.INTEGER_VARIABLE_TYPE), new Method(GET_QUERY_TIMEOUT_METHOD_NAME, new String[0], IntegerVariable.INTEGER_VARIABLE_TYPE), new Method(IS_CONNECTED_METHOD_NAME, new String[0], BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(PREPARE_STATEMENT_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE }, JDBCPreparedStatementVariable. JDBC_PREPARED_STATEMENT_VARIABLE_TYPE), new Method(ROLL_BACK_METHOD_NAME, new String[0], BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(SET_AUTO_COMMIT_METHOD_NAME, new String[] { BooleanVariable.BOOLEAN_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(SET_DRIVER_CLASS_METHOD_NAME, new String[] { StringVariable.STRING_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(SET_LOGIN_TIMEOUT_METHOD_NAME, new String[] { IntegerVariable.INTEGER_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE), new Method(SET_QUERY_TIMEOUT_METHOD_NAME, new String[] { IntegerVariable.INTEGER_VARIABLE_TYPE }, BooleanVariable.BOOLEAN_VARIABLE_TYPE) }; // The actual JDBC connection that we will use to perform all processing. private Connection jdbcConnection; // The maximum length of time to wait for a query or update. private int queryTimeout; // The URL used to establish the JDBC connection. private String connectionURL; // The variable that holds the reason for the last failure. private String failureReason; /** * Creates a new variable with no name, to be used only when creating a * variable with <CODE>Class.newInstance()</CODE>, and only when * <CODE>setName()</CODE> is called after that to set the name. * * @throws ScriptException If a problem occurs while initializing the new * variable. */ public JDBCConnectionVariable() throws ScriptException { jdbcConnection = null; queryTimeout = 0; connectionURL = null; failureReason = null; } /** * Retrieves the name of the variable type for this variable. * * @return The name of the variable type for this variable. */ @Override() public String getVariableTypeName() { return JDBC_CONNECTION_VARIABLE_TYPE; } /** * Retrieves a list of all methods defined for this variable. * * @return A list of all methods defined for this variable. */ @Override() public Method[] getMethods() { return JDBC_CONNECTION_VARIABLE_METHODS; } /** * Indicates whether this variable type has a method with the specified name. * * @param methodName The name of the method. * * @return <CODE>true</CODE> if this variable has a method with the specified * name, or <CODE>false</CODE> if it does not. */ @Override() public boolean hasMethod(String methodName) { for (int i=0; i < JDBC_CONNECTION_VARIABLE_METHODS.length; i++) { if (JDBC_CONNECTION_VARIABLE_METHODS[i].getName().equals(methodName)) { return true; } } return false; } /** * Retrieves the method number for the method that has the specified name and * argument types, or -1 if there is no such method. * * @param methodName The name of the method. * @param argumentTypes The list of argument types for the method. * * @return The method number for the method that has the specified name and * argument types. */ @Override() public int getMethodNumber(String methodName, String[] argumentTypes) { for (int i=0; i < JDBC_CONNECTION_VARIABLE_METHODS.length; i++) { if (JDBC_CONNECTION_VARIABLE_METHODS[i].hasSignature(methodName, argumentTypes)) { return i; } } return -1; } /** * Retrieves the return type for the method with the specified name and * argument types. * * @param methodName The name of the method. * @param argumentTypes The set of argument types for the method. * * @return The return type for the method, or <CODE>null</CODE> if there is * no such method defined. */ @Override() public String getReturnTypeForMethod(String methodName, String[] argumentTypes) { for (int i=0; i < JDBC_CONNECTION_VARIABLE_METHODS.length; i++) { if (JDBC_CONNECTION_VARIABLE_METHODS[i].hasSignature(methodName, argumentTypes)) { return JDBC_CONNECTION_VARIABLE_METHODS[i].getReturnType(); } } return null; } /** * Executes the specified method, using the provided variables as arguments * to the method, and makes the return value available to the caller. * * @param lineNumber The line number of the script in which the method * call occurs. * @param methodNumber The method number of the method to execute. * @param arguments The set of arguments to use for the method. * * @return The value returned from the method, or <CODE>null</CODE> if it * does not return a value. * * @throws ScriptException If the specified method does not exist, or if a * problem occurs while attempting to execute it. */ @Override() public Variable executeMethod(int lineNumber, int methodNumber, Argument[] arguments) throws ScriptException { switch (methodNumber) { case COMMIT_METHOD_NUMBER: if (jdbcConnection == null) { failureReason = "The JDBC connection is not established."; return new BooleanVariable(false); } try { jdbcConnection.commit(); failureReason = null; return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new BooleanVariable(false); } case CONNECT_1_METHOD_NUMBER: StringVariable sv = (StringVariable) arguments[0].getArgumentValue(); try { jdbcConnection = DriverManager.getConnection(sv.getStringValue()); connectionURL = sv.getStringValue(); failureReason = null; return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught Exception: " + e; jdbcConnection = null; connectionURL = null; return new BooleanVariable(false); } case CONNECT_2_METHOD_NUMBER: StringVariable sv1 = (StringVariable) arguments[0].getArgumentValue(); StringVariable sv2 = (StringVariable) arguments[1].getArgumentValue(); StringVariable sv3 = (StringVariable) arguments[2].getArgumentValue(); try { jdbcConnection = DriverManager.getConnection(sv1.getStringValue(), sv2.getStringValue(), sv3.getStringValue()); connectionURL = sv1.getStringValue(); failureReason = null; return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught Exception: " + e; jdbcConnection = null; connectionURL = null; return new BooleanVariable(false); } case DISCONNECT_METHOD_NUMBER: if (jdbcConnection != null) { try { jdbcConnection.close(); } catch (Exception e) {} } jdbcConnection = null; connectionURL = null; failureReason = null; return null; case EXECUTE_QUERY_METHOD_NUMBER: sv = (StringVariable) arguments[0].getArgumentValue(); if (jdbcConnection == null) { failureReason = "The JDBC connection is not established."; return new JDBCResultSetVariable(sv.getStringValue(), null); } try { Statement statement = jdbcConnection.createStatement(); statement.setQueryTimeout(queryTimeout); ResultSet resultSet = statement.executeQuery(sv.getStringValue()); statement.close(); failureReason = null; return new JDBCResultSetVariable(sv.getStringValue(), resultSet); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new JDBCResultSetVariable(sv.getStringValue(), null); } case EXECUTE_UPDATE_METHOD_NUMBER: sv = (StringVariable) arguments[0].getArgumentValue(); if (jdbcConnection == null) { failureReason = "The JDBC connection is not established."; return new IntegerVariable(-1); } try { Statement statement = jdbcConnection.createStatement(); statement.setQueryTimeout(queryTimeout); int rowsUpdated = statement.executeUpdate(sv.getStringValue()); statement.close(); failureReason = null; return new IntegerVariable(rowsUpdated); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new IntegerVariable(-1); } case GET_AUTO_COMMIT_METHOD_NUMBER: if (jdbcConnection == null) { failureReason = "The JDBC connection is not established."; return new BooleanVariable(false); } try { failureReason = null; return new BooleanVariable(jdbcConnection.getAutoCommit()); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new BooleanVariable(false); } case GET_FAILURE_REASON_METHOD_NUMBER: return new StringVariable(failureReason); case GET_LOGIN_TIMEOUT_METHOD_NUMBER: failureReason = null; return new IntegerVariable(DriverManager.getLoginTimeout()); case GET_QUERY_TIMEOUT_METHOD_NUMBER: failureReason = null; return new IntegerVariable(queryTimeout); case IS_CONNECTED_METHOD_NUMBER: if (jdbcConnection == null) { failureReason = null; return new BooleanVariable(false); } try { boolean isClosed = jdbcConnection.isClosed(); if (isClosed) { jdbcConnection = null; connectionURL = null; } return new BooleanVariable(! isClosed); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new BooleanVariable(false); } case PREPARE_STATEMENT_METHOD_NUMBER: sv = (StringVariable) arguments[0].getArgumentValue(); if (jdbcConnection == null) { failureReason = "The JDBC connection is not established."; return new JDBCPreparedStatementVariable(sv.getStringValue(), null); } try { PreparedStatement preparedStatement = jdbcConnection.prepareStatement(sv.getStringValue()); preparedStatement.setQueryTimeout(queryTimeout); failureReason = null; return new JDBCPreparedStatementVariable(sv.getStringValue(), preparedStatement); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new JDBCPreparedStatementVariable(sv.getStringValue(), null); } case ROLL_BACK_METHOD_NUMBER: if (jdbcConnection == null) { failureReason = "The JDBC connection is not established."; return new BooleanVariable(false); } try { jdbcConnection.rollback(); failureReason = null; return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new BooleanVariable(false); } case SET_AUTO_COMMIT_METHOD_NUMBR: BooleanVariable bv = (BooleanVariable) arguments[0].getArgumentValue(); if (jdbcConnection == null) { failureReason = "The JDBC connection is not established."; return new BooleanVariable(false); } try { jdbcConnection.setAutoCommit(bv.getBooleanValue()); failureReason = null; return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new BooleanVariable(false); } case SET_DRIVER_CLASS_METHOD_NUMBER: sv = (StringVariable) arguments[0].getArgumentValue(); try { Class<?> driverClass = Constants.classForName(sv.getStringValue()); Class<?> driverSuperclass = Constants.classForName("java.sql.Driver"); if (! driverSuperclass.isAssignableFrom(driverClass)) { failureReason = "Class " + sv.getStringValue() + " is not a valid JDBC driver class"; return new BooleanVariable(false); } failureReason = null; return new BooleanVariable(true); } catch (Exception e) { failureReason = "Caught Exception: " + e; return new BooleanVariable(false); } case SET_LOGIN_TIMEOUT_METHOD_NUMBER: IntegerVariable iv = (IntegerVariable) arguments[0].getArgumentValue(); if (iv.getIntValue() >= 0) { DriverManager.setLoginTimeout(iv.getIntValue()); failureReason = null; return new BooleanVariable(true); } else { failureReason = "The login timeout must be greater than or equal " + "to zero."; return new BooleanVariable(false); } case SET_QUERY_TIMEOUT_METHOD_NUMBER: iv = (IntegerVariable) arguments[0].getArgumentValue(); if (iv.getIntValue() >= 0) { queryTimeout = iv.getIntValue(); failureReason = null; return new BooleanVariable(true); } else { failureReason = "The login timeout must be greater than or equal " + "to zero."; return new BooleanVariable(false); } default: throw new ScriptException(lineNumber, "There is no method " + methodNumber + " defined for " + getArgumentType() + " variables."); } } /** * Assigns the value of the provided argument to this variable. The value of * the provided argument must be of the same type as this variable. * * @param argument The argument whose value should be assigned to this * variable. * * @throws ScriptException If a problem occurs while performing the * assignment. */ @Override() public void assign(Argument argument) throws ScriptException { if (! argument.getArgumentType().equals(JDBC_CONNECTION_VARIABLE_TYPE)) { throw new ScriptException("Attempt to assign an argument of type " + argument.getArgumentType() + " to a variable of type " + JDBC_CONNECTION_VARIABLE_TYPE + " rejected."); } JDBCConnectionVariable jcv = (JDBCConnectionVariable) argument.getArgumentValue(); jdbcConnection = jcv.jdbcConnection; queryTimeout = jcv.queryTimeout; connectionURL = jcv.connectionURL; failureReason = jcv.failureReason; } /** * Retrieves a string representation of the value of this argument. * * @return A string representation of the value of this argument. */ public String getValueAsString() { if (connectionURL == null) { return "JDBC Connection (not established)"; } else { return "JDBC Connection " + connectionURL; } } }