// jTDS JDBC Driver for Microsoft SQL Server and Sybase // Copyright (C) 2004 The jTDS Project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package net.sourceforge.jtds.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import net.sourceforge.jtds.ssl.Ssl; /** * jTDS implementation of the java.sql.Driver interface. * <p> * Implementation note: * <ol> * <li>Property text names and descriptions are loaded from an external file resource. * This allows the actual names and descriptions to be changed or localised without * impacting this code. * <li>The way in which the URL is parsed and converted to properties is rather * different from the original jTDS Driver class. * See parseURL and Connection.unpackProperties methods for more detail. * </ol> * @see java.sql.Driver * @author Brian Heineman * @author Mike Hutchinson * @author Alin Sinpalean * @version $Id: Driver.java,v 1.70.2.4 2009-12-30 11:19:40 ickzon Exp $ */ public class Driver implements java.sql.Driver { /** URL prefix used by the driver (i.e <code>jdbc:jtds:</code>). */ private static String driverPrefix = "jdbc:jtds:"; /** Driver major version. */ static final int MAJOR_VERSION = 1; /** Driver minor version. */ static final int MINOR_VERSION = 3; /** Driver version miscellanea (e.g "-rc2", ".1" or <code>null</code>). */ static final String MISC_VERSION = ".1"; /** TDS 4.2 protocol (SQL Server 6.5 and later and Sybase 9 and later). */ public static final int TDS42 = 1; /** TDS 5.0 protocol (Sybase 10 and later). */ public static final int TDS50 = 2; /** TDS 7.0 protocol (SQL Server 7.0 and later). */ public static final int TDS70 = 3; /** TDS 8.0 protocol (SQL Server 2000 and later)*/ public static final int TDS80 = 4; /** TDS 8.1 protocol (SQL Server 2000 SP1 and later). */ public static final int TDS81 = 5; /** TDS 9.0 protocol (SQL Server 2005 and later) */ public static final int TDS90 = 6; /** Microsoft SQL Server. */ public static final int SQLSERVER = 1; /** Sybase ASE. */ public static final int SYBASE = 2; // // Property name keys // public static final String APPNAME = "prop.appname"; public static final String AUTOCOMMIT = "prop.autocommit"; public static final String BATCHSIZE = "prop.batchsize"; public static final String BINDADDRESS = "prop.bindaddress"; public static final String BUFFERDIR = "prop.bufferdir"; public static final String BUFFERMAXMEMORY = "prop.buffermaxmemory"; public static final String BUFFERMINPACKETS = "prop.bufferminpackets"; public static final String CACHEMETA = "prop.cachemetadata"; public static final String CHARSET = "prop.charset"; public static final String DATABASENAME = "prop.databasename"; public static final String DOMAIN = "prop.domain"; public static final String INSTANCE = "prop.instance"; public static final String LANGUAGE = "prop.language"; public static final String LASTUPDATECOUNT = "prop.lastupdatecount"; public static final String LOBBUFFER = "prop.lobbuffer"; public static final String LOGFILE = "prop.logfile"; public static final String LOGINTIMEOUT = "prop.logintimeout"; public static final String MACADDRESS = "prop.macaddress"; public static final String MAXSTATEMENTS = "prop.maxstatements"; public static final String NAMEDPIPE = "prop.namedpipe"; public static final String PACKETSIZE = "prop.packetsize"; public static final String PASSWORD = "prop.password"; public static final String PORTNUMBER = "prop.portnumber"; public static final String PREPARESQL = "prop.preparesql"; public static final String PROGNAME = "prop.progname"; public static final String SERVERNAME = "prop.servername"; public static final String SERVERTYPE = "prop.servertype"; public static final String SOTIMEOUT = "prop.sotimeout"; public static final String SOKEEPALIVE = "prop.sokeepalive"; public static final String PROCESSID = "prop.processid"; public static final String SSL = "prop.ssl"; public static final String TCPNODELAY = "prop.tcpnodelay"; public static final String TDS = "prop.tds"; public static final String USECURSORS = "prop.usecursors"; public static final String USEJCIFS = "prop.usejcifs"; public static final String USENTLMV2 = "prop.usentlmv2"; public static final String USEKERBEROS = "prop.usekerberos"; public static final String USELOBS = "prop.uselobs"; public static final String USER = "prop.user"; public static final String SENDSTRINGPARAMETERSASUNICODE = "prop.useunicode"; public static final String WSID = "prop.wsid"; public static final String XAEMULATION = "prop.xaemulation"; static { try { // Register this with the DriverManager DriverManager.registerDriver(new Driver()); } catch (SQLException e) { } } public int getMajorVersion() { return MAJOR_VERSION; } public int getMinorVersion() { return MINOR_VERSION; } /** * Returns the driver version. * <p> * Per [908906] 0.7: Static Version information, please. * * @return the driver version */ public static final String getVersion() { return MAJOR_VERSION + "." + MINOR_VERSION + ((MISC_VERSION == null) ? "" : MISC_VERSION); } /** * Returns the string form of the object. * <p> * Per [887120] DriverVersion.getDriverVersion(); this will return a short * version name. * <p> * Added back to driver per [1006449] 0.9rc1: Driver version broken * * @return the driver version */ public String toString() { return "jTDS " + getVersion(); } public boolean jdbcCompliant() { return false; } public boolean acceptsURL(String url) throws SQLException { if (url == null) { return false; } return url.toLowerCase().startsWith(driverPrefix); } public Connection connect(String url, Properties info) throws SQLException { if (url == null || !url.toLowerCase().startsWith(driverPrefix)) { return null; } Properties props = setupConnectProperties(url, info); return new JtdsConnection(url, props); } public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties props) throws SQLException { Properties parsedProps = parseURL(url, (props == null ? new Properties() : props)); if (parsedProps == null) { throw new SQLException( Messages.get("error.driver.badurl", url), "08001"); } parsedProps = DefaultProperties.addDefaultProperties(parsedProps); final Map propertyMap = new HashMap(); final Map descriptionMap = new HashMap(); Messages.loadDriverProperties(propertyMap, descriptionMap); final Map choicesMap = createChoicesMap(); final Map requiredTrueMap = createRequiredTrueMap(); final DriverPropertyInfo[] dpi = new DriverPropertyInfo[propertyMap.size()]; final Iterator iterator = propertyMap.entrySet().iterator(); for (int i = 0; iterator.hasNext(); i++) { final Map.Entry entry = (Map.Entry) iterator.next(); final String key = (String) entry.getKey(); final String name = (String) entry.getValue(); final DriverPropertyInfo info = new DriverPropertyInfo(name, parsedProps.getProperty(name)); info.description = (String) descriptionMap.get(key); info.required = requiredTrueMap.containsKey(name); if (choicesMap.containsKey(name)) { info.choices = (String[]) choicesMap.get(name); } dpi[i] = info; } return dpi; } /** * Sets up properties for the {@link #connect(String, java.util.Properties)} method. * * @param url the URL of the database to which to connect * @param info a list of arbitrary string tag/value pairs as * connection arguments. * @return the set of properties for the connection * @throws SQLException if an error occurs parsing the URL */ private Properties setupConnectProperties(String url, Properties info) throws SQLException { Properties props = parseURL(url, info); if (props == null) { throw new SQLException(Messages.get("error.driver.badurl", url), "08001"); } if (props.getProperty(Messages.get(Driver.LOGINTIMEOUT)) == null) { props.setProperty(Messages.get(Driver.LOGINTIMEOUT), Integer.toString(DriverManager.getLoginTimeout())); } // Set default properties props = DefaultProperties.addDefaultProperties(props); return props; } /** * Creates a map of driver properties whose <code>choices</code> * field should be set when calling * {@link #getPropertyInfo(String, Properties)}. * <p/> * The values in the map are the <code>String[]</code> objects * that should be set to the <code>choices</code> field. * * @return The map of {@link DriverPropertyInfo} objects whose * <code>choices</code> should be set. */ private static Map createChoicesMap() { final HashMap choicesMap = new HashMap(); final String[] booleanChoices = new String[]{"true", "false"}; choicesMap.put(Messages.get(Driver.CACHEMETA), booleanChoices); choicesMap.put(Messages.get(Driver.LASTUPDATECOUNT), booleanChoices); choicesMap.put(Messages.get(Driver.NAMEDPIPE), booleanChoices); choicesMap.put(Messages.get(Driver.TCPNODELAY), booleanChoices); choicesMap.put(Messages.get(Driver.SENDSTRINGPARAMETERSASUNICODE), booleanChoices); choicesMap.put(Messages.get(Driver.USECURSORS), booleanChoices); choicesMap.put(Messages.get(Driver.USELOBS), booleanChoices); choicesMap.put(Messages.get(Driver.XAEMULATION), booleanChoices); final String[] prepareSqlChoices = new String[]{ String.valueOf(TdsCore.UNPREPARED), String.valueOf(TdsCore.TEMPORARY_STORED_PROCEDURES), String.valueOf(TdsCore.EXECUTE_SQL), String.valueOf(TdsCore.PREPARE), }; choicesMap.put(Messages.get(Driver.PREPARESQL), prepareSqlChoices); final String[] serverTypeChoices = new String[]{ String.valueOf(SQLSERVER), String.valueOf(SYBASE), }; choicesMap.put(Messages.get(Driver.SERVERTYPE), serverTypeChoices); final String[] tdsChoices = new String[]{ DefaultProperties.TDS_VERSION_42, DefaultProperties.TDS_VERSION_50, DefaultProperties.TDS_VERSION_70, DefaultProperties.TDS_VERSION_80, }; choicesMap.put(Messages.get(Driver.TDS), tdsChoices); final String[] sslChoices = new String[]{ Ssl.SSL_OFF, Ssl.SSL_REQUEST, Ssl.SSL_REQUIRE, Ssl.SSL_AUTHENTICATE }; choicesMap.put(Messages.get(Driver.SSL), sslChoices); return choicesMap; } /** * Creates a map of driver properties that should be marked as * required when calling {@link #getPropertyInfo(String, Properties)}. * <p/> * Note that only the key of the map is used to determine whether * the <code>required</code> field should be set to <code>true</code>. * If the key does not exist in the map, then the <code>required</code> * field is set to <code>false</code>. * * @return The map of {@link DriverPropertyInfo} objects where * <code>required</code> should be set to <code>true</code>. */ private static Map createRequiredTrueMap() { final HashMap requiredTrueMap = new HashMap(); requiredTrueMap.put(Messages.get(Driver.SERVERNAME), null); requiredTrueMap.put(Messages.get(Driver.SERVERTYPE), null); return requiredTrueMap; } /** * Parse the driver URL and extract the properties. * * @param url the URL to parse * @param info any existing properties already loaded in a * <code>Properties</code> object * @return the URL properties as a <code>Properties</code> object */ private static Properties parseURL(String url, Properties info) { Properties props = new Properties(); // Take local copy of existing properties for (Enumeration e = info.propertyNames(); e.hasMoreElements();) { String key = (String) e.nextElement(); String value = info.getProperty(key); if (value != null) { props.setProperty(key.toUpperCase(), value); } } StringBuilder token = new StringBuilder(16); int pos = 0; pos = nextToken(url, pos, token); // Skip jdbc if (!"jdbc".equalsIgnoreCase(token.toString())) { return null; // jdbc: missing } pos = nextToken(url, pos, token); // Skip jtds if (!"jtds".equalsIgnoreCase(token.toString())) { return null; // jtds: missing } pos = nextToken(url, pos, token); // Get server type String type = token.toString().toLowerCase(); Integer serverType = DefaultProperties.getServerType(type); if (serverType == null) { return null; // Bad server type } props.setProperty(Messages.get(Driver.SERVERTYPE), String.valueOf(serverType)); pos = nextToken(url, pos, token); // Null token between : and // if (token.length() > 0) { return null; // There should not be one! } pos = nextToken(url, pos, token); // Get server name String host = token.toString(); if (host.length() == 0) { host = props.getProperty(Messages.get(Driver.SERVERNAME)); if (host == null || host.length() == 0) { return null; // Server name missing } } props.setProperty(Messages.get(Driver.SERVERNAME), host); if (url.charAt(pos - 1) == ':' && pos < url.length()) { pos = nextToken(url, pos, token); // Get port number try { int port = Integer.parseInt(token.toString()); props.setProperty(Messages.get(Driver.PORTNUMBER), Integer.toString(port)); } catch(NumberFormatException e) { return null; // Bad port number } } if (url.charAt(pos - 1) == '/' && pos < url.length()) { pos = nextToken(url, pos, token); // Get database name props.setProperty(Messages.get(DATABASENAME), token.toString()); } // // Process any additional properties in URL // while (url.charAt(pos - 1) == ';' && pos < url.length()) { pos = nextToken(url, pos, token); String tmp = token.toString(); int index = tmp.indexOf('='); if (index > 0 && index < tmp.length() - 1) { props.setProperty(tmp.substring(0, index).toUpperCase(), tmp.substring(index + 1)); } else { props.setProperty(tmp.toUpperCase(), ""); } } return props; } /** * Extract the next lexical token from the URL. * * @param url The URL being parsed * @param pos The current position in the URL string. * @param token The buffer containing the extracted token. * @return The updated position as an <code>int</code>. */ private static int nextToken(String url, int pos, StringBuilder token) { token.setLength(0); boolean inQuote = false; while (pos < url.length()) { char ch = url.charAt(pos++); if (!inQuote) { if (ch == ':' || ch == ';') { break; } if (ch == '/') { if (pos < url.length() && url.charAt(pos) == '/') { pos++; } break; } } if (ch == '[') { inQuote = true; continue; } if (ch == ']') { inQuote = false; continue; } token.append(ch); } return pos; } public static void main(String[] args) { System.out.println("jTDS " + getVersion()); } //// JDBC4.1 demarcation, do NOT put any JDBC3/4.0 code below this line //// @Override public java.util.logging.Logger getParentLogger() throws java.sql.SQLFeatureNotSupportedException { // TODO Auto-generated method stub throw new AbstractMethodError(); } }