2003-11-03 15:22:07 +00:00

514 lines
15 KiB
Java

/*-------------------------------------------------------------------------
*
* 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
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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 <encoding>" argument
* to createdb.
*
* Our protocol takes the forms:
* <PRE>
* jdbc:postgresql://host:port/database?param1=val1&...
* </PRE>
*
* @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.
*
* <p>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.
*
* <p>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;
}