1 | /* |
2 | |
3 | Derby - Class org.apache.derby.jdbc.ClientDriver |
4 | |
5 | Copyright (c) 2001, 2005 The Apache Software Foundation or its licensors, where applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.jdbc; |
22 | |
23 | import java.util.Enumeration; |
24 | import java.util.Properties; |
25 | import java.sql.SQLException; |
26 | import org.apache.derby.client.am.Configuration; |
27 | import org.apache.derby.client.am.SqlException; |
28 | import org.apache.derby.client.am.Utils; |
29 | import org.apache.derby.client.am.Version; |
30 | import org.apache.derby.client.am.ClientJDBCObjectFactory; |
31 | import org.apache.derby.client.am.ClientMessageId; |
32 | import org.apache.derby.client.net.ClientJDBCObjectFactoryImpl; |
33 | import org.apache.derby.shared.common.reference.Attribute; |
34 | import org.apache.derby.shared.common.reference.SQLState; |
35 | import org.apache.derby.shared.common.reference.MessageId; |
36 | |
37 | |
38 | public class ClientDriver implements java.sql.Driver { |
39 | private transient int traceFileSuffixIndex_ = 0; |
40 | |
41 | private final static int DERBY_REMOTE_PROTOCOL = 1; |
42 | |
43 | private static ClientJDBCObjectFactory factoryObject = null; |
44 | |
45 | static private SQLException exceptionsOnLoadDriver__ = null; |
46 | // Keep track of the registere driver so that we can deregister it if we're a stored proc. |
47 | static private ClientDriver registeredDriver__ = null; |
48 | |
49 | static { |
50 | // This may possibly hit the race-condition bug of java 1.1. |
51 | // The Configuration static clause should execute before the following line does. |
52 | if (Configuration.exceptionsOnLoadResources != null) { |
53 | exceptionsOnLoadDriver__ = |
54 | Utils.accumulateSQLException( |
55 | Configuration.exceptionsOnLoadResources.getSQLException(), |
56 | exceptionsOnLoadDriver__); |
57 | } |
58 | try { |
59 | registeredDriver__ = new ClientDriver(); |
60 | java.sql.DriverManager.registerDriver(registeredDriver__); |
61 | } catch (java.sql.SQLException e) { |
62 | // A null log writer is passed, because jdbc 1 sql exceptions are automatically traced |
63 | exceptionsOnLoadDriver__ = |
64 | new SqlException(null, |
65 | new ClientMessageId(SQLState.JDBC_DRIVER_REGISTER)).getSQLException(); |
66 | exceptionsOnLoadDriver__.setNextException(e); |
67 | } |
68 | } |
69 | |
70 | public ClientDriver() { |
71 | } |
72 | |
73 | public java.sql.Connection connect(String url, |
74 | java.util.Properties properties) throws java.sql.SQLException { |
75 | org.apache.derby.client.net.NetConnection conn; |
76 | try { |
77 | if (exceptionsOnLoadDriver__ != null) { |
78 | throw exceptionsOnLoadDriver__; |
79 | } |
80 | |
81 | if (properties == null) { |
82 | properties = new java.util.Properties(); |
83 | } |
84 | |
85 | java.util.StringTokenizer urlTokenizer = |
86 | new java.util.StringTokenizer(url, "/:= \t\n\r\f", true); |
87 | |
88 | int protocol = tokenizeProtocol(url, urlTokenizer); |
89 | if (protocol == 0) { |
90 | return null; // unrecognized database URL prefix. |
91 | } |
92 | |
93 | String slashOrNull = null; |
94 | if (protocol == DERBY_REMOTE_PROTOCOL) { |
95 | try { |
96 | slashOrNull = urlTokenizer.nextToken(":/"); |
97 | } catch (java.util.NoSuchElementException e) { |
98 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
99 | throw new SqlException(null, |
100 | new ClientMessageId(SQLState.MALFORMED_URL), |
101 | 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(url, properties); |
114 | database = appendDatabaseAttributes(database,augmentedProperties); |
115 | |
116 | int traceLevel; |
117 | try { |
118 | traceLevel = ClientDataSource.getTraceLevel(augmentedProperties); |
119 | } catch (java.lang.NumberFormatException e) { |
120 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
121 | throw new SqlException(null, |
122 | new ClientMessageId(SQLState.TRACELEVEL_FORMAT_INVALID), e); |
123 | } |
124 | |
125 | // Jdbc 1 connections will write driver trace info on a |
126 | // driver-wide basis using the jdbc 1 driver manager log writer. |
127 | // This log writer may be narrowed to the connection-level |
128 | // This log writer will be passed to the agent constructor. |
129 | org.apache.derby.client.am.LogWriter dncLogWriter = |
130 | ClientDataSource.computeDncLogWriterForNewConnection(java.sql.DriverManager.getLogWriter(), |
131 | ClientDataSource.getTraceDirectory(augmentedProperties), |
132 | ClientDataSource.getTraceFile(augmentedProperties), |
133 | ClientDataSource.getTraceFileAppend(augmentedProperties), |
134 | traceLevel, |
135 | "_driver", |
136 | traceFileSuffixIndex_++); |
137 | |
138 | |
139 | conn = (org.apache.derby.client.net.NetConnection)getFactory(). |
140 | newNetConnection((org.apache.derby.client.net.NetLogWriter) |
141 | dncLogWriter, |
142 | java.sql.DriverManager.getLoginTimeout(), |
143 | server, |
144 | port, |
145 | database, |
146 | augmentedProperties); |
147 | } catch(SqlException se) { |
148 | throw se.getSQLException(); |
149 | } |
150 | |
151 | if(conn.isConnectionNull()) |
152 | return null; |
153 | |
154 | return conn; |
155 | } |
156 | |
157 | /** |
158 | * Append attributes to the database name except for user/password |
159 | * which are sent as part of the protocol. |
160 | * Other attributes will be sent to the server with the database name |
161 | * Assumes augmentedProperties is not null |
162 | * |
163 | * @param database - Short database name |
164 | * @param augmentedProperties - Set of properties to append as attributes |
165 | * @return databaseName + attributes (e.g. mydb;create=true) |
166 | */ |
167 | private String appendDatabaseAttributes(String database, Properties augmentedProperties) { |
168 | |
169 | StringBuffer longDatabase = new StringBuffer(database); |
170 | for (Enumeration keys = augmentedProperties.keys(); keys.hasMoreElements() ;) |
171 | { |
172 | String key = (String) keys.nextElement(); |
173 | if (key.equals(Attribute.USERNAME_ATTR) || |
174 | key.equals(Attribute.PASSWORD_ATTR)) |
175 | continue; |
176 | longDatabase.append(";" + key + "=" + augmentedProperties.getProperty(key)); |
177 | } |
178 | return longDatabase.toString(); |
179 | } |
180 | |
181 | public boolean acceptsURL(String url) throws java.sql.SQLException { |
182 | try |
183 | { |
184 | java.util.StringTokenizer urlTokenizer = |
185 | new java.util.StringTokenizer(url, "/:=; \t\n\r\f", true); |
186 | int protocol = tokenizeProtocol(url, urlTokenizer); |
187 | return protocol != 0; |
188 | } |
189 | catch ( SqlException se ) |
190 | { |
191 | throw se.getSQLException(); |
192 | } |
193 | } |
194 | |
195 | public java.sql.DriverPropertyInfo[] getPropertyInfo(String url, |
196 | java.util.Properties properties) throws java.sql.SQLException { |
197 | java.sql.DriverPropertyInfo driverPropertyInfo[] = new java.sql.DriverPropertyInfo[2]; |
198 | |
199 | // If there are no properties set already, |
200 | // then create a dummy properties just to make the calls go thru. |
201 | if (properties == null) { |
202 | properties = new java.util.Properties(); |
203 | } |
204 | |
205 | driverPropertyInfo[0] = |
206 | new java.sql.DriverPropertyInfo(Attribute.USERNAME_ATTR, |
207 | properties.getProperty(Attribute.USERNAME_ATTR, ClientDataSource.propertyDefault_user)); |
208 | |
209 | driverPropertyInfo[1] = |
210 | new java.sql.DriverPropertyInfo(Attribute.PASSWORD_ATTR, |
211 | properties.getProperty(Attribute.PASSWORD_ATTR)); |
212 | |
213 | driverPropertyInfo[0].description = |
214 | SqlException.getMessageUtil().getTextMessage( |
215 | MessageId.CONN_USERNAME_DESCRIPTION); |
216 | driverPropertyInfo[1].description = |
217 | SqlException.getMessageUtil().getTextMessage( |
218 | MessageId.CONN_PASSWORD_DESCRIPTION); |
219 | |
220 | driverPropertyInfo[0].required = true; |
221 | driverPropertyInfo[1].required = false; // depending on the security mechanism |
222 | |
223 | return driverPropertyInfo; |
224 | } |
225 | |
226 | public int getMajorVersion() { |
227 | return Version.getMajorVersion(); |
228 | } |
229 | |
230 | public int getMinorVersion() { |
231 | return Version.getMinorVersion(); |
232 | } |
233 | |
234 | public boolean jdbcCompliant() { |
235 | return Configuration.jdbcCompliant; |
236 | } |
237 | |
238 | // ----------------helper methods--------------------------------------------- |
239 | |
240 | // Tokenize one of the following: |
241 | // "jdbc:derby:" |
242 | // and return 0 if the protcol is unrecognized |
243 | // return DERBY_PROTOCOL for "jdbc:derby" |
244 | private static int tokenizeProtocol(String url, java.util.StringTokenizer urlTokenizer) throws SqlException { |
245 | // Is this condition necessary, StringTokenizer constructor may do this for us |
246 | if (url == null) { |
247 | return 0; |
248 | } |
249 | |
250 | if (urlTokenizer == null) { |
251 | return 0; |
252 | } |
253 | |
254 | try { |
255 | String jdbc = urlTokenizer.nextToken(":"); |
256 | if (!jdbc.equals("jdbc")) { |
257 | return 0; |
258 | } |
259 | if (!urlTokenizer.nextToken(":").equals(":")) { |
260 | return 0; // Skip over the first colon in jdbc:derby: |
261 | } |
262 | String dbname = urlTokenizer.nextToken(":"); |
263 | int protocol = 0; |
264 | if (dbname.equals("derby") && (url.indexOf("derby://") != -1)) { |
265 | // For Derby AS need to check for // since jdbc:derby: is also the |
266 | // embedded prefix |
267 | protocol = DERBY_REMOTE_PROTOCOL; |
268 | } else { |
269 | return 0; |
270 | } |
271 | |
272 | if (!urlTokenizer.nextToken(":").equals(":")) { |
273 | return 0; // Skip over the second colon in jdbc:derby: |
274 | } |
275 | |
276 | return protocol; |
277 | } catch (java.util.NoSuchElementException e) { |
278 | return 0; |
279 | } |
280 | } |
281 | |
282 | // tokenize "/server" from URL jdbc:derby://server:port/ |
283 | // returns server name |
284 | private static String tokenizeServerName(java.util.StringTokenizer urlTokenizer, |
285 | String url) throws SqlException { |
286 | try { |
287 | if (!urlTokenizer.nextToken("/").equals("/")) |
288 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
289 | { |
290 | throw new SqlException(null, |
291 | new ClientMessageId(SQLState.MALFORMED_URL), url); |
292 | } |
293 | return urlTokenizer.nextToken("/:"); |
294 | } catch (java.util.NoSuchElementException e) { |
295 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
296 | throw new SqlException(null, |
297 | new ClientMessageId(SQLState.MALFORMED_URL), url); |
298 | } |
299 | } |
300 | |
301 | // tokenize "[:portNumber]/" from URL jdbc:derby://server[:port]/ |
302 | // returns the portNumber or zero if portNumber is not specified. |
303 | private static int tokenizeOptionalPortNumber(java.util.StringTokenizer urlTokenizer, |
304 | String url) throws SqlException { |
305 | try { |
306 | String firstToken = urlTokenizer.nextToken(":/"); |
307 | if (firstToken.equals(":")) { |
308 | String port = urlTokenizer.nextToken("/"); |
309 | if (!urlTokenizer.nextToken("/").equals("/")) { |
310 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
311 | throw new SqlException(null, |
312 | new ClientMessageId(SQLState.MALFORMED_URL), url); |
313 | } |
314 | return Integer.parseInt(port); |
315 | } else if (firstToken.equals("/")) { |
316 | return 0; |
317 | } else { |
318 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
319 | throw new SqlException(null, |
320 | new ClientMessageId(SQLState.MALFORMED_URL), url); |
321 | } |
322 | } catch (java.util.NoSuchElementException e) { |
323 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
324 | throw new SqlException(null, |
325 | new ClientMessageId(SQLState.MALFORMED_URL), url, e); |
326 | } |
327 | } |
328 | |
329 | //return database name |
330 | private static String tokenizeDatabase(java.util.StringTokenizer urlTokenizer, |
331 | String url) throws SqlException { |
332 | try { |
333 | // DERBY-618 - database name can contain spaces in the path |
334 | String databaseName = urlTokenizer.nextToken("\t\n\r\f;"); |
335 | return databaseName; |
336 | } catch (java.util.NoSuchElementException e) { |
337 | // A null log writer is passed, because jdbc 1 sqlexceptions are automatically traced |
338 | throw new SqlException(null, |
339 | new ClientMessageId(SQLState.MALFORMED_URL), url, e); |
340 | } |
341 | } |
342 | |
343 | private static java.util.Properties tokenizeURLProperties(String url, |
344 | java.util.Properties properties) |
345 | throws SqlException { |
346 | String attributeString = null; |
347 | int attributeIndex = -1; |
348 | |
349 | if ((url != null) && |
350 | ((attributeIndex = url.indexOf(";")) != -1)) { |
351 | attributeString = url.substring(attributeIndex); |
352 | } |
353 | return ClientDataSource.tokenizeAttributes(attributeString, properties); |
354 | } |
355 | |
356 | /** |
357 | *This method returns an Implementation |
358 | *of ClientJDBCObjectFactory depending on |
359 | *VM under use |
360 | *Currently it returns either |
361 | *ClientJDBCObjectFactoryImpl |
362 | *(or) |
363 | *ClientJDBCObjectFactoryImpl40 |
364 | */ |
365 | |
366 | public static ClientJDBCObjectFactory getFactory() { |
367 | if(factoryObject!=null) |
368 | return factoryObject; |
369 | if(Configuration.supportsJDBC40()) { |
370 | factoryObject = createJDBC40FactoryImpl(); |
371 | } else { |
372 | factoryObject = createDefaultFactoryImpl(); |
373 | } |
374 | return factoryObject; |
375 | } |
376 | |
377 | /** |
378 | *Returns an instance of the ClientJDBCObjectFactoryImpl class |
379 | */ |
380 | private static ClientJDBCObjectFactory createDefaultFactoryImpl() { |
381 | return new ClientJDBCObjectFactoryImpl(); |
382 | } |
383 | |
384 | /** |
385 | *Returns an instance of the ClientJDBCObjectFactoryImpl40 class |
386 | *If a ClassNotFoundException occurs then it returns an |
387 | *instance of ClientJDBCObjectFactoryImpl |
388 | * |
389 | *If a future version of JDBC comes then |
390 | *a similar method would be added say createJDBCXXFactoryImpl |
391 | *in which if the class is not found then it would |
392 | *return the lower version thus having a sort of cascading effect |
393 | *until it gets a valid instance |
394 | */ |
395 | |
396 | private static ClientJDBCObjectFactory createJDBC40FactoryImpl() { |
397 | final String factoryName = |
398 | "org.apache.derby.client.net.ClientJDBCObjectFactoryImpl40"; |
399 | try { |
400 | return (ClientJDBCObjectFactory) |
401 | Class.forName(factoryName).newInstance(); |
402 | } catch (ClassNotFoundException cnfe) { |
403 | return createDefaultFactoryImpl(); |
404 | } catch (InstantiationException ie) { |
405 | return createDefaultFactoryImpl(); |
406 | } catch (IllegalAccessException iae) { |
407 | return createDefaultFactoryImpl(); |
408 | } |
409 | } |
410 | } |
411 | |
412 | |
413 | |