001: /*
002:
003: Derby - Class org.apache.derby.jdbc.InternalDriver
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 org.apache.derby.iapi.reference.Attribute;
025: import org.apache.derby.iapi.reference.SQLState;
026: import org.apache.derby.iapi.reference.MessageId;
027: import org.apache.derby.iapi.services.io.FormatableProperties;
028:
029: import org.apache.derby.iapi.jdbc.ConnectionContext;
030:
031: import org.apache.derby.iapi.services.monitor.ModuleControl;
032: import org.apache.derby.iapi.services.monitor.Monitor;
033: import org.apache.derby.iapi.services.context.ContextService;
034: import org.apache.derby.iapi.services.context.ContextManager;
035: import org.apache.derby.iapi.services.sanity.SanityManager;
036: import org.apache.derby.iapi.error.StandardException;
037: import org.apache.derby.iapi.services.i18n.MessageService;
038:
039: import org.apache.derby.iapi.sql.ResultSet;
040:
041: import org.apache.derby.iapi.jdbc.AuthenticationService;
042: import org.apache.derby.iapi.sql.ResultColumnDescriptor;
043:
044: import org.apache.derby.impl.jdbc.*;
045:
046: import java.sql.Connection;
047: import java.sql.DatabaseMetaData;
048: import java.sql.SQLException;
049:
050: import java.util.Properties;
051: import java.util.StringTokenizer;
052:
053: /**
054: Abstract factory class and api for JDBC objects.
055: @author djd
056: */
057:
058: public abstract class InternalDriver implements ModuleControl {
059:
060: private static final Object syncMe = new Object();
061: private static InternalDriver activeDriver;
062:
063: protected boolean active;
064: private ContextService contextServiceFactory;
065: private AuthenticationService authenticationService;
066:
067: public static final InternalDriver activeDriver() {
068: return activeDriver;
069: }
070:
071: public InternalDriver() {
072: contextServiceFactory = ContextService.getFactory();
073: }
074:
075: /*
076: ** Methods from ModuleControl
077: */
078:
079: public void boot(boolean create, Properties properties)
080: throws StandardException {
081:
082: synchronized (InternalDriver.syncMe) {
083: InternalDriver.activeDriver = this ;
084: }
085:
086: active = true;
087: }
088:
089: public void stop() {
090:
091: synchronized (InternalDriver.syncMe) {
092: InternalDriver.activeDriver = null;
093: }
094:
095: active = false;
096:
097: contextServiceFactory = null;
098: }
099:
100: /*
101: ** Methods from java.sql.Driver
102: */
103: public boolean acceptsURL(String url) {
104: return active && embeddedDriverAcceptsURL(url);
105: }
106:
107: /*
108: ** This method can be called by AutoloadedDriver so that we
109: ** don't accidentally boot Derby while answering the question "Can
110: ** you handle this URL?"
111: */
112: public static boolean embeddedDriverAcceptsURL(String url) {
113: return
114: // need to reject network driver's URL's
115: !url.startsWith(Attribute.JCC_PROTOCOL)
116: && !url.startsWith(Attribute.DNC_PROTOCOL)
117: && (url.startsWith(Attribute.PROTOCOL) || url
118: .equals(Attribute.SQLJ_NESTED));
119:
120: }
121:
122: public Connection connect(String url, Properties info)
123: throws SQLException {
124: if (!acceptsURL(url)) {
125: return null;
126: }
127:
128: /**
129: * If we are below the low memory watermark for obtaining
130: * a connection, then don't even try. Just throw an exception.
131: */
132: if (EmbedConnection.memoryState.isLowMemory()) {
133: throw EmbedConnection.NO_MEM;
134: }
135:
136: /*
137: ** A url "jdbc:default:connection" means get the current
138: ** connection. From within a method called from JSQL, the
139: ** "current" connection is the one that is running the
140: ** JSQL statement containing the method call.
141: */
142: boolean current = url.equals(Attribute.SQLJ_NESTED);
143:
144: /* If jdbc:default:connection, see if user already has a
145: * connection. All connection attributes are ignored.
146: */
147: if (current) {
148:
149: ConnectionContext connContext = getConnectionContext();
150:
151: if (connContext != null) {
152:
153: return connContext.getNestedConnection(false);
154:
155: }
156: // there is no cloudscape connection, so
157: // return null, as we are not the driver to handle this
158: return null;
159: }
160:
161: // convert the ;name=value attributes in the URL into
162: // properties.
163: FormatableProperties finfo = null;
164:
165: try {
166:
167: finfo = getAttributes(url, info);
168: info = null; // ensure we don't use this reference directly again.
169:
170: /*
171: ** A property "shutdown=true" means shut the system or database down
172: */
173: boolean shutdown = Boolean.valueOf(
174: finfo.getProperty(Attribute.SHUTDOWN_ATTR))
175: .booleanValue();
176:
177: if (shutdown) {
178:
179: // If we are shutting down the system don't attempt to create
180: // a connection; but we validate users credentials if we have to.
181: // In case of datbase shutdown, we ask the database authentication
182: // service to authenticate the user. If it is a system shutdown,
183: // then we ask the Driver to do the authentication.
184: //
185: if (InternalDriver.getDatabaseName(url, finfo).length() == 0) {
186: //
187: // We need to authenticate the user if authentication is
188: // ON. Note that this is a system shutdown.
189: // check that we do have a authentication service
190: // it is _always_ expected.
191: if (this .getAuthenticationService() == null)
192: throw Util
193: .generateCsSQLException(
194: SQLState.LOGIN_FAILED,
195: MessageService
196: .getTextMessage(MessageId.AUTH_NO_SERVICE_FOR_SYSTEM));
197:
198: if (!this .getAuthenticationService().authenticate(
199: (String) null, finfo)) {
200:
201: // not a valid user
202: throw Util
203: .generateCsSQLException(
204: SQLState.LOGIN_FAILED,
205: MessageService
206: .getTextMessage(MessageId.AUTH_INVALID));
207: }
208:
209: Monitor.getMonitor().shutdown();
210: throw Util
211: .generateCsSQLException(SQLState.CLOUDSCAPE_SYSTEM_SHUTDOWN);
212: }
213: }
214:
215: EmbedConnection conn = getNewEmbedConnection(url, finfo);
216:
217: // if this is not the correct driver a EmbedConnection
218: // object is returned in the closed state.
219: if (conn.isClosed()) {
220: return null;
221: }
222:
223: return conn;
224: } catch (OutOfMemoryError noMemory) {
225: EmbedConnection.memoryState.setLowMemory();
226: throw EmbedConnection.NO_MEM;
227: } finally {
228: // break any link with the user's Properties set.
229: if (finfo != null)
230: finfo.clearDefaults();
231: }
232: }
233:
234: public int getMajorVersion() {
235: return Monitor.getMonitor().getEngineVersion()
236: .getMajorVersion();
237: }
238:
239: public int getMinorVersion() {
240: return Monitor.getMonitor().getEngineVersion()
241: .getMinorVersion();
242: }
243:
244: public boolean jdbcCompliant() {
245: return true;
246: }
247:
248: /*
249: ** URL manipulation
250: */
251:
252: /**
253: Convert all the attributes in the url into properties and
254: combine them with the set provided.
255: <BR>
256: If the caller passed in a set of attributes (info != null)
257: then we set that up as the default of the returned property
258: set as the user's set. This means we can easily break the link
259: with the user's set, ensuring that we don't hang onto the users object.
260: It also means that we don't add our attributes into the user's
261: own property object.
262:
263: @exception SQLException thrown if URL form bad
264: */
265: protected FormatableProperties getAttributes(String url,
266: Properties info) throws SQLException {
267:
268: // We use FormatableProperties here to take advantage
269: // of the clearDefaults, method.
270: FormatableProperties finfo = new FormatableProperties(info);
271: info = null; // ensure we don't use this reference directly again.
272:
273: StringTokenizer st = new StringTokenizer(url, ";");
274: st.nextToken(); // skip the first part of the url
275:
276: while (st.hasMoreTokens()) {
277:
278: String v = st.nextToken();
279:
280: int eqPos = v.indexOf('=');
281: if (eqPos == -1)
282: throw Util.generateCsSQLException(
283: SQLState.MALFORMED_URL, url);
284:
285: //if (eqPos != v.lastIndexOf('='))
286: // throw Util.malformedURL(url);
287:
288: finfo.put((v.substring(0, eqPos)).trim(), (v
289: .substring(eqPos + 1)).trim());
290: }
291:
292: // now validate any attributes we can
293: //
294: // Boolean attributes -
295: // dataEncryption,create,createSource,convertToSource,shutdown,upgrade,current
296:
297: checkBoolean(finfo, Attribute.DATA_ENCRYPTION);
298: checkBoolean(finfo, Attribute.CREATE_ATTR);
299: checkBoolean(finfo, Attribute.SHUTDOWN_ATTR);
300: checkBoolean(finfo, Attribute.UPGRADE_ATTR);
301:
302: return finfo;
303: }
304:
305: private static void checkBoolean(Properties set, String attribute)
306: throws SQLException {
307: final String[] booleanChoices = { "true", "false" };
308: checkEnumeration(set, attribute, booleanChoices);
309: }
310:
311: private static void checkEnumeration(Properties set,
312: String attribute, String[] choices) throws SQLException {
313: String value = set.getProperty(attribute);
314: if (value == null)
315: return;
316:
317: for (int i = 0; i < choices.length; i++) {
318: if (value.toUpperCase(java.util.Locale.ENGLISH).equals(
319: choices[i].toUpperCase(java.util.Locale.ENGLISH)))
320: return;
321: }
322:
323: // The attribute value is invalid. Construct a string giving the choices for
324: // display in the error message.
325: String choicesStr = "{";
326: for (int i = 0; i < choices.length; i++) {
327: if (i > 0)
328: choicesStr += "|";
329: choicesStr += choices[i];
330: }
331:
332: throw Util.generateCsSQLException(SQLState.INVALID_ATTRIBUTE,
333: attribute, value, choicesStr + "}");
334: }
335:
336: /**
337: Get the database name from the url.
338: Copes with three forms
339:
340: jdbc:derby:dbname
341: jdbc:derby:dbname;...
342: jdbc:derby:;subname=dbname
343:
344: @param url The url being used for the connection
345: @param info The properties set being used for the connection, must include
346: the properties derived from the attributes in the url
347:
348: @return a String containing the database name or an empty string ("") if
349: no database name is present in the URL.
350: */
351: public static String getDatabaseName(String url, Properties info) {
352:
353: if (url.equals(Attribute.SQLJ_NESTED)) {
354: return "";
355: }
356:
357: // skip the jdbc:derby:
358: int attributeStart = url.indexOf(';');
359: String dbname;
360: if (attributeStart == -1)
361: dbname = url.substring(Attribute.PROTOCOL.length());
362: else
363: dbname = url.substring(Attribute.PROTOCOL.length(),
364: attributeStart);
365:
366: // For security reasons we rely on here an non-null string being
367: // taken as the database name, before the databaseName connection
368: // attribute. Specifically, even if dbname is blank we still we
369: // to use it rather than the connection attribute, even though
370: // it will end up, after the trim, as a zero-length string.
371: // See EmbeddedDataSource.update()
372:
373: if (dbname.length() == 0) {
374: if (info != null)
375: dbname = info
376: .getProperty(Attribute.DBNAME_ATTR, dbname);
377: }
378: // Beetle 4653 - trim database name to remove blanks that might make a difference on finding the database
379: // on unix platforms
380: dbname = dbname.trim();
381:
382: return dbname;
383: }
384:
385: public final ContextService getContextServiceFactory() {
386: return contextServiceFactory;
387: }
388:
389: // returns the authenticationService handle
390: public AuthenticationService getAuthenticationService() {
391: //
392: // If authenticationService handle not cached in yet, then
393: // ask the monitor to find it for us and set it here in its
394: // attribute.
395: //
396: if (this .authenticationService == null) {
397: this .authenticationService = (AuthenticationService) Monitor
398: .findService(AuthenticationService.MODULE,
399: "authentication");
400: }
401:
402: // We should have a Authentication Service (always)
403: //
404: if (SanityManager.DEBUG) {
405: SanityManager
406: .ASSERT(this .authenticationService != null,
407: "Unexpected - There is no valid authentication service!");
408: }
409: return this .authenticationService;
410: }
411:
412: /*
413: Methods to be overloaded in sub-implementations such as
414: a tracing driver.
415: */
416: protected abstract EmbedConnection getNewEmbedConnection(
417: String url, Properties info) throws SQLException;
418:
419: private ConnectionContext getConnectionContext() {
420:
421: /*
422: ** The current connection is the one in the current
423: ** connection context, so get the context.
424: */
425: ContextManager cm = getCurrentContextManager();
426:
427: ConnectionContext localCC = null;
428:
429: /*
430: cm is null the very first time, and whenever
431: we aren't actually nested.
432: */
433: if (cm != null) {
434: localCC = (ConnectionContext) (cm
435: .getContext(ConnectionContext.CONTEXT_ID));
436: }
437:
438: return localCC;
439: }
440:
441: private ContextManager getCurrentContextManager() {
442: return getContextServiceFactory().getCurrentContextManager();
443: }
444:
445: /**
446: Return true if this driver is active. Package private method.
447: */
448: public boolean isActive() {
449: return active;
450: }
451:
452: /**
453: * Get a new nested connection.
454: *
455: * @param conn The EmbedConnection.
456: *
457: * @return A nested connection object.
458: *
459: */
460: public abstract Connection getNewNestedConnection(
461: EmbedConnection conn);
462:
463: /*
464: ** methods to be overridden by subimplementations wishing to insert
465: ** their classes into the mix.
466: */
467:
468: public java.sql.Statement newEmbedStatement(EmbedConnection conn,
469: boolean forMetaData, int resultSetType,
470: int resultSetConcurrency, int resultSetHoldability) {
471: return new EmbedStatement(conn, forMetaData, resultSetType,
472: resultSetConcurrency, resultSetHoldability);
473: }
474:
475: /**
476: @exception SQLException if fails to create statement
477: */
478: public abstract java.sql.PreparedStatement newEmbedPreparedStatement(
479: EmbedConnection conn, String stmt, boolean forMetaData,
480: int resultSetType, int resultSetConcurrency,
481: int resultSetHoldability, int autoGeneratedKeys,
482: int[] columnIndexes, String[] columnNames)
483: throws SQLException;
484:
485: /**
486: @exception SQLException if fails to create statement
487: */
488: public abstract java.sql.CallableStatement newEmbedCallableStatement(
489: EmbedConnection conn, String stmt, int resultSetType,
490: int resultSetConcurrency, int resultSetHoldability)
491: throws SQLException;
492:
493: /**
494: * Return a new java.sql.DatabaseMetaData instance for this implementation.
495: @exception SQLException on failure to create.
496: */
497: public DatabaseMetaData newEmbedDatabaseMetaData(
498: EmbedConnection conn, String dbname) throws SQLException {
499: return new EmbedDatabaseMetaData(conn, dbname);
500: }
501:
502: /**
503: * Return a new java.sql.ResultSet instance for this implementation.
504: * @param conn Owning connection
505: * @param results Top level of language result set tree
506: * @param forMetaData Is this for meta-data
507: * @param statement The statement that is creating the SQL ResultSet
508: * @param isAtomic
509: * @return a new java.sql.ResultSet
510: * @throws SQLException
511: */
512: public abstract EmbedResultSet newEmbedResultSet(
513: EmbedConnection conn, ResultSet results,
514: boolean forMetaData, EmbedStatement statement,
515: boolean isAtomic) throws SQLException;
516:
517: /**
518: * Returns a new java.sql.ResultSetMetaData for this implementation
519: *
520: * @param columnInfo a ResultColumnDescriptor that stores information
521: * about the columns in a ResultSet
522: */
523: public EmbedResultSetMetaData newEmbedResultSetMetaData(
524: ResultColumnDescriptor[] columnInfo) {
525: return new EmbedResultSetMetaData(columnInfo);
526: }
527: }
|