001: /*
002:
003: Derby - Class org.apache.derby.jdbc.ClientDriver
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.jdbc;
023:
024: import java.util.Enumeration;
025: import java.util.Properties;
026: import java.sql.SQLException;
027: import org.apache.derby.client.am.Configuration;
028: import org.apache.derby.client.am.SqlException;
029: import org.apache.derby.client.am.Utils;
030: import org.apache.derby.client.am.Version;
031: import org.apache.derby.client.am.ClientJDBCObjectFactory;
032: import org.apache.derby.client.am.ClientMessageId;
033: import org.apache.derby.client.net.ClientJDBCObjectFactoryImpl;
034: import org.apache.derby.shared.common.reference.Attribute;
035: import org.apache.derby.shared.common.reference.SQLState;
036: import org.apache.derby.shared.common.reference.MessageId;
037:
038: public class ClientDriver implements java.sql.Driver {
039: private transient int traceFileSuffixIndex_ = 0;
040:
041: private final static int DERBY_REMOTE_PROTOCOL = 1;
042:
043: private static ClientJDBCObjectFactory factoryObject = null;
044:
045: static private SQLException exceptionsOnLoadDriver__ = null;
046: // Keep track of the registere driver so that we can deregister it if we're a stored proc.
047: static private ClientDriver registeredDriver__ = null;
048:
049: static {
050: // This may possibly hit the race-condition bug of java 1.1.
051: // The Configuration static clause should execute before the following line does.
052: if (Configuration.exceptionsOnLoadResources != null) {
053: exceptionsOnLoadDriver__ = Utils.accumulateSQLException(
054: Configuration.exceptionsOnLoadResources
055: .getSQLException(),
056: exceptionsOnLoadDriver__);
057: }
058: try {
059: registeredDriver__ = new ClientDriver();
060: java.sql.DriverManager.registerDriver(registeredDriver__);
061: } catch (java.sql.SQLException e) {
062: // A null log writer is passed, because jdbc 1 sql exceptions are automatically traced
063: exceptionsOnLoadDriver__ = new SqlException(null,
064: new ClientMessageId(SQLState.JDBC_DRIVER_REGISTER))
065: .getSQLException();
066: exceptionsOnLoadDriver__.setNextException(e);
067: }
068: }
069:
070: public ClientDriver() {
071: }
072:
073: public java.sql.Connection connect(String url,
074: java.util.Properties properties)
075: throws java.sql.SQLException {
076: org.apache.derby.client.net.NetConnection conn;
077: try {
078: if (exceptionsOnLoadDriver__ != null) {
079: throw exceptionsOnLoadDriver__;
080: }
081:
082: if (properties == null) {
083: properties = new java.util.Properties();
084: }
085:
086: java.util.StringTokenizer urlTokenizer = new java.util.StringTokenizer(
087: url, "/:= \t\n\r\f", true);
088:
089: int protocol = tokenizeProtocol(url, urlTokenizer);
090: if (protocol == 0) {
091: return null; // unrecognized database URL prefix.
092: }
093:
094: String slashOrNull = null;
095: if (protocol == DERBY_REMOTE_PROTOCOL) {
096: try {
097: slashOrNull = urlTokenizer.nextToken(":/");
098: } catch (java.util.NoSuchElementException e) {
099: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
100: throw new SqlException(null, new ClientMessageId(
101: SQLState.MALFORMED_URL), url, e);
102: }
103: }
104: String server = tokenizeServerName(urlTokenizer, url); // "/server"
105: int port = tokenizeOptionalPortNumber(urlTokenizer, url); // "[:port]/"
106: if (port == 0) {
107: port = ClientDataSource.propertyDefault_portNumber;
108: }
109:
110: // database is the database name and attributes. This will be
111: // sent to network server as the databaseName
112: String database = tokenizeDatabase(urlTokenizer, url); // "database"
113: java.util.Properties augmentedProperties = tokenizeURLProperties(
114: url, properties);
115: database = appendDatabaseAttributes(database,
116: augmentedProperties);
117:
118: int traceLevel;
119: try {
120: traceLevel = ClientDataSource
121: .getTraceLevel(augmentedProperties);
122: } catch (java.lang.NumberFormatException e) {
123: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
124: throw new SqlException(null, new ClientMessageId(
125: SQLState.TRACELEVEL_FORMAT_INVALID), e);
126: }
127:
128: // Jdbc 1 connections will write driver trace info on a
129: // driver-wide basis using the jdbc 1 driver manager log writer.
130: // This log writer may be narrowed to the connection-level
131: // This log writer will be passed to the agent constructor.
132: org.apache.derby.client.am.LogWriter dncLogWriter = ClientDataSource
133: .computeDncLogWriterForNewConnection(
134: java.sql.DriverManager.getLogWriter(),
135: ClientDataSource
136: .getTraceDirectory(augmentedProperties),
137: ClientDataSource
138: .getTraceFile(augmentedProperties),
139: ClientDataSource
140: .getTraceFileAppend(augmentedProperties),
141: traceLevel, "_driver",
142: traceFileSuffixIndex_++);
143:
144: conn = (org.apache.derby.client.net.NetConnection) getFactory()
145: .newNetConnection(
146: (org.apache.derby.client.net.NetLogWriter) dncLogWriter,
147: java.sql.DriverManager.getLoginTimeout(),
148: server, port, database, augmentedProperties);
149: } catch (SqlException se) {
150: throw se.getSQLException();
151: }
152:
153: if (conn.isConnectionNull())
154: return null;
155:
156: return conn;
157: }
158:
159: /**
160: * Append attributes to the database name except for user/password
161: * which are sent as part of the protocol.
162: * Other attributes will be sent to the server with the database name
163: * Assumes augmentedProperties is not null
164: *
165: * @param database - Short database name
166: * @param augmentedProperties - Set of properties to append as attributes
167: * @return databaseName + attributes (e.g. mydb;create=true)
168: */
169: private String appendDatabaseAttributes(String database,
170: Properties augmentedProperties) {
171:
172: StringBuffer longDatabase = new StringBuffer(database);
173: for (Enumeration keys = augmentedProperties.keys(); keys
174: .hasMoreElements();) {
175: String key = (String) keys.nextElement();
176: if (key.equals(Attribute.USERNAME_ATTR)
177: || key.equals(Attribute.PASSWORD_ATTR))
178: continue;
179: longDatabase.append(";" + key + "="
180: + augmentedProperties.getProperty(key));
181: }
182: return longDatabase.toString();
183: }
184:
185: public boolean acceptsURL(String url) throws java.sql.SQLException {
186: try {
187: java.util.StringTokenizer urlTokenizer = new java.util.StringTokenizer(
188: url, "/:=; \t\n\r\f", true);
189: int protocol = tokenizeProtocol(url, urlTokenizer);
190: return protocol != 0;
191: } catch (SqlException se) {
192: throw se.getSQLException();
193: }
194: }
195:
196: public java.sql.DriverPropertyInfo[] getPropertyInfo(String url,
197: java.util.Properties properties)
198: throws java.sql.SQLException {
199: java.sql.DriverPropertyInfo driverPropertyInfo[] = new java.sql.DriverPropertyInfo[2];
200:
201: // If there are no properties set already,
202: // then create a dummy properties just to make the calls go thru.
203: if (properties == null) {
204: properties = new java.util.Properties();
205: }
206:
207: driverPropertyInfo[0] = new java.sql.DriverPropertyInfo(
208: Attribute.USERNAME_ATTR, properties.getProperty(
209: Attribute.USERNAME_ATTR,
210: ClientDataSource.propertyDefault_user));
211:
212: driverPropertyInfo[1] = new java.sql.DriverPropertyInfo(
213: Attribute.PASSWORD_ATTR, properties
214: .getProperty(Attribute.PASSWORD_ATTR));
215:
216: driverPropertyInfo[0].description = SqlException
217: .getMessageUtil().getTextMessage(
218: MessageId.CONN_USERNAME_DESCRIPTION);
219: driverPropertyInfo[1].description = SqlException
220: .getMessageUtil().getTextMessage(
221: MessageId.CONN_PASSWORD_DESCRIPTION);
222:
223: driverPropertyInfo[0].required = true;
224: driverPropertyInfo[1].required = false; // depending on the security mechanism
225:
226: return driverPropertyInfo;
227: }
228:
229: public int getMajorVersion() {
230: return Version.getMajorVersion();
231: }
232:
233: public int getMinorVersion() {
234: return Version.getMinorVersion();
235: }
236:
237: public boolean jdbcCompliant() {
238: return Configuration.jdbcCompliant;
239: }
240:
241: // ----------------helper methods---------------------------------------------
242:
243: // Tokenize one of the following:
244: // "jdbc:derby:"
245: // and return 0 if the protcol is unrecognized
246: // return DERBY_PROTOCOL for "jdbc:derby"
247: private static int tokenizeProtocol(String url,
248: java.util.StringTokenizer urlTokenizer) throws SqlException {
249: // Is this condition necessary, StringTokenizer constructor may do this for us
250: if (url == null) {
251: return 0;
252: }
253:
254: if (urlTokenizer == null) {
255: return 0;
256: }
257:
258: try {
259: String jdbc = urlTokenizer.nextToken(":");
260: if (!jdbc.equals("jdbc")) {
261: return 0;
262: }
263: if (!urlTokenizer.nextToken(":").equals(":")) {
264: return 0; // Skip over the first colon in jdbc:derby:
265: }
266: String dbname = urlTokenizer.nextToken(":");
267: int protocol = 0;
268: if (dbname.equals("derby")
269: && (url.indexOf("derby://") != -1)) {
270: // For Derby AS need to check for // since jdbc:derby: is also the
271: // embedded prefix
272: protocol = DERBY_REMOTE_PROTOCOL;
273: } else {
274: return 0;
275: }
276:
277: if (!urlTokenizer.nextToken(":").equals(":")) {
278: return 0; // Skip over the second colon in jdbc:derby:
279: }
280:
281: return protocol;
282: } catch (java.util.NoSuchElementException e) {
283: return 0;
284: }
285: }
286:
287: // tokenize "/server" from URL jdbc:derby://server:port/
288: // returns server name
289: private static String tokenizeServerName(
290: java.util.StringTokenizer urlTokenizer, String url)
291: throws SqlException {
292: try {
293: if (!urlTokenizer.nextToken("/").equals("/"))
294: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
295: {
296: throw new SqlException(null, new ClientMessageId(
297: SQLState.MALFORMED_URL), url);
298: }
299: return urlTokenizer.nextToken("/:");
300: } catch (java.util.NoSuchElementException e) {
301: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
302: throw new SqlException(null, new ClientMessageId(
303: SQLState.MALFORMED_URL), url);
304: }
305: }
306:
307: // tokenize "[:portNumber]/" from URL jdbc:derby://server[:port]/
308: // returns the portNumber or zero if portNumber is not specified.
309: private static int tokenizeOptionalPortNumber(
310: java.util.StringTokenizer urlTokenizer, String url)
311: throws SqlException {
312: try {
313: String firstToken = urlTokenizer.nextToken(":/");
314: if (firstToken.equals(":")) {
315: String port = urlTokenizer.nextToken("/");
316: if (!urlTokenizer.nextToken("/").equals("/")) {
317: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
318: throw new SqlException(null, new ClientMessageId(
319: SQLState.MALFORMED_URL), url);
320: }
321: return Integer.parseInt(port);
322: } else if (firstToken.equals("/")) {
323: return 0;
324: } else {
325: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
326: throw new SqlException(null, new ClientMessageId(
327: SQLState.MALFORMED_URL), url);
328: }
329: } catch (java.util.NoSuchElementException e) {
330: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
331: throw new SqlException(null, new ClientMessageId(
332: SQLState.MALFORMED_URL), url, e);
333: }
334: }
335:
336: //return database name
337: private static String tokenizeDatabase(
338: java.util.StringTokenizer urlTokenizer, String url)
339: throws SqlException {
340: try {
341: // DERBY-618 - database name can contain spaces in the path
342: String databaseName = urlTokenizer.nextToken("\t\n\r\f;");
343: return databaseName;
344: } catch (java.util.NoSuchElementException e) {
345: // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced
346: throw new SqlException(null, new ClientMessageId(
347: SQLState.MALFORMED_URL), url, e);
348: }
349: }
350:
351: private static java.util.Properties tokenizeURLProperties(
352: String url, java.util.Properties properties)
353: throws SqlException {
354: String attributeString = null;
355: int attributeIndex = -1;
356:
357: if ((url != null)
358: && ((attributeIndex = url.indexOf(";")) != -1)) {
359: attributeString = url.substring(attributeIndex);
360: }
361: return ClientDataSource.tokenizeAttributes(attributeString,
362: properties);
363: }
364:
365: /**
366: *This method returns an Implementation
367: *of ClientJDBCObjectFactory depending on
368: *VM under use
369: *Currently it returns either
370: *ClientJDBCObjectFactoryImpl
371: *(or)
372: *ClientJDBCObjectFactoryImpl40
373: */
374:
375: public static ClientJDBCObjectFactory getFactory() {
376: if (factoryObject != null)
377: return factoryObject;
378: if (Configuration.supportsJDBC40()) {
379: factoryObject = createJDBC40FactoryImpl();
380: } else {
381: factoryObject = createDefaultFactoryImpl();
382: }
383: return factoryObject;
384: }
385:
386: /**
387: *Returns an instance of the ClientJDBCObjectFactoryImpl class
388: */
389: private static ClientJDBCObjectFactory createDefaultFactoryImpl() {
390: return new ClientJDBCObjectFactoryImpl();
391: }
392:
393: /**
394: *Returns an instance of the ClientJDBCObjectFactoryImpl40 class
395: *If a ClassNotFoundException occurs then it returns an
396: *instance of ClientJDBCObjectFactoryImpl
397: *
398: *If a future version of JDBC comes then
399: *a similar method would be added say createJDBCXXFactoryImpl
400: *in which if the class is not found then it would
401: *return the lower version thus having a sort of cascading effect
402: *until it gets a valid instance
403: */
404:
405: private static ClientJDBCObjectFactory createJDBC40FactoryImpl() {
406: final String factoryName = "org.apache.derby.client.net.ClientJDBCObjectFactoryImpl40";
407: try {
408: return (ClientJDBCObjectFactory) Class.forName(factoryName)
409: .newInstance();
410: } catch (ClassNotFoundException cnfe) {
411: return createDefaultFactoryImpl();
412: } catch (InstantiationException ie) {
413: return createDefaultFactoryImpl();
414: } catch (IllegalAccessException iae) {
415: return createDefaultFactoryImpl();
416: }
417: }
418: }
|