| /* |
| |
| Derby - Class org.apache.derby.impl.drda.Database |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.impl.drda; |
| |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Properties; |
| import org.apache.derby.iapi.jdbc.EngineConnection; |
| import org.apache.derby.iapi.reference.Attribute; |
| import org.apache.derby.iapi.tools.i18n.LocalizedResource; |
| |
| /** |
| Database stores information about the current database |
| It is used so that a session may have more than one database |
| */ |
| class Database |
| { |
| /* |
| * Considering that we are now doing some fiddling with the db name |
| * it is probably wise to keep dbName and shortDbName private and have |
| * accessors for them. |
| */ |
| private String dbName; // database name |
| private String shortDbName; // database name without attributes |
| String attrString=""; // attribute string |
| protected int securityMechanism; // Security mechanism |
| protected String userId; // User Id |
| protected String password; // password |
| protected String decryptedUserId; // Decrypted User id |
| protected String decryptedPassword; // Decrypted password |
| protected byte[] passwordSubstitute;// password substitute - SECMEC_USRSSBPWD |
| protected boolean rdbAllowUpdates = true; // Database allows updates -default is true |
| protected int accessCount; // Number of times we have tried to |
| // set up access to this database (only 1 |
| // allowed) |
| protected byte[] secTokenIn; // Security token from app requester |
| protected byte[] secTokenOut; // Security token sent to app requester |
| protected byte[] crrtkn; // Correlation token |
| protected String typDefNam; // Type definition name |
| protected int byteOrder; //deduced from typDefNam, save String comparisons |
| protected int ccsidSBC; // Single byte CCSID |
| protected int ccsidDBC; // Double byte CCSID |
| protected int ccsidMBC; // Mixed byte CCSID |
| protected String ccsidSBCEncoding; // Encoding for single byte code page |
| protected String ccsidDBCEncoding; // Encoding for double byte code page |
| protected String ccsidMBCEncoding; // Encoding for mixed byte code page |
| protected boolean RDBUPDRM_sent = false; //We have sent that an update |
| // occurred in this transaction |
| protected boolean sendTRGDFTRT = false; // Send package target default value |
| /** |
| * Connection to the database in the embedded engine. |
| */ |
| private EngineConnection conn; |
| DRDAStatement defaultStatement; // default statement used |
| // for execute imm |
| private DRDAStatement currentStatement; // current statement we are working on |
| /** Hash table for storing statements. */ |
| private Hashtable<Object, DRDAStatement> stmtTable; |
| |
| // constructor |
| /** |
| * Database constructor |
| * |
| * @param dbName database name |
| */ |
| Database (String dbName) |
| { |
| setDatabaseName(dbName); |
| this.stmtTable = new Hashtable<Object, DRDAStatement>(); |
| initializeDefaultStatement(); |
| } |
| |
| /** |
| * Take database name including attributes and set |
| * attrString and shortDbName accordingly. |
| * |
| * @param dbName database name, including attributes. |
| */ |
| public void setDatabaseName(String dbName) { |
| if (dbName != null) |
| { |
| int attrOffset = dbName.indexOf(';'); |
| if (attrOffset != -1) |
| { |
| this.attrString = dbName.substring(attrOffset,dbName.length()); |
| this.shortDbName = dbName.substring(0,attrOffset); |
| } |
| else |
| this.shortDbName = dbName; |
| } |
| |
| this.dbName = dbName; |
| |
| } |
| |
| public String getDatabaseName() { |
| return this.dbName; |
| } |
| |
| public String getShortDbName() { |
| return this.shortDbName; |
| } |
| |
| private void initializeDefaultStatement() |
| { |
| this.defaultStatement = new DRDAStatement(this); |
| } |
| |
| /** |
| * Set connection and create the SQL statement for the default statement |
| * |
| * @param conn Connection |
| * @exception SQLException |
| */ |
| final void setConnection(EngineConnection conn) |
| throws SQLException |
| { |
| if (this.conn != conn) { |
| // Need to drop the pb session data when switching connections |
| pbsd_ = null; |
| } |
| this.conn = conn; |
| if(conn != null) |
| defaultStatement.setStatement(conn); |
| } |
| /** |
| * Get the connection |
| * |
| * @return connection |
| */ |
| final EngineConnection getConnection() |
| { |
| return conn; |
| } |
| /** |
| * Get current DRDA statement |
| * |
| * @return DRDAStatement |
| * @exception SQLException |
| */ |
| protected DRDAStatement getCurrentStatement() |
| { |
| return currentStatement; |
| } |
| /** |
| * Get default statement for use in EXCIMM |
| * |
| * @return DRDAStatement |
| */ |
| protected DRDAStatement getDefaultStatement() |
| { |
| currentStatement = defaultStatement; |
| return defaultStatement; |
| } |
| |
| /** |
| * Get default statement for use in EXCIMM with specified pkgnamcsn |
| * The pkgnamcsn has the encoded isolation level |
| * |
| * @param pkgnamcsn package/ section # for statement |
| * @return DRDAStatement |
| */ |
| protected DRDAStatement getDefaultStatement(Pkgnamcsn pkgnamcsn) |
| { |
| currentStatement = defaultStatement; |
| currentStatement.setPkgnamcsn(pkgnamcsn); |
| return currentStatement; |
| } |
| |
| /** |
| * Get a new DRDA statement and store it in the stmtTable if stortStmt is |
| * true. If possible recycle an existing statement. When the server gets a |
| * new statement with a previously used pkgnamcsn, it means that |
| * client-side statement associated with this pkgnamcsn has been closed. In |
| * this case, server can re-use the DRDAStatement by doing the following: |
| * 1) Retrieve the old DRDAStatement associated with this pkgnamcsn and |
| * close it. |
| * 2) Reset the DRDAStatement state for re-use. |
| * |
| * @param pkgnamcsn Package name and section |
| * @return DRDAStatement |
| */ |
| protected DRDAStatement newDRDAStatement(Pkgnamcsn pkgnamcsn) |
| throws SQLException |
| { |
| DRDAStatement stmt = getDRDAStatement(pkgnamcsn); |
| if (stmt != null) { |
| stmt.close(); |
| stmt.reset(); |
| } |
| else |
| { |
| stmt = new DRDAStatement(this); |
| stmt.setPkgnamcsn(pkgnamcsn); |
| storeStatement(stmt); |
| } |
| return stmt; |
| } |
| |
| /** |
| * Get DRDA statement based on pkgnamcsn |
| * |
| * @param pkgnamcsn - key to access statement |
| * @return DRDAStatement |
| */ |
| protected DRDAStatement getDRDAStatement(Pkgnamcsn pkgnamcsn) { |
| DRDAStatement newStmt = |
| (DRDAStatement) stmtTable.get(pkgnamcsn.getStatementKey()); |
| if (newStmt != null) { |
| currentStatement = newStmt; |
| currentStatement.setCurrentDrdaResultSet(pkgnamcsn); |
| } |
| return newStmt; |
| } |
| |
| /** |
| * Make a new connection using the database name and set |
| * the connection in the database |
| * @param p Properties for connection attributes to pass to connect |
| */ |
| void makeConnection(Properties p) throws SQLException |
| { |
| p.put(Attribute.USERNAME_ATTR, userId); |
| |
| // take care of case of SECMEC_USRIDONL |
| if (password != null) |
| p.put(Attribute.PASSWORD_ATTR, password); |
| |
| // Contract between network server and embedded engine |
| // is that any connection returned implements EngineConnection. |
| EngineConnection conn = (EngineConnection) |
| NetworkServerControlImpl.getDriver().connect(Attribute.PROTOCOL |
| + shortDbName + attrString, p); |
| if (conn != null) { |
| conn.setAutoCommit(false); |
| } |
| setConnection(conn); |
| } |
| |
| /** |
| * This makes a dummy connection to the database in order to |
| * boot and/or create this last one. If database cannot |
| * be found or authentication does not succeed, this will throw |
| * a SQLException which we catch and do nothing. We don't pass a |
| * userid and password here as we don't need to for the purpose |
| * of this method - main goal is to cause the database to be |
| * booted via a dummy connection. |
| */ |
| void makeDummyConnection() |
| { |
| try { |
| // Contract between network server and embedded engine |
| // is that any connection returned implements EngineConnection. |
| EngineConnection conn = (EngineConnection) |
| NetworkServerControlImpl.getDriver().connect(Attribute.PROTOCOL |
| + shortDbName + attrString, new Properties()); |
| |
| // If we succeeded in getting a connection, well just close it |
| if (conn != null) { |
| conn.close(); |
| } |
| } catch (SQLException se) {} // Simply do nothing |
| } |
| |
| // Create string to pass to DataSource.setConnectionAttributes |
| String appendAttrString(Properties p) |
| { |
| if (p == null) |
| return null; |
| |
| Enumeration pKeys = p.propertyNames(); |
| while (pKeys.hasMoreElements()) |
| { |
| String key = (String) pKeys.nextElement(); |
| attrString +=";" + key + "=" + p.getProperty(key); |
| } |
| |
| return attrString; |
| } |
| |
| /** |
| * Store DRDA prepared statement |
| * @param stmt DRDA prepared statement |
| */ |
| protected void storeStatement(DRDAStatement stmt) throws SQLException |
| { |
| stmtTable.put(stmt.getPkgnamcsn().getStatementKey(), stmt); |
| } |
| |
| protected void removeStatement(DRDAStatement stmt) throws SQLException |
| { |
| stmtTable.remove(stmt.getPkgnamcsn().getStatementKey()); |
| stmt.close(); |
| } |
| |
| /** |
| * Make statement the current statement |
| * @param stmt |
| * |
| */ |
| |
| protected void setCurrentStatement(DRDAStatement stmt) |
| { |
| currentStatement = stmt; |
| } |
| |
| |
| protected void commit() throws SQLException |
| { |
| |
| if (conn != null) |
| conn.commit(); |
| } |
| |
| protected void rollback() throws SQLException |
| { |
| |
| if (conn != null) |
| conn.rollback(); |
| } |
| /** |
| * Database close does following cleanup tasks |
| * 1)Rollback any pending transaction on the Connection object (except |
| * for a global-XA Connection obejct) before closing the Connection. |
| * Without the rollback, the Connection close will result into an |
| * exception if there is a pending transaction on that Connection. |
| * 2)Clean up the statement table |
| * @throws SQLException on conn.close() error to be handled in DRDAConnThread. |
| */ |
| protected void close() throws SQLException |
| { |
| |
| try { |
| if (stmtTable != null) |
| { |
| for (Enumeration e = stmtTable.elements() ; e.hasMoreElements() ;) |
| { |
| ((DRDAStatement) e.nextElement()).close(); |
| } |
| |
| } |
| if (defaultStatement != null) |
| defaultStatement.close(); |
| if ((conn != null) && !conn.isClosed()) |
| { |
| //rollback all the pending transactions except global XA trans |
| if (! conn.isInGlobalTransaction()) |
| { |
| conn.rollback(); |
| } |
| conn.close(); |
| } |
| } |
| finally { |
| conn = null; |
| currentStatement = null; |
| defaultStatement = null; |
| stmtTable=null; |
| } |
| } |
| |
| final void setDrdaID(String drdaID) |
| { |
| if (conn != null) |
| conn.setDrdaID(drdaID); |
| } |
| |
| /** |
| * Set the internal isolation level to use for preparing statements. |
| * Subsequent prepares will use this isoalation level |
| * @param level internal isolation level |
| * |
| * @throws SQLException |
| * @see EngineConnection#setPrepareIsolation |
| * |
| */ |
| final void setPrepareIsolation(int level) throws SQLException |
| { |
| conn.setPrepareIsolation(level); |
| } |
| |
| final int getPrepareIsolation() throws SQLException |
| { |
| return conn.getPrepareIsolation(); |
| } |
| |
| protected String buildRuntimeInfo(String indent, LocalizedResource localLangUtil) |
| { |
| // DERBY-6714: stmtTable can be null if the session gets closed |
| // while we are constructing the runtime info. Create a local copy |
| // and check for null before accessing it. |
| Hashtable<Object, DRDAStatement> statements = stmtTable; |
| |
| String s = indent + |
| localLangUtil.getTextMessage("DRDA_RuntimeInfoDatabase.I") + |
| dbName + "\n" + |
| localLangUtil.getTextMessage("DRDA_RuntimeInfoUser.I") + |
| userId + "\n" + |
| localLangUtil.getTextMessage("DRDA_RuntimeInfoNumStatements.I") + |
| (statements == null ? 0 : statements.size()) + "\n"; |
| s += localLangUtil.getTextMessage("DRDA_RuntimeInfoPreparedStatementHeader.I"); |
| |
| if (statements != null) { |
| for (Enumeration e = statements.elements(); e.hasMoreElements(); ) |
| { |
| s += ((DRDAStatement) e.nextElement()).toDebugString(indent |
| +"\t") +"\n"; |
| } |
| } |
| |
| return s; |
| } |
| |
| private boolean locatorSupport = false; |
| private boolean locatorSupportChecked = false; |
| |
| /** |
| * Checks whether database can support locators. This is done by |
| * checking whether one of the stored procedures needed for |
| * locators exists. (If the database has been soft-upgraded from |
| * an earlier version, the procedures will not exist). |
| * |
| * @throws SQLException if metadata call fails |
| * @return <code>true</code> if locators are supported, |
| * <code>false</code> otherwise |
| */ |
| boolean supportsLocator() throws SQLException |
| { |
| if (!locatorSupportChecked) { |
| // Check if locator procedures exist |
| ResultSet rs = getConnection().getMetaData() |
| .getProcedures(null, "SYSIBM", "BLOBTRUNCATE"); |
| locatorSupport = rs.next(); // True if procedure exists |
| rs.close(); |
| locatorSupportChecked = true; |
| } |
| |
| return locatorSupport; |
| } |
| |
| |
| /** |
| * This method resets the state of this Database object so that it can |
| * be re-used. |
| * Note: currently this method resets the variables related to security |
| * mechanisms that have been investigated as needing a reset. |
| * TODO: Investigate what all variables in this class need to be |
| * reset when this database object is re-used on a connection pooling or |
| * transaction pooling. see DRDAConnThread.parseACCSEC (CodePoint.RDBNAM) |
| * where database object is re-used on a connection reset. |
| */ |
| public void reset() |
| { |
| // Reset variables for connection re-use. Currently only takes care |
| // of reset the variables that affect EUSRIDPWD and USRSSBPWD |
| // security mechanisms. (DERBY-1080) |
| decryptedUserId = null; |
| decryptedPassword = null; |
| passwordSubstitute = null; |
| secTokenIn = null; |
| secTokenOut = null; |
| userId = null; |
| password = null; |
| securityMechanism = 0; |
| } |
| |
| /** |
| * Piggy-backed session data. Null if no piggy-backing |
| * has happened yet. Lazy initialization is acceptable since the client's |
| * cache initially is empty so that any request made prior to the first |
| * round of piggy-backing will trigger an actual request to the server. |
| */ |
| private PiggyBackedSessionData pbsd_ = null; |
| |
| /** |
| * Get a reference (handle) to the PiggyBackedSessionData object. Null will |
| * be returned either if Database.conn is not a valid connection, or if the |
| * create argument is false and no object has yet been created. |
| * @param createOnDemand if true create the PiggyBackedSessionData on demand |
| * @return a reference to the PBSD object or null |
| * @throws java.sql.SQLException |
| */ |
| public PiggyBackedSessionData getPiggyBackedSessionData( |
| boolean createOnDemand) throws SQLException { |
| pbsd_ = PiggyBackedSessionData.getInstance(pbsd_, conn, createOnDemand); |
| return pbsd_; |
| } |
| } |