0001: /**
0002: * Sequoia: Database clustering technology.
0003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
0004: * Science And Control (INRIA).
0005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
0006: * Copyright (C) 2005-2006 Continuent, Inc.
0007: * Contact: sequoia@continuent.org
0008: *
0009: * Licensed under the Apache License, Version 2.0 (the "License");
0010: * you may not use this file except in compliance with the License.
0011: * You may obtain a copy of the License at
0012: *
0013: * http://www.apache.org/licenses/LICENSE-2.0
0014: *
0015: * Unless required by applicable law or agreed to in writing, software
0016: * distributed under the License is distributed on an "AS IS" BASIS,
0017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0018: * See the License for the specific language governing permissions and
0019: * limitations under the License.
0020: *
0021: * Initial developer(s): Emmanuel Cecchet.
0022: * Contributor(s): Julie Marguerite, Mathieu Peltier, Marek Prochazka, Sara
0023: * Bouchenak, Jaco Swart.
0024: */package org.continuent.sequoia.driver;
0025:
0026: import java.io.IOException;
0027: import java.net.Socket;
0028: import java.security.GeneralSecurityException;
0029: import java.sql.DriverPropertyInfo;
0030: import java.sql.SQLException;
0031: import java.util.ArrayList;
0032: import java.util.HashMap;
0033: import java.util.Iterator;
0034: import java.util.Properties;
0035:
0036: import javax.net.SocketFactory;
0037:
0038: import org.continuent.sequoia.common.exceptions.AuthenticationException;
0039: import org.continuent.sequoia.common.exceptions.NoMoreControllerException;
0040: import org.continuent.sequoia.common.exceptions.driver.DriverSQLException;
0041: import org.continuent.sequoia.common.exceptions.driver.VirtualDatabaseUnavailableException;
0042: import org.continuent.sequoia.common.net.SSLConfiguration;
0043: import org.continuent.sequoia.common.net.SocketFactoryFactory;
0044: import org.continuent.sequoia.common.protocol.Commands;
0045: import org.continuent.sequoia.common.protocol.Field;
0046: import org.continuent.sequoia.common.protocol.SQLDataSerialization;
0047: import org.continuent.sequoia.common.protocol.TypeTag;
0048: import org.continuent.sequoia.common.stream.DriverBufferedInputStream;
0049: import org.continuent.sequoia.common.stream.DriverBufferedOutputStream;
0050: import org.continuent.sequoia.common.util.Constants;
0051: import org.continuent.sequoia.driver.connectpolicy.AbstractControllerConnectPolicy;
0052:
0053: /**
0054: * Sequoia Driver for client side. This driver is a generic driver that is
0055: * designed to replace any specific JDBC driver that could be used by a client.
0056: * The client only has to know the node where the Sequoia controller is running
0057: * and the database he wants to access (the RDBMS could be PostgreSQL, Oracle,
0058: * DB2, Sybase, MySQL or whatever, we only need the name of the database and the
0059: * Sequoia controller will be responsible for finding the RDBMs hosting this
0060: * database).
0061: * <p>
0062: * The Sequoia driver can be loaded from the client with:
0063: * <code>Class.forName("org.continuent.sequoia.driver.Driver");</code>
0064: * <p>
0065: * The URL expected for the use with Sequoia is:
0066: * <code>jdbc:sequoia://host1:port1,host2:port2/database</code>.
0067: * <p>
0068: * At least one host must be specified. If several hosts are given, one is
0069: * picked up randomly from the list. If the currently selected controller fails,
0070: * another one is automatically picked up from the list.
0071: * <p>
0072: * Default port number is 25322 if omitted.
0073: * <p>
0074: * Those 2 examples are equivalent:
0075: *
0076: * <pre>
0077: * DriverManager.getConnection("jdbc:sequoia://localhost:/tpcw");
0078: * DriverManager.getConnection("jdbc:sequoia://localhost:25322/tpcw");
0079: * </pre>
0080: *
0081: * <p>
0082: * Examples using 2 controllers for fault tolerance:
0083: *
0084: * <pre>
0085: * DriverManager
0086: * .getConnection("jdbc:sequoia://cluster1.continuent.org:25322,cluster2.continuent.org:25322/tpcw");
0087: * DriverManager
0088: * .getConnection("jdbc:sequoia://localhost:25322,remote.continuent.org:25322/tpcw");
0089: * DriverManager
0090: * .getConnection("jdbc:sequoia://smpnode.com:25322,smpnode.com:1098/tpcw");
0091: * </pre>
0092: *
0093: * <p>
0094: * The driver accepts a number of options that starts after a ? sign and are
0095: * separated by an & sign. Each option is a name=value pair. Example:
0096: * jdbc:sequoia://host/db?option1=value1;option2=value2.
0097: * <p>
0098: * Currently supported options are:
0099: *
0100: * <pre>
0101: * user: user login
0102: * password: user password
0103: * escapeBackslash: set this to true to escape backslashes when performing escape processing of PreparedStatements
0104: * escapeSingleQuote: set this to true to escape single quotes (') when performing escape processing of PreparedStatements
0105: * escapeCharacter: use this character to prepend and append to the values when performing escape processing of PreparedStatements
0106: * connectionPooling: set this to false if you do not want the driver to perform transparent connection pooling
0107: * preferredController: defines the strategy to use to choose a preferred controller to connect to
0108: * - jdbc:sequoia://node1,node2,node3/myDB?preferredController=ordered
0109: * Always connect to node1, and if not available then try to node2 and
0110: * finally if none are available try node3.
0111: * - jdbc:sequoia://node1,node2,node3/myDB?preferredController=random
0112: * Pickup a controller node randomly (default strategy)
0113: * - jdbc:sequoia://node1,node2:25343,node3/myDB?preferredController=node2:25343,node3
0114: * Round-robin between node2 and node3, fallback to node1 if none of node2
0115: * and node3 is available.
0116: * - jdbc:sequoia://node1,node2,node3/myDB?preferredController=roundRobin
0117: * Round robin starting with first node in URL.
0118: * pingDelayInMs Interval in milliseconds between two pings of a controller. The
0119: * default is 1000 (1 second).
0120: * controllerTimeoutInMs timeout in milliseconds after which a controller is
0121: * considered as dead if it did not respond to pings. Default is 25000 (25
0122: * seconds).
0123: * persistentConnection: defines if a connection should remain persistent on
0124: * cluster backends between connection opening and closing (bypasses any
0125: * connection pooling and preserve all information relative to the connection
0126: * context. Default is false.
0127: * retrieveSQLWarnings: set this to true if you want the controller to retrieve
0128: * SQL warnings. Default is false, which means that (Connection|Statement|ResultSet).getWarnings()
0129: * will always return null.
0130: * allowCommitWithAutoCommit: When set to true, trying to call commit/rollback
0131: * on a connection in autoCommit will not throw an exception. If set to false
0132: * (default) an SQLException will be thrown when commit is called on a
0133: * connection in autoCommit mode.
0134: * alwaysGetGeneratedKeys: when set to true, always fetch generated keys even if
0135: * not requested with Statement.RETURN_GENERATED_KEYS.
0136: * </pre>
0137: *
0138: * <p>
0139: * This original code has been inspired from the PostgreSQL JDBC driver by Peter
0140: * T. Mount <peter@retep.org.uk>and the MM MySQL JDBC Drivers from Mark Matthews
0141: * <mmatthew@worldserver.com>.
0142: *
0143: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
0144: * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
0145: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
0146: * @author <a href="mailto:Marek.Prochazka@inrialpes.fr">Marek Prochazka </a>
0147: * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
0148: * @author <a href="mailto:jaco.swart@iblocks.co.uk">Jaco Swart </a>
0149: * @author <a href="mailto:Gilles.Rayrat@continuent.com">Gilles Rayrat </a>
0150: * @version 1.0
0151: */
0152:
0153: public class Driver implements java.sql.Driver {
0154: /** Sequoia URL header. */
0155: protected String sequoiaUrlHeader = "jdbc:sequoia://";
0156:
0157: /** Sequoia URL header length. */
0158: protected int sequoiaUrlHeaderLength = sequoiaUrlHeader.length();
0159: /**
0160: * Default interval in milliseconds between two pings of a controller. The
0161: * default is 1000 (1 second)
0162: */
0163: public static final int DEFAULT_PING_DELAY_IN_MS = 1000;
0164: /**
0165: * Default timeout in milliseconds after which a controller is considered as
0166: * dead if it did not respond to pings. Default is 25000 (25 seconds).
0167: */
0168: public static final int DEFAULT_CONTROLLER_TIMEOUT_IN_MS = 25000;
0169:
0170: /**
0171: * List of driver properties initialized in the static class initializer
0172: * <p>
0173: * !!! Static intializer needs to be udpated when new properties are added !!!
0174: */
0175: protected static ArrayList driverPropertiesNames;
0176:
0177: /** Sequoia driver property name (if you add one, read driverProperties above). */
0178: protected static final String HOST_PROPERTY = "HOST";
0179: protected static final String PORT_PROPERTY = "PORT";
0180: protected static final String DATABASE_PROPERTY = "DATABASE";
0181: protected static final String USER_PROPERTY = "user";
0182: protected static final String PASSWORD_PROPERTY = "password";
0183:
0184: protected static final String ESCAPE_BACKSLASH_PROPERTY = "escapeBackslash";
0185: protected static final String ESCAPE_SINGLE_QUOTE_PROPERTY = "escapeSingleQuote";
0186: protected static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter";
0187: protected static final String CONNECTION_POOLING_PROPERTY = "connectionPooling";
0188: protected static final String PREFERRED_CONTROLLER_PROPERTY = "preferredController";
0189: protected static final String PING_DELAY_IN_MS_PROPERTY = "pingDelayInMs";
0190: protected static final String CONTROLLER_TIMEOUT_IN_MS_PROPERTY = "controllerTimeoutInMs";
0191: protected static final String DEBUG_PROPERTY = "debugLevel";
0192: protected static final String PERSISTENT_CONNECTION_PROPERTY = "persistentConnection";
0193: protected static final String RETRIEVE_SQL_WARNINGS_PROPERTY = "retrieveSQLWarnings";
0194: protected static final String ALLOW_COMMIT_WITH_AUTOCOMMIT_PROPERTY = "allowCommitWithAutoCommit";
0195: protected static final String ALWAYS_RETRIEVE_GENERATED_KEYS_PROPERTY = "alwaysGetGeneratedKeys";
0196:
0197: /** Sequoia driver property description. */
0198: private static final String HOST_PROPERTY_DESCRIPTION = "Hostname of Sequoia controller";
0199: private static final String PORT_PROPERTY_DESCRIPTION = "Port number of Sequoia controller";
0200: private static final String DATABASE_PROPERTY_DESCRIPTION = "Database name";
0201: private static final String USER_PROPERTY_DESCRIPTION = "Username to authenticate as";
0202: private static final String PASSWORD_PROPERTY_DESCRIPTION = "Password to use for authentication";
0203: private static final String ESCAPE_BACKSLASH_PROPERTY_DESCRIPTION = "Set this to true to escape backslashes when performing escape processing of PreparedStatements";
0204: private static final String ESCAPE_SINGLE_QUOTE_PROPERTY_DESCRIPTION = "Set this to true to escape single quotes (') when performing escape processing of PreparedStatements";
0205: private static final String ESCAPE_CHARACTER_PROPERTY_DESCRIPTION = "Use this character to prepend and append to the values when performing escape processing of PreparedStatements";
0206: protected static final String CONNECTION_POOLING_PROPERTY_DESCRIPTION = "Set this to false if you do not want the driver to perform transparent connection pooling";
0207: protected static final String PREFERRED_CONTROLLER_PROPERTY_DESCRIPTION = "Defines the strategy to use to choose a preferred controller to connect to";
0208: protected static final String PING_DELAY_IN_MS_DESCRIPTION = "Interval in milliseconds between two pings of a controller";
0209: protected static final String CONTROLLER_TIMEOUT_IN_MS_DESCRIPTION = "Timeout in milliseconds after which a controller is considered as dead if it did not respond to pings";
0210: protected static final String DEBUG_PROPERTY_DESCRIPTION = "Debug level that can be set to 'debug', 'info' or 'off'";
0211: protected static final String PERSISTENT_CONNECTION_PROPERTY_DESCRIPTION = "Defines if a connection in autoCommit mode should remain persistent on cluster backends";
0212: protected static final String RETRIEVE_SQL_WARNINGS_PROPERTY_DESCRIPTION = "Set this to true to allow retrieval of SQL warnings. A value set to false will make *.getWarnings() always return null";
0213: protected static final String ALLOW_COMMIT_WITH_AUTOCOMMIT_PROPERTY_DESCRIPTION = "Indicates whether or not commit can be called while autocommit is enabled";
0214: protected static final String ALWAYS_RETRIEVE_GENERATED_KEYS_DESCRIPTION = "Indicates whether or not every INSERT should make generated keys available, if any";
0215:
0216: /** Driver major version. */
0217: public static final int MAJOR_VERSION = Constants.getMajorVersion();
0218:
0219: /** Driver minor version. */
0220: public static final int MINOR_VERSION = Constants.getMinorVersion();
0221: /** Get the sequoia.ssl.enabled system property to check if SSL is enabled */
0222: protected static final boolean SSL_ENABLED_PROPERTY = "true"
0223: .equalsIgnoreCase(System.getProperty("sequoia.ssl.enabled"));
0224:
0225: /**
0226: * Cache of parsed URLs used to connect to the controller. It always grows and
0227: * is never purged: we don't yet handle the unlikely case of a long-lived
0228: * driver using zillions of different URLs. Warning: this caches urls BOTH
0229: * with AND without properties.
0230: * <p>
0231: * Hashmap is URL=> <code>SequoiaUrl</code>
0232: */
0233: private HashMap parsedUrlsCache = new HashMap();
0234:
0235: /** List of connections that are ready to be closed. */
0236: protected ArrayList pendingConnectionClosing = new ArrayList();
0237: protected boolean connectionClosingThreadisAlive = false;
0238:
0239: private JDBCRegExp jdbcRegExp = new SequoiaJDBCRegExp();
0240:
0241: // optimization: non-lazy class loading
0242: private static Class c1 = Field.class;
0243: private static Class c2 = SQLDataSerialization.class;
0244: private static Class c3 = TypeTag.class;
0245: private static Class c4 = DriverResultSet.class;
0246:
0247: // The static initializer registers ourselves with the DriverManager
0248: // and try to bind the Sequoia Controller
0249: static {
0250: // Register with the DriverManager (see JDBC API Tutorial and Reference,
0251: // Second Edition p. 941)
0252: try {
0253: java.sql.DriverManager.registerDriver(new Driver());
0254: } catch (SQLException e) {
0255: throw new RuntimeException(
0256: "Unable to register Sequoia driver");
0257: }
0258:
0259: // Build the static list of driver properties
0260: driverPropertiesNames = new ArrayList();
0261: driverPropertiesNames.add(Driver.HOST_PROPERTY);
0262: driverPropertiesNames.add(Driver.PORT_PROPERTY);
0263: driverPropertiesNames.add(Driver.DATABASE_PROPERTY);
0264: driverPropertiesNames.add(Driver.USER_PROPERTY);
0265: driverPropertiesNames.add(Driver.PASSWORD_PROPERTY);
0266:
0267: driverPropertiesNames.add(Driver.ESCAPE_BACKSLASH_PROPERTY);
0268: driverPropertiesNames.add(Driver.ESCAPE_SINGLE_QUOTE_PROPERTY);
0269: driverPropertiesNames.add(Driver.ESCAPE_CHARACTER_PROPERTY);
0270: driverPropertiesNames.add(Driver.CONNECTION_POOLING_PROPERTY);
0271: driverPropertiesNames.add(Driver.PREFERRED_CONTROLLER_PROPERTY);
0272: driverPropertiesNames.add(Driver.PING_DELAY_IN_MS_PROPERTY);
0273: driverPropertiesNames
0274: .add(Driver.CONTROLLER_TIMEOUT_IN_MS_PROPERTY);
0275: driverPropertiesNames.add(Driver.DEBUG_PROPERTY);
0276: driverPropertiesNames
0277: .add(Driver.PERSISTENT_CONNECTION_PROPERTY);
0278: driverPropertiesNames
0279: .add(Driver.ALLOW_COMMIT_WITH_AUTOCOMMIT_PROPERTY);
0280: driverPropertiesNames
0281: .add(Driver.ALWAYS_RETRIEVE_GENERATED_KEYS_PROPERTY);
0282: }
0283:
0284: /**
0285: * Creates a new <code>Driver</code>. Only an actual instance can be
0286: * registered to the DriverManager, see call to
0287: * {@link java.sql.DriverManager#registerDriver(java.sql.Driver)} in static
0288: * initializer block just above.
0289: */
0290: public Driver() {
0291: // see javadoc above
0292: }
0293:
0294: /**
0295: * Asks the Sequoia controller if the requested database can be accessed with
0296: * the provided user name and password. If the Sequoia controller can't access
0297: * the requested database, an <code>SQLException</code> is thrown, else a
0298: * "fake" <code>Connection</code> is returned to the user so that he or she
0299: * can create <code>Statements</code>.
0300: *
0301: * @param url the URL of the Sequoia controller to which to connect.
0302: * @param clientProperties a list of arbitrary string tag/value pairs as
0303: * connection arguments (usually at least a "user" and "password").
0304: * In case of conflict, this list overrides settings from the url;
0305: * see SEQUOIA-105
0306: * @return a <code>Connection</code> object that represents a connection to
0307: * the database through the Sequoia Controller.
0308: * @exception SQLException if an error occurs.
0309: */
0310: public java.sql.Connection connect(String url,
0311: Properties clientProperties) throws SQLException,
0312: VirtualDatabaseUnavailableException,
0313: NoMoreControllerException {
0314: if (url == null)
0315: throw new SQLException("Invalid null URL in connect");
0316:
0317: /**
0318: * If the URL is for another driver we must return null according to the
0319: * javadoc of the implemented interface. This is likely to happen as the
0320: * DriverManager tries every driver until one succeeds.
0321: */
0322: if (!url.startsWith(sequoiaUrlHeader))
0323: return null;
0324:
0325: Properties filteredProperties = filterProperties(clientProperties);
0326:
0327: String urlCacheKey = url + filteredProperties.toString();
0328:
0329: // In the common case, we do not synchronize
0330: SequoiaUrl sequoiaUrl = (SequoiaUrl) parsedUrlsCache
0331: .get(urlCacheKey);
0332: if (sequoiaUrl == null) // Not in the cache
0333: {
0334: synchronized (this ) {
0335: // Recheck here in case someone updated before we entered the
0336: // synchronized block
0337: sequoiaUrl = (SequoiaUrl) parsedUrlsCache
0338: .get(urlCacheKey);
0339: if (sequoiaUrl == null) {
0340: sequoiaUrl = new SequoiaUrl(this , url,
0341: filteredProperties);
0342: parsedUrlsCache.put(urlCacheKey, sequoiaUrl);
0343: }
0344: }
0345: }
0346:
0347: ControllerInfo controller = null;
0348: try {
0349: Connection newConn = getConnectionToNewController(sequoiaUrl);
0350: if (newConn != null)
0351: controller = newConn.getControllerInfo();
0352: return newConn;
0353: }
0354: // Exceptions thrown directly to client:
0355: // VirtualDatabaseUnavailableException
0356: // NoMoreControllerException
0357: catch (AuthenticationException e) {
0358: throw (SQLException) new SQLException(e.getMessage())
0359: .initCause(e);
0360: } catch (GeneralSecurityException e) {
0361: e.printStackTrace();
0362: throw (SQLException) new SQLException(
0363: "Fatal General Security Exception received while trying to connect")
0364: .initCause(e);
0365: } catch (RuntimeException e) {
0366: e.printStackTrace();
0367: throw (SQLException) new SQLException(
0368: "Fatal Runtime Exception received while trying to connect to"
0369: + e + ")").initCause(e);
0370: }
0371: }
0372:
0373: /**
0374: * This function should be used to establish a new connection to another
0375: * controller in the list. It monitors "virtualdatabase not available"
0376: * failures by calling {@link #connectToNextController(SequoiaUrl)} only a
0377: * limited number of times (number = number of available controllers).
0378: *
0379: * @param sequoiaUrl Sequoia URL object including parameters
0380: * @return connection to the next available controller
0381: * @throws AuthenticationException if the authentication has failed
0382: * @throws NoMoreControllerException if all controllers in the list are down
0383: * @throws DriverSQLException if the connection cannot be established with the
0384: * controller
0385: * @throws VirtualDatabaseUnavailableException if none of the remaining
0386: * controllers has the desired vdb
0387: */
0388: protected Connection getConnectionToNewController(
0389: SequoiaUrl sequoiaUrl) throws AuthenticationException,
0390: NoMoreControllerException,
0391: VirtualDatabaseUnavailableException,
0392: GeneralSecurityException {
0393: // This will count the number of controllers left to connect to upon
0394: // VDB not available exceptions
0395: // No need to synchronize with getController(): at worth, we will not try
0396: // one controller that just reappeared, or we will try two time the same
0397: // controller. This is harmless, we just want to prevent endless retry loops
0398: int numberOfCtrlsLeft = sequoiaUrl.getControllerConnectPolicy()
0399: .numberOfAliveControllers();
0400: while (numberOfCtrlsLeft > 0) {
0401: try {
0402: return connectToNextController(sequoiaUrl);
0403: } catch (VirtualDatabaseUnavailableException vdbue) {
0404: numberOfCtrlsLeft--;
0405: }
0406: }
0407: // at this point, we have tried all controllers
0408: throw new VirtualDatabaseUnavailableException(
0409: "Virtual database " + sequoiaUrl.getDatabaseName()
0410: + " not found on any of the controllers");
0411: }
0412:
0413: /**
0414: * Connects with the specified parameters to the next controller (next
0415: * according to the current connection policy).<br>
0416: * Retrieves a new controller by asking the connect policy, creates and
0417: * connects a socket to this new controller, and registers the new socket to
0418: * the policy. Then, tries to authenticate to the controller. Finally, creates
0419: * the connection with the given parameters.<br>
0420: * Upon IOException during connection, this function will mark the new
0421: * controller as failing (by calling
0422: * {@link AbstractControllerConnectPolicy#forceControllerDown(ControllerInfo)}
0423: * and try the next controller until NoMoreControllerException (which will be
0424: * forwarded to caller)<br>
0425: * If a VirtualDatabaseUnavailableException is thrown, the controller's vdb
0426: * will be considered as down by calling
0427: * {@link AbstractControllerConnectPolicy#setVdbDownOnController(ControllerInfo)}
0428: * and the exception will be forwarded to caller Note that newly connected
0429: * controller can be retrieved by calling
0430: * {@link Connection#getControllerInfo()}
0431: *
0432: * @param sequoiaUrl Sequoia URL object including parameters
0433: * @return connection to the next available controller
0434: * @throws VirtualDatabaseUnavailableException if the given vdb is not
0435: * available on the new controller we are trying to connect to
0436: * @throws AuthenticationException if the authentication has failed or the
0437: * database name is wrong
0438: * @throws NoMoreControllerException if all controllers in the list are down
0439: */
0440: private Connection connectToNextController(SequoiaUrl sequoiaUrl)
0441: throws AuthenticationException, NoMoreControllerException,
0442: VirtualDatabaseUnavailableException,
0443: GeneralSecurityException {
0444: // TODO: methods should be extracted to reduce the size of this one
0445:
0446: ControllerInfo newController = null;
0447:
0448: // Check the user
0449: String user = (String) sequoiaUrl.getParameters().get(
0450: USER_PROPERTY);
0451: if (user == null || user.equals(""))
0452: throw new AuthenticationException(
0453: "Invalid user name in connect");
0454: // Check the password
0455: String password = (String) sequoiaUrl.getParameters().get(
0456: PASSWORD_PROPERTY);
0457: if (password == null)
0458: password = "";
0459:
0460: // Let's go for a new connection
0461:
0462: // This is actually a connection constructor,
0463: // we should try to move most of it below.
0464: AbstractControllerConnectPolicy policy = sequoiaUrl
0465: .getControllerConnectPolicy();
0466: try {
0467: Socket socket = null;
0468: // This synchronized block prevents other connections from doing a
0469: // forceControllerDown between our getController and registerSocket
0470: synchronized (policy) {
0471: // Choose a controller according to the policy
0472: newController = policy.getController();
0473:
0474: // Try to retrieve a reusable connection
0475: if (!"false".equals(sequoiaUrl.getParameters().get(
0476: CONNECTION_POOLING_PROPERTY))) { // Connection pooling is activated
0477: Connection c = retrievePendingClosingConnection(
0478: sequoiaUrl, newController, user, password);
0479: if (c != null) {
0480: if (sequoiaUrl.isDebugEnabled())
0481: System.out
0482: .println("Reusing connection from pool");
0483: return c; // Re-use this one
0484: }
0485: }
0486:
0487: // Create the socket
0488: // SSL enabled ?
0489: if (SSL_ENABLED_PROPERTY) {
0490: SocketFactory sslFact = SocketFactoryFactory
0491: .createFactory(SSLConfiguration
0492: .getDefaultConfig());
0493: socket = sslFact.createSocket();
0494: } else {
0495: // no ssl - we use ordinary socket
0496: socket = new Socket();
0497: }
0498: // Register asap the socket to the policy callback so it can kill it
0499: // (even when connecting)
0500: // synchronized is reentrant => we can call a policy synchronized method
0501: // inside this synchronized(policy) block
0502: policy.registerSocket(newController, socket);
0503:
0504: socket.connect(newController);
0505: }
0506:
0507: // Disable Nagle algorithm else small messages are not sent
0508: // (at least under Linux) even if we flush the output stream.
0509: socket.setTcpNoDelay(true);
0510:
0511: if (sequoiaUrl.isInfoEnabled())
0512: System.out.println("Authenticating with controller "
0513: + newController);
0514:
0515: DriverBufferedOutputStream out = new DriverBufferedOutputStream(
0516: socket, sequoiaUrl.getDebugLevel());
0517: // Send protocol version and database name
0518: out.writeInt(Commands.ProtocolVersion);
0519: out.writeLongUTF(sequoiaUrl.getDatabaseName());
0520: out.flush();
0521:
0522: // Send user information
0523: out.writeLongUTF(user);
0524: out.writeLongUTF(password);
0525: out.flush();
0526:
0527: // Create input stream only here else it will block
0528: DriverBufferedInputStream in = new DriverBufferedInputStream(
0529: socket, sequoiaUrl.getDebugLevel());
0530:
0531: return new Connection(this , socket, in, out, sequoiaUrl,
0532: newController, user, password);
0533: } // try connect to the controller/connection constructor
0534: catch (IOException ioe) {
0535: policy.forceControllerDown(newController);
0536: return connectToNextController(sequoiaUrl);
0537: } catch (VirtualDatabaseUnavailableException vdbue) {
0538: // mark vdb as down. Caller will retry if appropriate
0539: policy.setVdbDownOnController(newController);
0540: throw vdbue;
0541: }
0542: // Other exceptions are forwarded to caller
0543: }
0544:
0545: /**
0546: * This extracts from the (too complex) client Properties a leaner and cleaner
0547: * HashMap with is: - maybe empty but never null, - holding only the keys we
0548: * are interested in, - its values are guaranteed to be strings, - no complex
0549: * and hidden layered "defaults". See SEQUOIA-105 and SEQUOIA-440
0550: *
0551: * @param props to filter
0552: * @return filtered properties
0553: * @throws SQLException
0554: */
0555: protected Properties filterProperties(Properties props) {
0556: Properties filtered = new Properties();
0557:
0558: if (props == null)
0559: return filtered;
0560:
0561: // extract only the keys we know
0562: Iterator iter = driverPropertiesNames.iterator();
0563: while (iter.hasNext()) {
0564: String name = (String) iter.next();
0565: String val = props.getProperty(name);
0566: if (val == null)
0567: continue;
0568: filtered.setProperty(name, val);
0569: }
0570:
0571: return filtered;
0572: }
0573:
0574: /**
0575: * This method is used to implement the transparent connection pooling and try
0576: * to retrieve a connection that was recently closed to the given controller
0577: * with the provided login/password information.
0578: *
0579: * @param url Sequoia URL object including parameters
0580: * @param controllerInfo the controller to connect to
0581: * @param user user name used for connection
0582: * @param password password used for connection
0583: * @return a connection that could be reuse or null if none
0584: */
0585: private Connection retrievePendingClosingConnection(SequoiaUrl url,
0586: ControllerInfo controllerInfo, String user, String password) {
0587: // Check if there is a connection that is about to be closed that could
0588: // be reused. We take the bet that if a connection has been released by
0589: // a client, in the general case, it will reuse the same connection.
0590: // As we need to keep the work in the synchronized block as minimal as
0591: // possible, we have to extract the string comparison
0592: // (url,name,password,controller)from the sync block. This way, we cannot
0593: // just read/compare/take the connection without synchronizing the whole
0594: // thing. A solution is to systematically extract the first available
0595: // connection in the sync block, and do the checkings outside the block. If
0596: // we fail, we re-sync to put the connection back but in practice it is
0597: // almost always a success and we don't really care to pay this extra cost
0598: // once in a while.
0599: try {
0600: Connection c;
0601: synchronized (pendingConnectionClosing) {
0602: // Take the last one to prevent shifting all elements
0603: c = (Connection) pendingConnectionClosing
0604: .remove(pendingConnectionClosing.size() - 1);
0605: }
0606: if (url.equals(c.getSequoiaUrl()) // This compares all the Connection
0607: // properties
0608: && controllerInfo.equals(c.getControllerInfo())
0609: && user.equals(c.getUserName())
0610: && password.equals(c.getPassword())) { // Great! Take this one.
0611: c.isClosed = false;
0612: return c;
0613: } else {
0614: // Put this connection back, it is not good for us
0615: synchronized (pendingConnectionClosing) {
0616: pendingConnectionClosing.add(c);
0617: // Now scan the list for a suitable connection
0618: for (Iterator iter = pendingConnectionClosing
0619: .iterator(); iter.hasNext();) {
0620: Connection conn = (Connection) iter.next();
0621: if (url.equals(conn.getSequoiaUrl()) // This compares all the
0622: // Connection
0623: // properties
0624: && controllerInfo.equals(conn
0625: .getControllerInfo())
0626: && user.equals(conn.getUserName())
0627: && password.equals(conn.getPassword())) { // Great! Take this one.
0628: iter.remove();
0629: conn.isClosed = false;
0630: return conn;
0631: }
0632: }
0633: }
0634: }
0635: } catch (IndexOutOfBoundsException ignore) {
0636: // No connection available
0637: }
0638: return null;
0639: }
0640:
0641: /**
0642: * Tests if the URL is understood by the driver. Simply tries to construct a
0643: * parsed URLs and catch the failure.
0644: *
0645: * @param url the JDBC URL.
0646: * @return <code>true</code> if the URL is correct, otherwise an exception
0647: * with extensive error message is thrown.
0648: * @exception SQLException if the URL is incorrect an explicit error message
0649: * is given.
0650: */
0651: public synchronized boolean acceptsURL(String url)
0652: throws SQLException {
0653: if (url == null)
0654: return false;
0655:
0656: try {
0657: SequoiaUrl sequoiaUrl = (SequoiaUrl) parsedUrlsCache
0658: .get(url);
0659: if (sequoiaUrl == null) // Not in the cache
0660: {
0661: synchronized (this ) {
0662: // Recheck here in case someone updated before we entered the
0663: // synchronized block
0664: sequoiaUrl = (SequoiaUrl) parsedUrlsCache.get(url);
0665: if (sequoiaUrl == null) {
0666: // URL parsed here.
0667: sequoiaUrl = new SequoiaUrl(this , url,
0668: new Properties());
0669: // Update the cache anyway that can be useful later on
0670: parsedUrlsCache.put(url, sequoiaUrl);
0671: }
0672: }
0673: }
0674: return true;
0675: } catch (SQLException e) {
0676: return false;
0677: }
0678: }
0679:
0680: /**
0681: * Change the database name in the provided URL.
0682: *
0683: * @param url URL to parse
0684: * @param newDbName new database name to insert
0685: * @return the updated URL
0686: * @throws SQLException if an error occurs while parsing the url
0687: */
0688: public String changeDatabaseName(String url, String newDbName)
0689: throws SQLException {
0690: StringBuffer sb = new StringBuffer();
0691: sb.append(sequoiaUrlHeader);
0692:
0693: SequoiaUrl sequoiaUrl = (SequoiaUrl) parsedUrlsCache.get(url);
0694: if (sequoiaUrl == null) {
0695: acceptsURL(url); // parse and put in cache
0696: sequoiaUrl = (SequoiaUrl) parsedUrlsCache.get(url);
0697: }
0698:
0699: // append controller list
0700: ControllerInfo[] controllerList = sequoiaUrl
0701: .getControllerList();
0702: for (int i = 0; i < controllerList.length; i++) {
0703: if (i == 0)
0704: sb.append(controllerList[i].toString());
0705: else
0706: sb.append("," + controllerList[i].toString());
0707: }
0708: sb.append("/" + newDbName);
0709:
0710: // append parameters parsed above
0711: HashMap params = sequoiaUrl.getParameters();
0712: if (params != null) {
0713: Iterator paramsKeys = params.keySet().iterator();
0714: String element = null;
0715: while (paramsKeys.hasNext()) {
0716: if (element == null)
0717: sb.append("?");
0718: else
0719: sb.append("&");
0720: element = (String) paramsKeys.next();
0721: sb.append(element + "=" + params.get(paramsKeys));
0722: }
0723: }
0724: return sb.toString();
0725: }
0726:
0727: /**
0728: * Get the default transaction isolation level to use for this driver.
0729: *
0730: * @return java.sql.Connection.TRANSACTION_READ_UNCOMMITTED
0731: */
0732: protected int getDefaultTransactionIsolationLevel() {
0733: return java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
0734: }
0735:
0736: /**
0737: * Returns the SequoiaJDBCRegExp value.
0738: *
0739: * @return Returns the SequoiaJDBCRegExp.
0740: */
0741: public JDBCRegExp getJDBCRegExp() {
0742: return jdbcRegExp;
0743: }
0744:
0745: /**
0746: * This method is intended to allow a generic GUI tool to discover what
0747: * properties it should prompt a human for in order to get enough information
0748: * to connect to a database.
0749: * <p>
0750: * The only properties supported by Sequoia are:
0751: * <ul>
0752: * <li>HOST_PROPERTY</li>
0753: * <li>PORT_PROPERTY</li>
0754: * <li>DATABASE_PROPERTY</li>
0755: * <li>USER_PROPERTY</li>
0756: * <li>PASSWORD_PROPERTY</li>
0757: * <li>ESCAPE_BACKSLASH_PROPERTY</li>
0758: * <li>ESCAPE_SINGLE_QUOTE_PROPERTY</li>
0759: * <li>ESCAPE_CHARACTER_PROPERTY</li>
0760: * <li>CONNECTION_POOLING_PROPERTY</li>
0761: * <li>PREFERRED_CONTROLLER_PROPERTY</li>
0762: * <li>PING_DELAY_IN_MS_PROPERTY</li>
0763: * <li>CONTROLLER_TIMEOUT_IN_MS_PROPERTY</li>
0764: * <li>DEBUG_PROPERTY</li>
0765: * <li>PERSISTENT_CONNECTION_PROPERTY</li>
0766: * <li>RETRIEVE_SQL_WARNINGS_PROPERTY</li>
0767: * <li>ALLOW_COMMIT_WITH_AUTOCOMMIT_PROPERTY</li>
0768: * </ul>
0769: *
0770: * @param url the URL of the database to connect to
0771: * @param info a proposed list of tag/value pairs that will be sent on connect
0772: * open.
0773: * @return an array of <code>DriverPropertyInfo</code> objects describing
0774: * possible properties. This array may be an empty array if no
0775: * properties are required (note that this override any setting that
0776: * might be set in the URL).
0777: * @exception SQLException if the url is not valid
0778: * @see java.sql.Driver#getPropertyInfo
0779: */
0780: public DriverPropertyInfo[] getPropertyInfo(String url,
0781: Properties info) throws SQLException {
0782: if (!acceptsURL(url))
0783: throw new SQLException("Invalid url " + url);
0784:
0785: SequoiaUrl sequoiaUrl;
0786: synchronized (this ) {
0787: sequoiaUrl = (SequoiaUrl) parsedUrlsCache.get(url);
0788: if (sequoiaUrl == null)
0789: throw new SQLException(
0790: "Error while retrieving URL information");
0791: }
0792: HashMap params = sequoiaUrl.getParameters();
0793:
0794: String host = info.getProperty(HOST_PROPERTY);
0795: if (host == null) {
0796: ControllerInfo[] controllerList = sequoiaUrl
0797: .getControllerList();
0798: for (int i = 0; i < controllerList.length; i++) {
0799: ControllerInfo controller = controllerList[i];
0800: if (i == 0)
0801: host = controller.toString();
0802: else
0803: host += "," + controller.toString();
0804: }
0805: }
0806: DriverPropertyInfo hostProp = new DriverPropertyInfo(
0807: HOST_PROPERTY, host);
0808: hostProp.required = true;
0809: hostProp.description = HOST_PROPERTY_DESCRIPTION;
0810:
0811: DriverPropertyInfo portProp = new DriverPropertyInfo(
0812: PORT_PROPERTY, info.getProperty(PORT_PROPERTY, Integer
0813: .toString(SequoiaUrl.DEFAULT_CONTROLLER_PORT)));
0814: portProp.required = false;
0815: portProp.description = PORT_PROPERTY_DESCRIPTION;
0816:
0817: String database = info.getProperty(DATABASE_PROPERTY);
0818: if (database == null)
0819: database = sequoiaUrl.getDatabaseName();
0820: DriverPropertyInfo databaseProp = new DriverPropertyInfo(
0821: DATABASE_PROPERTY, database);
0822: databaseProp.required = true;
0823: databaseProp.description = DATABASE_PROPERTY_DESCRIPTION;
0824:
0825: String user = info.getProperty(USER_PROPERTY);
0826: if (user == null)
0827: user = (String) params.get(USER_PROPERTY);
0828: DriverPropertyInfo userProp = new DriverPropertyInfo(
0829: USER_PROPERTY, user);
0830: userProp.required = true;
0831: userProp.description = USER_PROPERTY_DESCRIPTION;
0832:
0833: String password = info.getProperty(PASSWORD_PROPERTY);
0834: if (password == null)
0835: password = (String) params.get(PASSWORD_PROPERTY);
0836: DriverPropertyInfo passwordProp = new DriverPropertyInfo(
0837: PASSWORD_PROPERTY, password);
0838: passwordProp.required = true;
0839: passwordProp.description = PASSWORD_PROPERTY_DESCRIPTION;
0840:
0841: String escapeChar = info.getProperty(ESCAPE_CHARACTER_PROPERTY);
0842: if (escapeChar == null)
0843: escapeChar = (String) params.get(ESCAPE_CHARACTER_PROPERTY);
0844: DriverPropertyInfo escapeCharProp = new DriverPropertyInfo(
0845: ESCAPE_CHARACTER_PROPERTY, escapeChar);
0846: escapeCharProp.required = false;
0847: escapeCharProp.description = ESCAPE_CHARACTER_PROPERTY_DESCRIPTION;
0848:
0849: String escapeBackslash = info
0850: .getProperty(ESCAPE_BACKSLASH_PROPERTY);
0851: if (escapeBackslash == null)
0852: escapeBackslash = (String) params
0853: .get(ESCAPE_BACKSLASH_PROPERTY);
0854: DriverPropertyInfo escapeBackProp = new DriverPropertyInfo(
0855: ESCAPE_BACKSLASH_PROPERTY, escapeBackslash);
0856: escapeBackProp.required = false;
0857: escapeBackProp.description = ESCAPE_BACKSLASH_PROPERTY_DESCRIPTION;
0858:
0859: String escapeSingleQuote = info
0860: .getProperty(ESCAPE_SINGLE_QUOTE_PROPERTY);
0861: if (escapeSingleQuote == null)
0862: escapeSingleQuote = (String) params
0863: .get(ESCAPE_SINGLE_QUOTE_PROPERTY);
0864: DriverPropertyInfo escapeSingleProp = new DriverPropertyInfo(
0865: ESCAPE_SINGLE_QUOTE_PROPERTY, escapeSingleQuote);
0866: escapeSingleProp.required = false;
0867: escapeSingleProp.description = ESCAPE_SINGLE_QUOTE_PROPERTY_DESCRIPTION;
0868:
0869: String connectionPooling = info
0870: .getProperty(CONNECTION_POOLING_PROPERTY);
0871: if (connectionPooling == null)
0872: connectionPooling = (String) params
0873: .get(CONNECTION_POOLING_PROPERTY);
0874: DriverPropertyInfo connectionPoolingProp = new DriverPropertyInfo(
0875: CONNECTION_POOLING_PROPERTY, connectionPooling);
0876: connectionPoolingProp.required = false;
0877: connectionPoolingProp.description = CONNECTION_POOLING_PROPERTY_DESCRIPTION;
0878:
0879: String preferredController = info
0880: .getProperty(PREFERRED_CONTROLLER_PROPERTY);
0881: if (preferredController == null)
0882: preferredController = (String) params
0883: .get(PREFERRED_CONTROLLER_PROPERTY);
0884: DriverPropertyInfo preferredControllerProp = new DriverPropertyInfo(
0885: PREFERRED_CONTROLLER_PROPERTY, preferredController);
0886: preferredControllerProp.required = false;
0887: preferredControllerProp.description = PREFERRED_CONTROLLER_PROPERTY_DESCRIPTION;
0888:
0889: String pingDelayInMs = info
0890: .getProperty(PING_DELAY_IN_MS_PROPERTY);
0891: if (pingDelayInMs == null)
0892: pingDelayInMs = (String) params
0893: .get(PING_DELAY_IN_MS_PROPERTY);
0894: DriverPropertyInfo pingDelayInMsProp = new DriverPropertyInfo(
0895: PING_DELAY_IN_MS_PROPERTY, pingDelayInMs);
0896: pingDelayInMsProp.required = false;
0897: pingDelayInMsProp.description = PING_DELAY_IN_MS_DESCRIPTION;
0898:
0899: String controllerTimeoutInMs = info
0900: .getProperty(CONTROLLER_TIMEOUT_IN_MS_PROPERTY);
0901: if (controllerTimeoutInMs == null)
0902: controllerTimeoutInMs = (String) params
0903: .get(CONTROLLER_TIMEOUT_IN_MS_PROPERTY);
0904: DriverPropertyInfo controllerTimeoutInMsProp = new DriverPropertyInfo(
0905: CONTROLLER_TIMEOUT_IN_MS_PROPERTY,
0906: controllerTimeoutInMs);
0907: controllerTimeoutInMsProp.required = false;
0908: controllerTimeoutInMsProp.description = CONTROLLER_TIMEOUT_IN_MS_DESCRIPTION;
0909:
0910: String persistentConnection = info
0911: .getProperty(PERSISTENT_CONNECTION_PROPERTY);
0912: if (persistentConnection == null)
0913: persistentConnection = (String) params
0914: .get(PERSISTENT_CONNECTION_PROPERTY);
0915: DriverPropertyInfo persistentConnectionProp = new DriverPropertyInfo(
0916: PERSISTENT_CONNECTION_PROPERTY, persistentConnection);
0917: persistentConnectionProp.required = false;
0918: persistentConnectionProp.description = PERSISTENT_CONNECTION_PROPERTY_DESCRIPTION;
0919:
0920: String retrieveSQLWarnings = info
0921: .getProperty(RETRIEVE_SQL_WARNINGS_PROPERTY);
0922: if (retrieveSQLWarnings == null)
0923: retrieveSQLWarnings = (String) params
0924: .get(RETRIEVE_SQL_WARNINGS_PROPERTY);
0925: DriverPropertyInfo retrieveSQLWarningsProp = new DriverPropertyInfo(
0926: RETRIEVE_SQL_WARNINGS_PROPERTY, retrieveSQLWarnings);
0927: retrieveSQLWarningsProp.required = false;
0928: retrieveSQLWarningsProp.description = RETRIEVE_SQL_WARNINGS_PROPERTY_DESCRIPTION;
0929:
0930: String getGeneratedKeys = info
0931: .getProperty(ALWAYS_RETRIEVE_GENERATED_KEYS_PROPERTY);
0932: if (getGeneratedKeys == null)
0933: getGeneratedKeys = (String) params
0934: .get(ALWAYS_RETRIEVE_GENERATED_KEYS_PROPERTY);
0935: DriverPropertyInfo getGeneratedKeysProp = new DriverPropertyInfo(
0936: ALWAYS_RETRIEVE_GENERATED_KEYS_PROPERTY,
0937: getGeneratedKeys);
0938: getGeneratedKeysProp.required = false;
0939: getGeneratedKeysProp.description = ALWAYS_RETRIEVE_GENERATED_KEYS_DESCRIPTION;
0940:
0941: return new DriverPropertyInfo[] { hostProp, portProp,
0942: databaseProp, userProp, passwordProp, escapeCharProp,
0943: escapeBackProp, escapeSingleProp,
0944: connectionPoolingProp, preferredControllerProp,
0945: persistentConnectionProp, retrieveSQLWarningsProp,
0946: getGeneratedKeysProp };
0947: }
0948:
0949: /**
0950: * Gets the driver's major version number
0951: *
0952: * @return the driver's major version number
0953: */
0954: public int getMajorVersion() {
0955: return MAJOR_VERSION;
0956: }
0957:
0958: /**
0959: * Gets the driver's minor version number
0960: *
0961: * @return the driver's minor version number
0962: */
0963: public int getMinorVersion() {
0964: return MINOR_VERSION;
0965: }
0966:
0967: /**
0968: * Reports whether the driver is a genuine JDBC compliant driver. A driver may
0969: * only report <code>true</code> here if it passes the JDBC compliance
0970: * tests, otherwise it is required to return <code>false</code>. JDBC
0971: * compliance requires full support for the JDBC API and full support for SQL
0972: * 92 Entry Level. We cannot ensure that the underlying JDBC drivers will be
0973: * JDBC compliant, so it is safer to return <code>false</code>.
0974: *
0975: * @return always <code>false</code>
0976: */
0977: public boolean jdbcCompliant() {
0978: return false;
0979: }
0980:
0981: /**
0982: * @return True, escape processing of backslash is ON by default
0983: */
0984: public boolean getEscapeBackslash() {
0985: return true;
0986: }
0987:
0988: /**
0989: * @return the default escape character
0990: */
0991: public String getEscapeChar() {
0992: return "\'";
0993: }
0994:
0995: /**
0996: * @return True, escape processing of single quote is ON by default
0997: */
0998: public boolean getEscapeSingleQuote() {
0999: return true;
1000: }
1001:
1002: /**
1003: * @return True, as connection pooling is activated by default
1004: */
1005: public boolean getConnectionPooling() {
1006: return true;
1007: }
1008:
1009: /**
1010: * @return False, as connection are not persistent by default.
1011: */
1012: public boolean getPersistentConnection() {
1013: return false;
1014: }
1015:
1016: /**
1017: * @return False, as retrieval of SQL warnings is disabled by default.
1018: */
1019: public boolean getRetrieveSQLWarnings() {
1020: return false;
1021: }
1022:
1023: /**
1024: * @return False, as retrieval of generated keys is not forced by default.
1025: */
1026: public boolean getRetrieveGeneratedKeys() {
1027: return false;
1028: }
1029: }
|