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