001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
002: // Copyright (C) 2004 The jTDS Project
003: //
004: // This library is free software; you can redistribute it and/or
005: // modify it under the terms of the GNU Lesser General Public
006: // License as published by the Free Software Foundation; either
007: // version 2.1 of the License, or (at your option) any later version.
008: //
009: // This library is distributed in the hope that it will be useful,
010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: // Lesser General Public License for more details.
013: //
014: // You should have received a copy of the GNU Lesser General Public
015: // License along with this library; if not, write to the Free Software
016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: //
018: package net.sourceforge.jtds.jdbc;
019:
020: import java.sql.Connection;
021: import java.sql.DriverManager;
022: import java.sql.DriverPropertyInfo;
023: import java.sql.SQLException;
024: import java.util.Enumeration;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.Map;
028: import java.util.Properties;
029:
030: import net.sourceforge.jtds.ssl.Ssl;
031:
032: /**
033: * jTDS implementation of the java.sql.Driver interface.
034: * <p>
035: * Implementation note:
036: * <ol>
037: * <li>Property text names and descriptions are loaded from an external file resource.
038: * This allows the actual names and descriptions to be changed or localised without
039: * impacting this code.
040: * <li>The way in which the URL is parsed and converted to properties is rather
041: * different from the original jTDS Driver class.
042: * See parseURL and Connection.unpackProperties methods for more detail.
043: * </ol>
044: * @see java.sql.Driver
045: * @author Brian Heineman
046: * @author Mike Hutchinson
047: * @author Alin Sinpalean
048: * @version $Id: Driver.java,v 1.70 2007/08/02 22:35:27 bheineman Exp $
049: */
050: public class Driver implements java.sql.Driver {
051: /** URL prefix used by the driver (i.e <code>jdbc:jtds:</code>). */
052: private static String driverPrefix = "jdbc:jtds:";
053: /** Driver major version. */
054: static final int MAJOR_VERSION = 1;
055: /** Driver minor version. */
056: static final int MINOR_VERSION = 2;
057: /** Driver version miscellanea (e.g "-rc2", ".1" or <code>null</code>). */
058: static final String MISC_VERSION = ".2";
059: /** Set if the JDBC specification to implement is 3.0 or greater. */
060: public static final boolean JDBC3 = "1.4".compareTo(System
061: .getProperty("java.specification.version")) <= 0;
062: /** TDS 4.2 protocol (SQL Server 6.5 and later and Sybase 9 and later). */
063: public static final int TDS42 = 1;
064: /** TDS 5.0 protocol (Sybase 10 and later). */
065: public static final int TDS50 = 2;
066: /** TDS 7.0 protocol (SQL Server 7.0 and later). */
067: public static final int TDS70 = 3;
068: /** TDS 8.0 protocol (SQL Server 2000 and later)*/
069: public static final int TDS80 = 4;
070: /** TDS 8.1 protocol (SQL Server 2000 SP1 and later). */
071: public static final int TDS81 = 5;
072: /** Microsoft SQL Server. */
073: public static final int SQLSERVER = 1;
074: /** Sybase ASE. */
075: public static final int SYBASE = 2;
076:
077: //
078: // Property name keys
079: //
080: public static final String APPNAME = "prop.appname";
081: public static final String BATCHSIZE = "prop.batchsize";
082: public static final String BINDADDRESS = "prop.bindaddress";
083: public static final String BUFFERDIR = "prop.bufferdir";
084: public static final String BUFFERMAXMEMORY = "prop.buffermaxmemory";
085: public static final String BUFFERMINPACKETS = "prop.bufferminpackets";
086: public static final String CACHEMETA = "prop.cachemetadata";
087: public static final String CHARSET = "prop.charset";
088: public static final String DATABASENAME = "prop.databasename";
089: public static final String DOMAIN = "prop.domain";
090: public static final String INSTANCE = "prop.instance";
091: public static final String LANGUAGE = "prop.language";
092: public static final String LASTUPDATECOUNT = "prop.lastupdatecount";
093: public static final String LOBBUFFER = "prop.lobbuffer";
094: public static final String LOGFILE = "prop.logfile";
095: public static final String LOGINTIMEOUT = "prop.logintimeout";
096: public static final String MACADDRESS = "prop.macaddress";
097: public static final String MAXSTATEMENTS = "prop.maxstatements";
098: public static final String NAMEDPIPE = "prop.namedpipe";
099: public static final String PACKETSIZE = "prop.packetsize";
100: public static final String PASSWORD = "prop.password";
101: public static final String PORTNUMBER = "prop.portnumber";
102: public static final String PREPARESQL = "prop.preparesql";
103: public static final String PROGNAME = "prop.progname";
104: public static final String SERVERNAME = "prop.servername";
105: public static final String SERVERTYPE = "prop.servertype";
106: public static final String SOTIMEOUT = "prop.sotimeout";
107: public static final String SSL = "prop.ssl";
108: public static final String TCPNODELAY = "prop.tcpnodelay";
109: public static final String TDS = "prop.tds";
110: public static final String USECURSORS = "prop.usecursors";
111: public static final String USEJCIFS = "prop.usejcifs";
112: public static final String USENTLMV2 = "prop.usentlmv2";
113: public static final String USELOBS = "prop.uselobs";
114: public static final String USER = "prop.user";
115: public static final String SENDSTRINGPARAMETERSASUNICODE = "prop.useunicode";
116: public static final String WSID = "prop.wsid";
117: public static final String XAEMULATION = "prop.xaemulation";
118:
119: static {
120: try {
121: // Register this with the DriverManager
122: DriverManager.registerDriver(new Driver());
123: } catch (SQLException e) {
124: }
125: }
126:
127: public int getMajorVersion() {
128: return MAJOR_VERSION;
129: }
130:
131: public int getMinorVersion() {
132: return MINOR_VERSION;
133: }
134:
135: /**
136: * Returns the driver version.
137: * <p>
138: * Per [908906] 0.7: Static Version information, please.
139: *
140: * @return the driver version
141: */
142: public static final String getVersion() {
143: return MAJOR_VERSION + "." + MINOR_VERSION
144: + ((MISC_VERSION == null) ? "" : MISC_VERSION);
145: }
146:
147: /**
148: * Returns the string form of the object.
149: * <p>
150: * Per [887120] DriverVersion.getDriverVersion(); this will return a short
151: * version name.
152: * <p>
153: * Added back to driver per [1006449] 0.9rc1: Driver version broken
154: *
155: * @return the driver version
156: */
157: public String toString() {
158: return "jTDS " + getVersion();
159: }
160:
161: public boolean jdbcCompliant() {
162: return false;
163: }
164:
165: public boolean acceptsURL(String url) throws SQLException {
166: if (url == null) {
167: return false;
168: }
169:
170: return url.toLowerCase().startsWith(driverPrefix);
171: }
172:
173: public Connection connect(String url, Properties info)
174: throws SQLException {
175: if (url == null || !url.toLowerCase().startsWith(driverPrefix)) {
176: return null;
177: }
178:
179: Properties props = setupConnectProperties(url, info);
180:
181: if (JDBC3) {
182: return new ConnectionJDBC3(url, props);
183: }
184:
185: return new ConnectionJDBC2(url, props);
186: }
187:
188: public DriverPropertyInfo[] getPropertyInfo(final String url,
189: final Properties props) throws SQLException {
190:
191: Properties parsedProps = parseURL(url,
192: (props == null ? new Properties() : props));
193:
194: if (parsedProps == null) {
195: throw new SQLException(Messages.get("error.driver.badurl",
196: url), "08001");
197: }
198:
199: parsedProps = DefaultProperties
200: .addDefaultProperties(parsedProps);
201:
202: final Map propertyMap = new HashMap();
203: final Map descriptionMap = new HashMap();
204: Messages.loadDriverProperties(propertyMap, descriptionMap);
205:
206: final Map choicesMap = createChoicesMap();
207: final Map requiredTrueMap = createRequiredTrueMap();
208:
209: final DriverPropertyInfo[] dpi = new DriverPropertyInfo[propertyMap
210: .size()];
211: final Iterator iterator = propertyMap.entrySet().iterator();
212: for (int i = 0; iterator.hasNext(); i++) {
213:
214: final Map.Entry entry = (Map.Entry) iterator.next();
215: final String key = (String) entry.getKey();
216: final String name = (String) entry.getValue();
217:
218: final DriverPropertyInfo info = new DriverPropertyInfo(
219: name, parsedProps.getProperty(name));
220: info.description = (String) descriptionMap.get(key);
221: info.required = requiredTrueMap.containsKey(name);
222:
223: if (choicesMap.containsKey(name)) {
224: info.choices = (String[]) choicesMap.get(name);
225: }
226:
227: dpi[i] = info;
228: }
229:
230: return dpi;
231: }
232:
233: /**
234: * Sets up properties for the {@link #connect(String, java.util.Properties)} method.
235: *
236: * @param url the URL of the database to which to connect
237: * @param info a list of arbitrary string tag/value pairs as
238: * connection arguments.
239: * @return the set of properties for the connection
240: * @throws SQLException if an error occurs parsing the URL
241: */
242: private Properties setupConnectProperties(String url,
243: Properties info) throws SQLException {
244:
245: Properties props = parseURL(url, info);
246:
247: if (props == null) {
248: throw new SQLException(Messages.get("error.driver.badurl",
249: url), "08001");
250: }
251:
252: if (props.getProperty(Messages.get(Driver.LOGINTIMEOUT)) == null) {
253: props.setProperty(Messages.get(Driver.LOGINTIMEOUT),
254: Integer.toString(DriverManager.getLoginTimeout()));
255: }
256:
257: // Set default properties
258: props = DefaultProperties.addDefaultProperties(props);
259: return props;
260: }
261:
262: /**
263: * Creates a map of driver properties whose <code>choices</code>
264: * field should be set when calling
265: * {@link #getPropertyInfo(String, Properties)}.
266: * <p/>
267: * The values in the map are the <code>String[]</code> objects
268: * that should be set to the <code>choices</code> field.
269: *
270: * @return The map of {@link DriverPropertyInfo} objects whose
271: * <code>choices</code> should be set.
272: */
273: private static Map createChoicesMap() {
274:
275: final HashMap choicesMap = new HashMap();
276:
277: final String[] booleanChoices = new String[] { "true", "false" };
278: choicesMap.put(Messages.get(Driver.CACHEMETA), booleanChoices);
279: choicesMap.put(Messages.get(Driver.LASTUPDATECOUNT),
280: booleanChoices);
281: choicesMap.put(Messages.get(Driver.NAMEDPIPE), booleanChoices);
282: choicesMap.put(Messages.get(Driver.TCPNODELAY), booleanChoices);
283: choicesMap.put(Messages
284: .get(Driver.SENDSTRINGPARAMETERSASUNICODE),
285: booleanChoices);
286: choicesMap.put(Messages.get(Driver.USECURSORS), booleanChoices);
287: choicesMap.put(Messages.get(Driver.USELOBS), booleanChoices);
288: choicesMap
289: .put(Messages.get(Driver.XAEMULATION), booleanChoices);
290:
291: final String[] prepareSqlChoices = new String[] {
292: String.valueOf(TdsCore.UNPREPARED),
293: String.valueOf(TdsCore.TEMPORARY_STORED_PROCEDURES),
294: String.valueOf(TdsCore.EXECUTE_SQL),
295: String.valueOf(TdsCore.PREPARE), };
296: choicesMap.put(Messages.get(Driver.PREPARESQL),
297: prepareSqlChoices);
298:
299: final String[] serverTypeChoices = new String[] {
300: String.valueOf(SQLSERVER), String.valueOf(SYBASE), };
301: choicesMap.put(Messages.get(Driver.SERVERTYPE),
302: serverTypeChoices);
303:
304: final String[] tdsChoices = new String[] {
305: DefaultProperties.TDS_VERSION_42,
306: DefaultProperties.TDS_VERSION_50,
307: DefaultProperties.TDS_VERSION_70,
308: DefaultProperties.TDS_VERSION_80, };
309: choicesMap.put(Messages.get(Driver.TDS), tdsChoices);
310:
311: final String[] sslChoices = new String[] { Ssl.SSL_OFF,
312: Ssl.SSL_REQUEST, Ssl.SSL_REQUIRE, Ssl.SSL_AUTHENTICATE };
313: choicesMap.put(Messages.get(Driver.SSL), sslChoices);
314:
315: return choicesMap;
316: }
317:
318: /**
319: * Creates a map of driver properties that should be marked as
320: * required when calling {@link #getPropertyInfo(String, Properties)}.
321: * <p/>
322: * Note that only the key of the map is used to determine whether
323: * the <code>required</code> field should be set to <code>true</code>.
324: * If the key does not exist in the map, then the <code>required</code>
325: * field is set to <code>false</code>.
326: *
327: * @return The map of {@link DriverPropertyInfo} objects where
328: * <code>required</code> should be set to <code>true</code>.
329: */
330: private static Map createRequiredTrueMap() {
331: final HashMap requiredTrueMap = new HashMap();
332: requiredTrueMap.put(Messages.get(Driver.SERVERNAME), null);
333: requiredTrueMap.put(Messages.get(Driver.SERVERTYPE), null);
334: return requiredTrueMap;
335: }
336:
337: /**
338: * Parse the driver URL and extract the properties.
339: *
340: * @param url the URL to parse
341: * @param info any existing properties already loaded in a
342: * <code>Properties</code> object
343: * @return the URL properties as a <code>Properties</code> object
344: */
345: private static Properties parseURL(String url, Properties info) {
346: Properties props = new Properties();
347:
348: // Take local copy of existing properties
349: for (Enumeration e = info.propertyNames(); e.hasMoreElements();) {
350: String key = (String) e.nextElement();
351: String value = info.getProperty(key);
352:
353: if (value != null) {
354: props.setProperty(key.toUpperCase(), value);
355: }
356: }
357:
358: StringBuffer token = new StringBuffer(16);
359: int pos = 0;
360:
361: pos = nextToken(url, pos, token); // Skip jdbc
362:
363: if (!"jdbc".equalsIgnoreCase(token.toString())) {
364: return null; // jdbc: missing
365: }
366:
367: pos = nextToken(url, pos, token); // Skip jtds
368:
369: if (!"jtds".equalsIgnoreCase(token.toString())) {
370: return null; // jtds: missing
371: }
372:
373: pos = nextToken(url, pos, token); // Get server type
374: String type = token.toString().toLowerCase();
375:
376: Integer serverType = DefaultProperties.getServerType(type);
377: if (serverType == null) {
378: return null; // Bad server type
379: }
380: props.setProperty(Messages.get(Driver.SERVERTYPE), String
381: .valueOf(serverType));
382:
383: pos = nextToken(url, pos, token); // Null token between : and //
384:
385: if (token.length() > 0) {
386: return null; // There should not be one!
387: }
388:
389: pos = nextToken(url, pos, token); // Get server name
390: String host = token.toString();
391:
392: if (host.length() == 0) {
393: host = props.getProperty(Messages.get(Driver.SERVERNAME));
394: if (host == null || host.length() == 0) {
395: return null; // Server name missing
396: }
397: }
398:
399: props.setProperty(Messages.get(Driver.SERVERNAME), host);
400:
401: if (url.charAt(pos - 1) == ':' && pos < url.length()) {
402: pos = nextToken(url, pos, token); // Get port number
403:
404: try {
405: int port = Integer.parseInt(token.toString());
406: props.setProperty(Messages.get(Driver.PORTNUMBER),
407: Integer.toString(port));
408: } catch (NumberFormatException e) {
409: return null; // Bad port number
410: }
411: }
412:
413: if (url.charAt(pos - 1) == '/' && pos < url.length()) {
414: pos = nextToken(url, pos, token); // Get database name
415: props.setProperty(Messages.get(DATABASENAME), token
416: .toString());
417: }
418:
419: //
420: // Process any additional properties in URL
421: //
422: while (url.charAt(pos - 1) == ';' && pos < url.length()) {
423: pos = nextToken(url, pos, token);
424: String tmp = token.toString();
425: int index = tmp.indexOf('=');
426:
427: if (index > 0 && index < tmp.length() - 1) {
428: props.setProperty(
429: tmp.substring(0, index).toUpperCase(), tmp
430: .substring(index + 1));
431: } else {
432: props.setProperty(tmp.toUpperCase(), "");
433: }
434: }
435:
436: return props;
437: }
438:
439: /**
440: * Extract the next lexical token from the URL.
441: *
442: * @param url The URL being parsed
443: * @param pos The current position in the URL string.
444: * @param token The buffer containing the extracted token.
445: * @return The updated position as an <code>int</code>.
446: */
447: private static int nextToken(String url, int pos, StringBuffer token) {
448: token.setLength(0);
449:
450: while (pos < url.length()) {
451: char ch = url.charAt(pos++);
452:
453: if (ch == ':' || ch == ';') {
454: break;
455: }
456:
457: if (ch == '/') {
458: if (pos < url.length() && url.charAt(pos) == '/') {
459: pos++;
460: }
461:
462: break;
463: }
464:
465: token.append(ch);
466: }
467:
468: return pos;
469: }
470:
471: public static void main(String[] args) {
472: System.out.println("jTDS " + getVersion());
473: }
474: }
|