/*------------------------------------------------------------------------- * * Driver.java(.in) * The Postgresql JDBC Driver implementation * * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/Attic/Driver.java.in,v 1.37 2003/11/03 15:22:06 davec Exp $ * *------------------------------------------------------------------------- */ package org.postgresql; import java.io.*; import java.sql.*; import java.util.*; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; /* * The Java SQL framework allows for multiple database drivers. Each * driver should supply a class that implements the Driver interface * *

The DriverManager will try to load as many drivers as it can find and * then for any given connection request, it will ask each driver in turn * to try to connect to the target URL. * *

It is strongly recommended that each Driver class should be small and * standalone so that the Driver class can be loaded and queried without * bringing in vast quantities of supporting code. * *

When a Driver class is loaded, it should create an instance of itself * and register it with the DriverManager. This means that a user can load * and register a driver by doing Class.forName("foo.bah.Driver") * * @see org.postgresql.PGConnection * @see java.sql.Driver */ public class Driver implements java.sql.Driver { // make these public so they can be used in setLogLevel below public static final int DEBUG = 2; public static final int INFO = 1; public static boolean logDebug = false; public static boolean logInfo = false; static { try { // moved the registerDriver from the constructor to here // because some clients call the driver themselves (I know, as // my early jdbc work did - and that was based on other examples). // Placing it here, means that the driver is registered once only. java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException e) { e.printStackTrace(); } } /* * Try to make a database connection to the given URL. The driver * should return "null" if it realizes it is the wrong kind of * driver to connect to the given URL. This will be common, as * when the JDBC driverManager is asked to connect to a given URL, * it passes the URL to each loaded driver in turn. * *

The driver should raise an SQLException if it is the right driver * to connect to the given URL, but has trouble connecting to the * database. * *

The java.util.Properties argument can be used to pass arbitrary * string tag/value pairs as connection arguments. * * user - (optional) The user to connect as * password - (optional) The password for the user * ssl - (optional) Use SSL when connecting to the server * charSet - (optional) The character set to be used for converting * to/from the database to unicode. If multibyte is enabled on the * server then the character set of the database is used as the default, * otherwise the jvm character encoding is used as the default. * This value is only used when connecting to a 7.2 or older server. * loglevel - (optional) Enable logging of messages from the driver. * The value is an integer from 1 to 2 where: * INFO = 1, DEBUG = 2 * The output is sent to DriverManager.getPrintWriter() if set, * otherwise it is sent to System.out. * compatible - (optional) This is used to toggle * between different functionality as it changes across different releases * of the jdbc driver code. The values here are versions of the jdbc * client and not server versions. For example in 7.1 get/setBytes * worked on LargeObject values, in 7.2 these methods were changed * to work on bytea values. This change in functionality could * be disabled by setting the compatible level to be "7.1", in * which case the driver will revert to the 7.1 functionality. * *

Normally, at least * "user" and "password" properties should be included in the * properties. For a list of supported * character encoding , see * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html * Note that you will probably want to have set up the Postgres database * itself to use the same encoding, with the "-E " argument * to createdb. * * Our protocol takes the forms: *

	 *	jdbc:postgresql://host:port/database?param1=val1&...
	 * 
* * @param url the URL of the database to connect to * @param info a list of arbitrary tag/value pairs as connection * arguments * @return a connection to the URL or null if it isnt us * @exception SQLException if a database access error occurs * @see java.sql.Driver#connect */ public java.sql.Connection connect(String url, Properties info) throws SQLException { if ((props = parseURL(url, info)) == null) { if (Driver.logDebug) Driver.debug("Error in url" + url); return null; } try { if (Driver.logDebug) Driver.debug("connect " + url); @JDBCCONNECTCLASS@ con = (@JDBCCONNECTCLASS@)(Class.forName("@JDBCCONNECTCLASS@").newInstance()); con.openConnection (host(), port(), props, database(), url, this); return (java.sql.Connection)con; } catch (ClassNotFoundException ex) { if (Driver.logDebug) Driver.debug("error", ex); throw new PSQLException("postgresql.jvm.version", PSQLState.SYSTEM_ERROR, ex); } catch (PSQLException ex1) { // re-throw the exception, otherwise it will be caught next, and a // org.postgresql.unusual error will be returned instead. throw ex1; } catch (Exception ex2) { if (Driver.logDebug) { Driver.debug("error", ex2); } throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, ex2); } } /* * Returns true if the driver thinks it can open a connection to the * given URL. Typically, drivers will return true if they understand * the subprotocol specified in the URL and false if they don't. Our * protocols start with jdbc:postgresql: * * @see java.sql.Driver#acceptsURL * @param url the URL of the driver * @return true if this driver accepts the given URL * @exception SQLException if a database-access error occurs * (Dont know why it would *shrug*) */ public boolean acceptsURL(String url) throws SQLException { if (parseURL(url, null) == null) return false; return true; } /* * The getPropertyInfo method is intended to allow a generic GUI * tool to discover what properties it should prompt a human for * in order to get enough information to connect to a database. * *

Note that depending on the values the human has supplied so * far, additional values may become necessary, so it may be necessary * to iterate through several calls to getPropertyInfo * * @param url the Url of the database to connect to * @param info a proposed list of tag/value pairs that will be sent on * connect open. * @return An array of DriverPropertyInfo objects describing * possible properties. This array may be an empty array if * no properties are required * @exception SQLException if a database-access error occurs * @see java.sql.Driver#getPropertyInfo */ public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { //This method isn't really implemented //we just parse the URL to ensure it is valid parseURL(url, info); return new DriverPropertyInfo[0]; } /* * Gets the drivers major version number * * @return the drivers major version number */ public int getMajorVersion() { return @MAJORVERSION@; } /* * Get the drivers minor version number * * @return the drivers minor version number */ public int getMinorVersion() { return @MINORVERSION@; } /* * Returns the VERSION variable from Makefile.global */ public static String getVersion() { return "@VERSION@ (build " + m_buildNumber + ")"; } /* * Report whether the driver is a genuine JDBC compliant driver. A * driver may only report "true" here if it passes the JDBC compliance * tests, otherwise it is required to return false. JDBC compliance * requires full support for the JDBC API and full support for SQL 92 * Entry Level. * *

For PostgreSQL, this is not yet possible, as we are not SQL92 * compliant (yet). */ public boolean jdbcCompliant() { return false; } private Properties props; static private String[] protocols = { "jdbc", "postgresql" }; /* * Constructs a new DriverURL, splitting the specified URL into its * component parts * @param url JDBC URL to parse * @param defaults Default properties * @return Properties with elements added from the url * @exception SQLException */ Properties parseURL(String url, Properties defaults) throws SQLException { int state = -1; Properties urlProps = new Properties(defaults); String l_urlServer = url; String l_urlArgs = ""; int l_qPos = url.indexOf('?'); if (l_qPos != -1) { l_urlServer = url.substring(0,l_qPos); l_urlArgs = url.substring(l_qPos+1); } // look for an IPv6 address that is enclosed by [] // the upcoming parsing that uses colons as identifiers can't handle // the colons in an IPv6 address. int ipv6start = l_urlServer.indexOf("["); int ipv6end = l_urlServer.indexOf("]"); String ipv6address = null; if (ipv6start != -1 && ipv6end > ipv6start) { ipv6address = l_urlServer.substring(ipv6start+1,ipv6end); l_urlServer = l_urlServer.substring(0,ipv6start)+"ipv6host"+l_urlServer.substring(ipv6end+1); } //parse the server part of the url StringTokenizer st = new StringTokenizer(l_urlServer, ":/", true); int count; for (count = 0; (st.hasMoreTokens()); count++) { String token = st.nextToken(); // PM Aug 2 1997 - Modified to allow multiple backends if (count <= 3) { if ((count % 2) == 1 && token.equals(":")) ; else if ((count % 2) == 0) { boolean found = (count == 0) ? true : false; for (int tmp = 0;tmp < protocols.length;tmp++) { if (token.equals(protocols[tmp])) { // PM June 29 1997 Added this property to enable the driver // to handle multiple backend protocols. if (count == 2 && tmp > 0) { urlProps.put("Protocol", token); found = true; } } } if (found == false) return null; } else return null; } else if (count > 3) { if (count == 4 && token.equals("/")) state = 0; else if (count == 4) { urlProps.put("PGDBNAME", token); state = -2; } else if (count == 5 && state == 0 && token.equals("/")) state = 1; else if (count == 5 && state == 0) return null; else if (count == 6 && state == 1) urlProps.put("PGHOST", token); else if (count == 7 && token.equals(":")) state = 2; else if (count == 8 && state == 2) { try { Integer portNumber = Integer.decode(token); urlProps.put("PGPORT", portNumber.toString()); } catch (Exception e) { return null; } } else if ((count == 7 || count == 9) && (state == 1 || state == 2) && token.equals("/")) state = -1; else if (state == -1) { urlProps.put("PGDBNAME", token); state = -2; } } } if (count <= 1) { return null; } // if we extracted an IPv6 address out earlier put it back if (ipv6address != null) urlProps.put("PGHOST",ipv6address); //parse the args part of the url StringTokenizer qst = new StringTokenizer(l_urlArgs, "&"); for (count = 0; (qst.hasMoreTokens()); count++) { String token = qst.nextToken(); int l_pos = token.indexOf('='); if (l_pos == -1) { urlProps.put(token, ""); } else { urlProps.put(token.substring(0,l_pos), token.substring(l_pos+1)); } } return urlProps; } /* * @return the hostname portion of the URL */ public String host() { return props.getProperty("PGHOST", "localhost"); } /* * @return the port number portion of the URL or the default if no port was specified */ public int port() { return Integer.parseInt(props.getProperty("PGPORT", "@DEF_PGPORT@")); } /* * @return the database name of the URL */ public String database() { return props.getProperty("PGDBNAME", ""); } /* * @return the value of any property specified in the URL or properties * passed to connect(), or null if not found. */ public String property(String name) { return props.getProperty(name); } /* * This method was added in v6.5, and simply throws an SQLException * for an unimplemented method. I decided to do it this way while * implementing the JDBC2 extensions to JDBC, as it should help keep the * overall driver size down. */ public static SQLException notImplemented() { return new PSQLException("postgresql.unimplemented", PSQLState.NOT_IMPLEMENTED); } /** * used to turn logging on to a certain level, can be called * by specifying fully qualified class ie org.postgresql.Driver.setLogLevel() * @param int logLevel sets the level which logging will respond to * INFO being almost no messages * DEBUG most verbose */ public static void setLogLevel(int logLevel) { logDebug = (logLevel >= DEBUG) ? true : false; logInfo = (logLevel >= INFO) ? true : false; } /* * logging message at the debug level * messages will be printed if the logging level is less or equal to DEBUG */ public static void debug(String msg) { if (logDebug) { DriverManager.println(msg); } } /* * logging message at the debug level * messages will be printed if the logging level is less or equal to DEBUG */ public static void debug(String msg, Exception ex) { if (logDebug) { DriverManager.println(msg); if(ex != null) { DriverManager.println(ex.toString()); } } } /* * logging message at info level * messages will be printed if the logging level is less or equal to INFO */ public static void info(String msg) { if (logInfo) { DriverManager.println(msg); } } /* * logging message at info level * messages will be printed if the logging level is less or equal to INFO */ public static void info(String msg, Exception ex) { if (logInfo) { DriverManager.println(msg); if(ex != null) { DriverManager.println(ex.toString()); } } } public static void makeSSL(org.postgresql.core.PGStream p_stream) throws IOException { @SSL@ if (logDebug) @SSL@ debug("converting regular socket connection to ssl"); @SSL@ javax.net.ssl.SSLSocketFactory factory = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(); @SSL@ p_stream.connection = (javax.net.ssl.SSLSocket) factory.createSocket(p_stream.connection,p_stream.host,p_stream.port,true); @SSL@ p_stream.pg_input = new BufferedInputStream(p_stream.connection.getInputStream(), 8192); @SSL@ p_stream.pg_output = new BufferedOutputStream(p_stream.connection.getOutputStream(), 8192); } public static boolean sslEnabled() { boolean l_return = false; @SSL@ l_return = true; return l_return; } //The build number should be incremented for every new build private static int m_buildNumber = 209; }