/* * (C) Copyright IBM Corp. 2009 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.apps; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import javax.security.auth.login.LoginException; import com.ibm.gaiandb.GaianDBConfig; import com.ibm.gaiandb.GaianNode; import com.ibm.gaiandb.apps.dashboard.Dashboard; import com.ibm.gaiandb.diags.GDBMessages; import com.ibm.gaiandb.security.common.SecurityToken; import sun.misc.BASE64Encoder; import com.ibm.gaiandb.Logger; /** * Provides a framework for applications to connect to GaianDB easily. * * @author Samir Talwar - stalwar@uk.ibm.com */ public class DBConnector { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009"; private static final Logger logger = new Logger( "DBConnector", 30 ); /** The default connection URL. */ private static final String DEFAULT_URL = "jdbc:derby://localhost:" + GaianNode.DEFAULT_PORT + "/" + GaianDBConfig.GAIANDB_NAME; /** The default database user. */ private static final String DEFAULT_USER = GaianDBConfig.GAIAN_NODE_DEFAULT_USR; /** The default database password. */ private static final String DEFAULT_PASSWORD = GaianDBConfig.GAIAN_NODE_DEFAULT_PWD; /** * True if the Derby connection driver is loaded, as we only need to load it * once. */ private static boolean isDerbyDriverLoaded = false; /** The database connection. */ protected Connection conn; /** The JDBC URL with which we last attempted to connect. */ protected String url; /** True if we kept retrying last time we attempted to connect. */ protected boolean retry; /** A copy of the properties used to connect. */ protected Properties propsCache=null; /** The delay in seconds between connection retries. */ protected int retryDelay = 5; /* * Three methods of connection are supported: * 1. "simple", consisting of user's name and password; * 2. "asserted", where the user's identity is asserted by a trusted authority; * 3. "token", consisting of a token that represents the user's credentials. * * Each of the modes above require a set of properties, which are passed in via the Properties object. * These properties may contain two or more of the following: * 1. DB URL (e.g. jdbc:derby:/localhost:6414/gaiandb) -- mandatory * 2. User name -- mandatory * 3. Password * 4. SecurityToken * See below for key names. */ /** Connection modes */ public static final byte MODE_SIMPLE=1; public static final byte MODE_ASSERT=2; public static final byte MODE_TOKEN=3; public static final String MODEKEY="mode"; // indicates connection mode public static final String URLKEY="url"; public static final String USERKEY="user"; public static final String DOMAINKEY="domain"; public static final String PWDKEY="password"; public static final String PROXYUIDKEY = "proxy-user"; public static final String PROXYPWDKEY = "proxy-pwd"; public static final String TOKENKEY="token"; private static final String ANONUID = "APP";; /** * Retrieves the time we delay by between connections retries. * * @return The delay time, in seconds. */ public int getRetryDelay() { return retryDelay; } /** * Sets the delay time between connection retries. * * @param retryDelay * The new delay time, in seconds. */ public void setRetryDelay(int retryDelay) { this.retryDelay = retryDelay; } /** * Creates a new connector, but does not connect to any database until the * <code>connect</code> method is called. */ public DBConnector() { this(DEFAULT_USER, DEFAULT_PASSWORD, false); } /** * Initialises the connection URL, username and password from the * <code>args</code> parameter - usually command-line arguments. * * @param args * The application's command-line arguments. * * @throws ClassNotFoundException * if the Derby client driver class cannot be found. * @throws LoginException */ public DBConnector(String[] args) { this( args.length > 0 ? args[0] : DEFAULT_URL, args.length > 1 ? args[1] : DEFAULT_USER, args.length > 2 ? args[2] : DEFAULT_PASSWORD); } /** * Initialises the connection URL, username and password using the argument * provided. * * @param url * The JDBC URL of the database. * @param user * The chosen username. * @param password * The password associated with the given username. * * @throws ClassNotFoundException * if the Derby client driver class cannot be found. * @throws LoginException */ private DBConnector(String url, String user, String password) { this(user, password, true); this.url=url; } public DBConnector(String user, String password, final boolean retry) { this.propsCache = new Properties(); this.propsCache.setProperty(USERKEY, user); this.propsCache.setProperty(PWDKEY, password); this.propsCache.put(MODEKEY, MODE_SIMPLE); this.retry = retry; } public Connection connect(String url) throws LoginException, ClassNotFoundException { this.url=url; return this.connect(); } /** * Connects to the database at the URL provided, using the provided username * and password. Will try again if it fails. * * @param url * The JDBC URL of the database in the form * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>. * @param user * The chosen username. * @param password * The password associated with the given username. * * @return The database connection. * * @throws ClassNotFoundException * if the Derby client driver class cannot be found. * @throws LoginException */ public Connection connect(String url, Properties info) throws ClassNotFoundException, LoginException { return connect(url, info, true); } /** * Connects to the database at the URL provided, using the provided properties. * * @param url * The JDBC URL of the database in the form * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>. * @param user * The chosen username. * @param password * The password associated with the given username. * @param retry * If true, this method will keep trying to connect indefinitely * until it succeeds. If false, it will return null on failure. * * @return The database connection on success, or null on failure. * * @throws ClassNotFoundException * if the Derby client driver class cannot be found. */ public Connection connect(String url, Properties info, final boolean retry) throws ClassNotFoundException, LoginException { this.url = url; this.retry = retry; if (null == info || null == url || null == info.getProperty(USERKEY) || null == info.get(MODEKEY)) return null; // mandatory values are missing byte mode = Byte.parseByte(info.get(MODEKEY).toString()); if (!isDerbyDriverLoaded) { Class.forName( GaianDBConfig.DERBY_CLIENT_DRIVER ); isDerbyDriverLoaded = true; } logger.logInfo("Attempting to connect to " + url + " using mode: " + MODEKEY); conn = null; do { try { if (mode==MODE_SIMPLE || mode==MODE_ASSERT) { conn = DriverManager.getConnection(url, info); } if (mode==MODE_TOKEN) { SecurityToken secureToken = (SecurityToken)info.get(TOKENKEY); if (null == secureToken) return null; // mandatory value is missing // initiate "anonymous" connection conn = DriverManager.getConnection(url, ANONUID, "anonymous"); if (conn!=null && !conn.isClosed()) { String sid=sendAuthToken(secureToken); // send token to server if (null != sid) { conn.close(); // build a user name (with domain) -- must conform with SQL92 naming conventions StringBuffer sb = new StringBuffer(); sb.append('\"'); sb.append(info.getProperty(USERKEY)); sb.append('@'); sb.append(info.getProperty(DOMAINKEY)); sb.append('\"'); String uid=sb.toString(); // initiate connection for "real" user conn = DriverManager.getConnection(url, uid, sid); } } } if (null != conn && !conn.isClosed()) logger.logInfo("Connected to the database at " + url); break; } catch (SQLException e) { logger.logWarning(GDBMessages.DBCONNECTOR_CANNOT_CONNECT, "Could not connect to the database with credentials: " + e); conn = null; } if (retry) { try { Thread.sleep(retryDelay * 1000); } catch (InterruptedException e) { return null; } } } while (retry); return conn; } private String sendAuthToken(SecurityToken st) { // sends a user ID (UID) and security token (st), returns a session ID (SID) String sid=null; if (null != st) { try { String query = "VALUES GAIANDB.AUTHTOKEN(?)"; PreparedStatement ps = conn.prepareStatement(query); ps.setQueryTimeout(Dashboard.QUERY_TIMEOUT); String token=""; if (st.isValid()) token = new BASE64Encoder().encodeBuffer(st.get()); // convert to a String with Base64Encoding ps.setString(1, token); ResultSet resultSet = ps.executeQuery(); while (resultSet.next()) { sid = resultSet.getString(1); } // clean up resultSet.close(); resultSet = null; ps.close(); ps = null; } catch (SQLException e) { logger.logException(GDBMessages.DBCONNECTOR_CANNOT_FIND_SESSION_ID, "Could not find the session ID for this token", e); } } return sid; } /** * Connects using the pre-defined properties. * * @return The database connection. * * @throws ClassNotFoundException * if the Derby client driver class cannot be found. * @throws LoginException */ public Connection connect() throws ClassNotFoundException, LoginException { return connect(this.url, this.propsCache, this.retry); } /** * Returns the connection for use with other classes. * * @return A connection to the database, or <code>null</code> if one has not * been established yet. */ public Connection getConnection() { return conn; } /** * Displays the message you've given and a termination message, then quits * the program. Useful for a quick bailout. * * @param message * The message to display on <code>System.err</code>. */ public static void terminate(String message) { if (message != null && message.length() > 0) { logger.logWarning(GDBMessages.DBCONNECTOR_SHUTDOWN_MESSAGE, message); } logger.logInfo("This program will now terminate."); System.exit(1); // TODO replace this with a proper clean shutdown } }