0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package com.sun.rave.faces.data;
0043:
0044: import java.beans.Beans;
0045: import java.sql.Connection;
0046: import java.sql.DatabaseMetaData;
0047: import java.sql.PreparedStatement;
0048: import java.sql.ResultSet;
0049: import java.sql.ResultSetMetaData;
0050: import java.sql.SQLException;
0051: import java.sql.Types;
0052: import java.util.ArrayList;
0053: import java.util.Iterator;
0054: import java.util.List;
0055: import java.util.Map;
0056: import javax.faces.FacesException;
0057: import javax.faces.context.FacesContext;
0058: import javax.faces.model.DataModel;
0059: import javax.faces.model.DataModelEvent;
0060: import javax.faces.model.DataModelListener;
0061: import javax.sql.RowSet;
0062: import javax.sql.RowSetEvent;
0063: import javax.sql.RowSetListener;
0064: import com.sun.rave.faces.util.ComponentBundle;
0065:
0066: /**
0067: * <p>Runtime implementation of <code>javax.faces.model.DataModel</code>
0068: * that caches retrieved database data in memory (in a session scope
0069: * attribute of type <code>DataCache</code>), even when the underlying
0070: * rowset is closed.</p>
0071: *
0072: * @author craigmcc
0073: */
0074: public class RowSetDataModel extends DataModel {
0075:
0076: // ------------------------------------------------------------ Constructors
0077:
0078: /**
0079: * <p>Create a new {@link RowSetDataModel} instance not connected to
0080: * any underlying <code>RowSet</code>.</p>
0081: */
0082: public RowSetDataModel() {
0083:
0084: this (null);
0085:
0086: }
0087:
0088: /**
0089: * <p>Create a new {@link RowSetDataModel} instance wrapping the
0090: * specified <code>RowSet</code>.</p>
0091: *
0092: * @param rowSet <code>RowSet</code> to be mapped
0093: */
0094: public RowSetDataModel(RowSet rowSet) {
0095:
0096: super ();
0097: setWrappedData(rowSet);
0098:
0099: }
0100:
0101: // ------------------------------------------------------ Instance Variables
0102:
0103: /**
0104: * <p>The number of fake data rows we will compose at
0105: * design time.</p>
0106: */
0107: private static final int DESIGN_TIME_ROWS = 5;
0108:
0109: /**
0110: * <p>Localization resources for this package.</p>
0111: */
0112: private static final ComponentBundle bundle = ComponentBundle
0113: .getBundle(RowSetDataModel.class);
0114:
0115: /**
0116: * <p>List of column names for the <code>RowSet</code> we are
0117: * currently connected to (if any).</p>
0118: */
0119: private List columnNames = new ArrayList();
0120:
0121: /**
0122: * <p>identifies whether or not the underlying database can support
0123: * the conditonal clause used to handle nulls
0124: * (see compooseUpdateAndDeleteStatements).</p>
0125: */
0126: private boolean useConditionalWhereClause = true;
0127:
0128: /**
0129: * <p>List of column SQL types for the <code>RowSet</code> we are
0130: * currently connected to (if any).</p>
0131: */
0132: private List columnTypes = new ArrayList();
0133:
0134: /**
0135: * <p>List of column Java types (<code>Class</code> objects)
0136: * for the <code>RowSet</code> we are
0137: * currently connected to (if any).</p>
0138: */
0139: private List columnJavaTypes = new ArrayList();
0140:
0141: /**
0142: * <p>The {@link DataCache} containing our cached row and
0143: * column information. A new instance (saved in session scope)
0144: * is created on demand if not already present.</p>
0145: */
0146: private DataCache dataCache = null;
0147:
0148: /**
0149: * <p>The session attribute key under which our
0150: * <code>DataCache</code> instance will be stored.
0151: */
0152: private String dataCacheKey = null;
0153:
0154: /**
0155: * <p><code>RowSetListener</code> for significant events
0156: * on the <code>RowSet</code> we are currently connected to (if any).</p>
0157: */
0158: private RowSetListener listener = new CachedRowSetListener();
0159:
0160: /**
0161: * <p>The <code>ResultSetMetaData</code> associated with the
0162: * <code>RowSet</code> we are currently connected to (if any).</p>
0163: */
0164: private ResultSetMetaData metadata = null;
0165:
0166: /**
0167: * <p>The zero relative index of the currently positioned row,
0168: * or -1 if we are not positioned on a row.</p>
0169: */
0170: private int rowIndex;
0171:
0172: /**
0173: * <p>The <code>RowSet</code> that we are currently connected to
0174: * (if any).</p>
0175: */
0176: private RowSet rowSet = null;
0177:
0178: /**
0179: * <p>The schema name on which we will perform updates when committing
0180: * changes to the database. If specified, this will be included in the
0181: * UPDATE statement synthesized by the <code>commit()</code> method.</p>
0182: */
0183: private String schemaName = null;
0184:
0185: /**
0186: * <p>List of schema names for the columns of the <code>RowSet</code> we are
0187: * currently connected to (if any).</p>
0188: */
0189: private List schemaNames = new ArrayList();
0190:
0191: /**
0192: * <p>The table name on which we will perform updates when committing
0193: * changes to the database.</p>
0194: */
0195: private String tableName = null;
0196:
0197: /**
0198: * <p>List of table names for the columns of the <code>RowSet</code> we are
0199: * currently connected to (if any).</p>
0200: */
0201: private List tableNames = new ArrayList();
0202:
0203: // -------------------------------------------------------------- Properties
0204:
0205: /**
0206: * <p>Return the {@link DataCache} containing our cached row and
0207: * column data, creating one if necessary.</p>
0208: */
0209: public DataCache getDataCache() {
0210:
0211: // Create a new cache if one does not already exist
0212: Map sessionMap = FacesContext.getCurrentInstance()
0213: .getExternalContext().getSessionMap();
0214: dataCache = (DataCache) sessionMap.get(getDataCacheKey());
0215: if (dataCache == null) {
0216: dataCache = new DataCache();
0217: sessionMap.put(getDataCacheKey(), dataCache);
0218: }
0219: return dataCache;
0220:
0221: }
0222:
0223: /**
0224: * <p>Return the session attribute key under which our {@link DataCache}
0225: * instance will be stored.
0226: */
0227: public String getDataCacheKey() {
0228:
0229: return this .dataCacheKey;
0230:
0231: }
0232:
0233: /**
0234: * <p>Set the session attribute key under which our
0235: * {@link DataCache} instance will be stored.
0236: *
0237: * @param dataCacheKey The new key
0238: */
0239: public void setDataCacheKey(String dataCacheKey) {
0240:
0241: this .dataCacheKey = dataCacheKey;
0242:
0243: }
0244:
0245: /**
0246: * <p>Return the <code>RowSet</code> we are connected with,
0247: * if any; otherwise, return <code>null</code>. This is a
0248: * type=safe alias for <code>getWrappedData()</code>.</p>
0249: */
0250: public RowSet getRowSet() {
0251:
0252: return ((RowSet) getWrappedData());
0253:
0254: }
0255:
0256: /**
0257: * <p>Set the <code>RowSet</code> we are connected with,
0258: * or pass <code>null</code> to disconnect. This is a
0259: * type-safe alias for <code>setWrappedData()</code>.</p>
0260: *
0261: * @param rowSet The <code>RowSet</code> we are connected to,
0262: * or <code>null</code> to disconnect
0263: */
0264: public void setRowSet(RowSet rowSet) {
0265:
0266: setWrappedData(rowSet);
0267:
0268: }
0269:
0270: /**
0271: * <p>Return the name of the database schema containing the table
0272: * we will update when <code>commit()</code> is called.</p>
0273: */
0274: public String getSchemaName() {
0275:
0276: return this .schemaName;
0277:
0278: }
0279:
0280: /**
0281: * <p>Set the name of the database schema containing the table
0282: * we will update when <code>commit()</code> is called.</p>
0283: *
0284: * @param schemaName The schema name to be updated
0285: */
0286: public void setSchemaName(String schemaName) {
0287:
0288: this .schemaName = schemaName;
0289:
0290: }
0291:
0292: /**
0293: * <p>Return the name of the database table we will update when
0294: * <code>commit()</code> is called.</p>
0295: */
0296: public String getTableName() {
0297:
0298: return this .tableName;
0299:
0300: }
0301:
0302: /**
0303: * <p>Set the name of the database table we will update when
0304: * <code>commit()</code> is called.</p>
0305: *
0306: * @param tableName The table name to be updated
0307: */
0308: public void setTableName(String tableName) {
0309:
0310: this .tableName = tableName;
0311:
0312: }
0313:
0314: // --------------------------------------------------------- Public Methods
0315:
0316: /**
0317: * <p>Clear any cached row-specific data. This method may be called by
0318: * application logic when it is known that the next page to be processed
0319: * will not involve the <code>RowSet</code> we are connected to, or
0320: * when the application wants to refresh the cached data to reflect
0321: * any changes on the underlying database contents.</p>
0322: *
0323: * <p><strong>NOTE</strong> - Calling <code>execute()</code> on this
0324: * <code>RowSetDataModel</code> will implicitly call <code>clear()</code>
0325: * for you.</p>
0326: */
0327: public void clear() {
0328:
0329: // System.out.println("RSDM: clear()");
0330: getDataCache().clear();
0331:
0332: }
0333:
0334: /**
0335: * <p>Push any deleted or updated cached rows to the specified table,
0336: * then call <code>commit()</code> on the JDBC <code>Connection</code>
0337: * underlying our current <code>RowSet</code>, as well as our associated
0338: * <code>DataCache</code>. If any <code>SQLException</code> occurs, call
0339: * <code>rollback()</code> on the <code>Connection()</code> and
0340: * <code>reset()</code> on the <code>DataCache</code>.</p>
0341: *
0342: * @exception IllegalArgumentException if the <code>tableName</code>
0343: * property has not yet been set
0344: * @exception IllegalStateException if this method is called when
0345: * not connected to an underlying rowset
0346: * @exception SQLException if an error occurs while committing
0347: */
0348: public void commit() throws SQLException {
0349:
0350: // System.out.println("RSDM: commit()");
0351:
0352: // Validate our preconditions
0353: if (tableName == null) {
0354: throw new IllegalArgumentException(bundle
0355: .getMessage("noTableName")); // NOI18N
0356: }
0357: if (!connected()) {
0358: throw new IllegalStateException(bundle
0359: .getMessage("notConnected")); // NOI18N
0360: }
0361:
0362: // Local variables for resources we will need
0363: Connection conn = null;
0364: SQLException exception = null;
0365: PreparedStatement ustmt = null;
0366: PreparedStatement dstmt = null;
0367: String extracted = null; // Table name extracted from command
0368: List names = null; // Column names extracted from DBMD
0369:
0370: // Acquire the JDBC resources we will need
0371: try {
0372:
0373: // Extract a table name from the command if we do not have
0374: // one (because JDBC metadata did not include it).
0375: if ((getTableName() == null)
0376: || (getTableName().length() < 1)) {
0377: extracted = extract(getRowSet().getCommand());
0378: }
0379:
0380: // Acquire the Connection we will be using
0381: try {
0382: conn = getRowSet().getStatement().getConnection();
0383: } catch (NullPointerException e) {
0384: exception = new SQLException(bundle
0385: .getMessage("noConnection"));
0386: throw new FacesException(exception);
0387: }
0388:
0389: // Retrieve the database metadata for this connection
0390: DatabaseMetaData dbmd = conn.getMetaData();
0391:
0392: // Set useConditionalWhereClause based on driver
0393: String driverName = dbmd.getDriverName();
0394: if (driverName != null && driverName.equals("DB2")) { // NOI18N
0395: useConditionalWhereClause = false;
0396: } else {
0397: useConditionalWhereClause = true;
0398: }
0399:
0400: // Always get column names that match the table, else we have no way
0401: // to throw away columns not in this table (if the driver does not
0402: // provide the tablename in ResultSetMetaData
0403: names = columns(dbmd, (extracted != null) ? extracted
0404: : getTableName(), columnNames);
0405:
0406: String[] statements = composeStatements(extracted, names,
0407: useConditionalWhereClause, null);
0408: ustmt = conn.prepareStatement(statements[0]);
0409: dstmt = conn.prepareStatement(statements[1]);
0410: } catch (SQLException e) {
0411:
0412: exception = e;
0413:
0414: }
0415:
0416: // Scan cached rows, performing deletes and updates as needed
0417: try {
0418:
0419: if (exception == null) {
0420: Iterator keys = getDataCache().iterator();
0421: while (keys.hasNext()) {
0422: Integer key = (Integer) keys.next();
0423: DataCache.Row row = getDataCache().get(
0424: key.intValue());
0425: DataCache.Column columns[] = row.getColumns();
0426: if (row.isDeleted()) {
0427: PreparedStatement tmpStmt = dstmt;
0428: boolean requiresCustomDeleteStatement = false;
0429: int dindex = 1;
0430: if (useConditionalWhereClause) {
0431: // Delete this row in the database
0432: // System.out.println("RSDM: delete " + row);
0433: for (int i = 0; i < columns.length; i++) {
0434: if (match(i, names)) {
0435: // each column is set twice, see composeStatements
0436: tmpStmt.setObject(dindex++,
0437: columns[i].getOriginal(),
0438: columns[i].getSqlType());
0439: tmpStmt.setObject(dindex++,
0440: columns[i].getOriginal(),
0441: columns[i].getSqlType());
0442: }
0443: }
0444: } else {
0445: /*
0446: * First, let's see if we can use the PreparedStatement or not.
0447: * If the original values contain a null, we cannot use it since
0448: * WHERE column-name = null does not work. We'll need to prepare
0449: * another statement that contains column-name IS NULL for all
0450: * columns that are null
0451: */
0452: for (int i = 0; i < columns.length; i++) {
0453: if (match(i, names)) {
0454: if (columns[i].getOriginal() == null) {
0455: requiresCustomDeleteStatement = true;
0456: break;
0457: }
0458: }
0459: }
0460: if (requiresCustomDeleteStatement) {
0461: tmpStmt = conn
0462: .prepareStatement(composeStatements(
0463: extracted, names,
0464: false, columns)[1]);
0465: }
0466: for (int i = 0; i < columns.length; i++) {
0467: if (match(i, names)) {
0468: if (columns[i].getOriginal() != null) {
0469: tmpStmt.setObject(dindex++,
0470: columns[i]
0471: .getOriginal());
0472: }
0473: }
0474: }
0475: }
0476: int dresult = tmpStmt.executeUpdate();
0477: if (requiresCustomDeleteStatement) {
0478: try {
0479: tmpStmt.close();
0480: } catch (SQLException e) {
0481: // We'll do nothing if we can't close the statement
0482: }
0483: }
0484: if (dresult < 1) {
0485: exception = new SQLException(bundle
0486: .getMessage("deleteFailedMissing",
0487: key)); // NOI18N
0488: break;
0489: } else if (dresult > 1) {
0490: exception = new SQLException(bundle
0491: .getMessage("deleteFailedMultiple",
0492: key)); // NOI18N
0493: break;
0494: }
0495: } else if (row.isUpdated()) {
0496: PreparedStatement tmpStmt = ustmt;
0497: boolean requiresCustomUpdateStatement = false;
0498: if (!useConditionalWhereClause) {
0499: /*
0500: * First, let's see if we can use the PreparedStatement or not.
0501: * If the original values contain a null, we cannot use it since
0502: * WHERE column-name = null does not work. We'll need to prepare
0503: * another statement that contains column-name IS NULL for all
0504: * columns that are null
0505: */
0506: for (int i = 0; i < columns.length; i++) {
0507: if (match(i, names)) {
0508: if (columns[i].getOriginal() == null) {
0509: requiresCustomUpdateStatement = true;
0510: break;
0511: }
0512: }
0513: }
0514: if (requiresCustomUpdateStatement) {
0515: tmpStmt = conn
0516: .prepareStatement(composeStatements(
0517: extracted, names,
0518: false, columns)[0]);
0519: }
0520: }
0521: // Update this row in the database
0522: // System.out.println("RSDM: update " + row);
0523: int uindex = 1;
0524: for (int i = 0; i < columns.length; i++) {
0525: if (match(i, names)) {
0526: tmpStmt.setObject(uindex++, columns[i]
0527: .getValue(), columns[i]
0528: .getSqlType());
0529: }
0530: }
0531: // Where clause
0532: if (useConditionalWhereClause) {
0533: for (int i = 0; i < columns.length; i++) {
0534: if (match(i, names)) {
0535: /*
0536: * This is the where clause, so we set every column
0537: * twice, see composeStatements
0538: */
0539: tmpStmt.setObject(uindex++,
0540: columns[i].getOriginal(),
0541: columns[i].getSqlType());
0542: tmpStmt.setObject(uindex++,
0543: columns[i].getOriginal(),
0544: columns[i].getSqlType());
0545: }
0546: }
0547: } else {
0548: /*
0549: * This is the where clause, we only set the property if the
0550: * column is non-null. If it is null, a custom PreparedStatement
0551: * was created with the "<column-name> IS NULL" syntax.
0552: */
0553: for (int i = 0; i < columns.length; i++) {
0554: if (match(i, names)) {
0555: if (columns[i].getOriginal() != null) {
0556: ustmt.setObject(uindex++,
0557: columns[i]
0558: .getOriginal());
0559: }
0560: }
0561: }
0562: }
0563: int uresult = tmpStmt.executeUpdate();
0564: if (requiresCustomUpdateStatement) {
0565: try {
0566: tmpStmt.close();
0567: } catch (SQLException e) {
0568: // We'll do nothing if we can't close the statement
0569: }
0570: }
0571: if (uresult < 1) {
0572: exception = new SQLException(bundle
0573: .getMessage("updateFailedMissing",
0574: key)); // NOI18N
0575: break;
0576: } else if (uresult > 1) {
0577: exception = new SQLException(bundle
0578: .getMessage("updateFailedMultiple",
0579: key)); // NOI18N
0580: break;
0581: }
0582: }
0583: }
0584: }
0585:
0586: } catch (SQLException e) {
0587:
0588: exception = e;
0589:
0590: }
0591:
0592: // Commit on the database and associated cache
0593: try {
0594: if (exception == null) {
0595: conn.commit();
0596: getDataCache().commit();
0597: }
0598: } catch (SQLException e) {
0599: exception = e;
0600: }
0601:
0602: // Roll back if necessary
0603: if (exception != null) {
0604: try {
0605: conn.rollback();
0606: } catch (SQLException e) {
0607: ;
0608: }
0609: getDataCache().reset();
0610: }
0611:
0612: // Free allocated resources
0613: if (ustmt != null) {
0614: try {
0615: ustmt.close();
0616: } catch (SQLException e) {
0617: ;
0618: }
0619: ustmt = null;
0620: }
0621: if (dstmt != null) {
0622: try {
0623: dstmt.close();
0624: } catch (SQLException e) {
0625: ;
0626: }
0627: dstmt = null;
0628: }
0629:
0630: // Rethrow any saved exception
0631: if (exception != null) {
0632: throw exception;
0633: }
0634:
0635: }
0636:
0637: /**
0638: * <p>Clear any cached data, then re-execute the
0639: * query for the rowset we are connected to. <strong>WARNING</strong> -
0640: * this method should <strong>ONLY</strong> be called when you
0641: * have changed the query parameters for your query, or your
0642: * application has performed other database transaction(s) on a
0643: * different page that should be reflected in the query results.</p>
0644: *
0645: * @exception IllegalStateException if this method is called when
0646: * not connected to an underlying rowset
0647: * @exception SQLException if an error occurs executing the rowset
0648: */
0649: public void execute() throws SQLException {
0650:
0651: // System.out.println("RSDM: execute()");
0652:
0653: // Validate our preconditions
0654: if (!connected()) {
0655: throw new IllegalStateException(bundle
0656: .getMessage("notConnected")); // NOI18N
0657: }
0658:
0659: // Call clear() on the underlying cache
0660: getDataCache().clear();
0661:
0662: // Call execute() on the underlying rowset
0663: getRowSet().execute();
0664:
0665: }
0666:
0667: /**
0668: * <p>Reset any deleted or updated values in the cache, so that any cached
0669: * rows no longer appear to have been modified. This method has no
0670: * effect on the connected rowset (if any).</p>
0671: */
0672: public void reset() {
0673:
0674: // System.out.println("RSDM: reset()");
0675: getDataCache().reset();
0676:
0677: }
0678:
0679: /**
0680: * <p>Reset any deleted or updated values in the cache (as is done by
0681: * the <code>reset()</code> method), then call <code>rollback()</code>
0682: * on the JDBC <code>Connectino</code> underlying our current
0683: * <code>RowSet</code>.</p>
0684: *
0685: * @exception IllegalStateException if this method is called when
0686: * not connected to an underlying rowset
0687: * @exception SQLException if an error occurs while committing
0688: */
0689: public void rollback() throws SQLException {
0690:
0691: // System.out.println("RSDM: rollback()");
0692:
0693: // Validate our preconditions
0694: if (!connected()) {
0695: throw new IllegalStateException(bundle
0696: .getMessage("notConnected")); // NOI18N
0697: }
0698:
0699: // Reset the cache
0700: getDataCache().reset();
0701:
0702: // Roll back the database connection
0703: getRowSet().getStatement().getConnection().rollback();
0704:
0705: }
0706:
0707: /**
0708: * <p>Set the designated parameter on the <code>RowSet</code> to which
0709: * we are connected. This parameter will have no effect on any currently
0710: * selected rows; it takes effect the next time you call
0711: * <code>execute()</codew>.</p>
0712: *
0713: * @param index One-relative parameter index
0714: * @param value Value to be set
0715: *
0716: * @exception IllegalStateException if this method is called when
0717: * not connected to an underlying rowset
0718: * @exception SQLException if an error occurs setting the parameter value
0719: */
0720: public void setObject(int index, Object value) throws SQLException {
0721:
0722: // Validate our preconditions
0723: if (!connected()) {
0724: throw new IllegalStateException(bundle
0725: .getMessage("notConnected")); // NOI18N
0726: }
0727:
0728: // Set the parameter value
0729: getRowSet().setObject(index, value);
0730:
0731: }
0732:
0733: /**
0734: * <p>Set the designated parameter on the <code>RowSet</code> to which
0735: * we are connected. This parameter will have no effect on any currently
0736: * selected rows; it takes effect the next time you call
0737: * <code>execute()</codew>.</p>
0738: *
0739: * @param index One-relative parameter index
0740: * @param value Value to be set
0741: * @param type Destination SQL type (as defined in <code>java.sql.Types</code>)
0742: *
0743: * @exception IllegalStateException if this method is called when
0744: * not connected to an underlying rowset
0745: * @exception SQLException if an error occurs setting the parameter value
0746: */
0747: public void setObject(int index, Object value, int type)
0748: throws SQLException {
0749:
0750: // Validate our preconditions
0751: if (!connected()) {
0752: throw new IllegalStateException(bundle
0753: .getMessage("notConnected")); // NOI18N
0754: }
0755:
0756: // Set the parameter value
0757: getRowSet().setObject(index, value, type);
0758:
0759: }
0760:
0761: /**
0762: * <p>Set the designated parameter on the <code>RowSet</code> to which
0763: * we are connected. This parameter will have no effect on any currently
0764: * selected rows; it takes effect the next time you call
0765: * <code>execute()</codew>.</p>
0766: *
0767: * @param index One-relative parameter index
0768: * @param value Value to be set
0769: * @param type Destination SQL type (as defined in <code>java.sql.Types</code>)
0770: * @param scale For <code>java.sql.Types.DECIMAL</code> or
0771: * <code>java.sql.Types.NUMERIC</code> types, the number of digits
0772: * after the decimal point (ignored for all other types)
0773: *
0774: * @exception IllegalStateException if this method is called when
0775: * not connected to an underlying rowset
0776: * @exception SQLException if an error occurs setting the parameter value
0777: */
0778: public void setObject(int index, Object value, int type, int scale)
0779: throws SQLException {
0780:
0781: // Validate our preconditions
0782: if (!connected()) {
0783: throw new IllegalStateException(bundle
0784: .getMessage("notConnected")); // NOI18N
0785: }
0786:
0787: // Set the parameter value
0788: getRowSet().setObject(index, value, type, scale);
0789:
0790: }
0791:
0792: // ------------------------------------------------------- DataModel Methods
0793:
0794: /**
0795: * <p>Return -1 to indicate that the number of rows available is
0796: * unknown.</p>
0797: */
0798: public int getRowCount() {
0799:
0800: if (Beans.isDesignTime()) {
0801: return DESIGN_TIME_ROWS;
0802: }
0803:
0804: return (-1);
0805:
0806: }
0807:
0808: /**
0809: * <p>Return a <code>Map</code> representing the column values for
0810: * the row specified by the current <code>rowIndex</code>. The
0811: * returned Map supports case-insensitive matching on column names,
0812: * and records any updates to the column values for later transfer
0813: * to the database when <code>commit()</code> is called. It does
0814: * not allow column names (and corresponding values) to be added
0815: * or removed.</p>
0816: *
0817: * @exception IllegalArgumentException if there is no cached
0818: * data for the current <code>rowIndex</code>
0819: */
0820: public Object getRowData() {
0821:
0822: // System.out.println("RSDM: getRowData(" + rowIndex + ") --> " + getDataCache().get(rowIndex));
0823:
0824: DataCache.Row row = getDataCache().get(rowIndex);
0825: if (row == null) {
0826: throw new IllegalArgumentException("" + rowIndex);
0827: }
0828: return row;
0829:
0830: }
0831:
0832: /**
0833: * <p>Return the zero-relative index of the currently positioned row,
0834: * or -1 if we are not positioned on a row.</p>
0835: */
0836: public int getRowIndex() {
0837:
0838: return (rowIndex);
0839:
0840: }
0841:
0842: /**
0843: * <p>Return the <code>RowSet</code> we are currently wrapping,
0844: * if any.</p>
0845: */
0846: public Object getWrappedData() {
0847:
0848: return (rowSet);
0849:
0850: }
0851:
0852: /**
0853: * <p>Return <code>true</code> if there is a cache entry for the
0854: * current <code>rowIndex</code> value.</p>
0855: */
0856: public boolean isRowAvailable() {
0857:
0858: //designtime check only
0859: if (Beans.isDesignTime()) {
0860: if (rowIndex < 0 || rowIndex >= DESIGN_TIME_ROWS) {
0861: return false;
0862: }
0863: }
0864:
0865: //designtime and runtime
0866: if (rowIndex >= 0) {
0867: return getDataCache().get(rowIndex) != null;
0868: } else {
0869: return false;
0870: }
0871:
0872: }
0873:
0874: //to be called at designtime only as part of fix for 6333068
0875: private boolean shouldRowBeAvailable() {
0876: return rowIndex >= 0 && rowIndex < DESIGN_TIME_ROWS;
0877: }
0878:
0879: /**
0880: * <p>Set the zero relative index for the newly positioned row, or
0881: * set to -1 for no currently selected row. If there is no current
0882: * cache entry for this row, but we are currently connected, position
0883: * the underlying <code>RowSet</code> to the corresponding one-relative
0884: * row number, and create a new cache entry. In addition, fire a
0885: * <code>DataModelEvent</code> if needed, per the Javadocs for
0886: * this method on <code>javax.faces.model.DataModel</code>.</p>
0887: *
0888: * @param rowIndex The row index, or -1 for no selected row
0889: *
0890: * @exception FacesException if an error occurs setting the row index
0891: * @exception IllegalArgumentException if rowIndex is less than -1
0892: */
0893: public void setRowIndex(int rowIndex) {
0894:
0895: // System.out.println("RSDM: setRowIndex(" + rowIndex + ")");
0896:
0897: // Bounds check on the incoming argument
0898: if (rowIndex < -1) {
0899: throw new IllegalArgumentException(bundle.getMessage(
0900: "invalidRowIndex", new Integer(rowIndex))); // NOI18N
0901: }
0902:
0903: // Update the current row index
0904: int oldIndex = this .rowIndex;
0905: this .rowIndex = rowIndex;
0906:
0907: // If we are not connected, nothing else to do
0908: if (!connected()) {
0909: return;
0910: }
0911:
0912: // Construct a new cache item if necessary
0913: if ((rowIndex >= 0) && connected() && !Beans.isDesignTime()
0914: && (getDataCache().get(rowIndex) == null)) {
0915: DataCache.Row row = create();
0916: if (row != null) {
0917: getDataCache().add(rowIndex, row);
0918: }
0919: }
0920:
0921: // Broadcast an event to interested listeners if we changed rows
0922: DataModelListener listeners[] = getDataModelListeners();
0923: if ((oldIndex != rowIndex) && (listeners != null)) {
0924: Object rowData = null;
0925: if (Beans.isDesignTime()) {
0926: if (shouldRowBeAvailable() && !isRowAvailable()) {
0927: synchronize();
0928: }
0929: }
0930: if (isRowAvailable()) {
0931: rowData = getRowData();
0932: }
0933: DataModelEvent event = new DataModelEvent(this , rowIndex,
0934: rowData);
0935: int n = listeners.length;
0936: for (int i = 0; i < n; i++) {
0937: if (null != listeners[i]) {
0938: listeners[i].rowSelected(event);
0939: }
0940: }
0941: }
0942:
0943: }
0944:
0945: /**
0946: * <p>Set the <code>RowSet</code> wrapped by this
0947: * <code>RowSetDataModel</code>, or <code>null</code> to disconnect
0948: * from the previously connected <code>RowSet</code>.</p>
0949: *
0950: * @param rowSet <code>RowSet</code> to be wrapped, or <code>null</code>
0951: * to disconnect
0952: *
0953: * @exception ClassCastException if this object is not of the
0954: * correct type
0955: */
0956: public void setWrappedData(Object rowSet) {
0957:
0958: if (rowSet == null) {
0959: disconnect();
0960: } else {
0961: disconnect();
0962: connect((RowSet) rowSet);
0963: }
0964:
0965: }
0966:
0967: // --------------------------------------------------------- Private Methods
0968:
0969: /**
0970: * <p>Return the subset of the specified set of column names (from the
0971: * original query) that are part of the specified table. This is useful
0972: * on databases whose JDBC driver does not include column names in the
0973: * <code>ResultSetMetaData</code>.</p>
0974: *
0975: * @param conn <code>Connection</code> from which we can acquire
0976: * database metadata
0977: * @param extracted The table or schema.table identifier extracted
0978: * from our query
0979: * @param columns List of column names included in the query (of which
0980: * a subset will be returned)
0981: *
0982: * @exception SQLException if an error occurs processing the metadata
0983: */
0984: private List columns(DatabaseMetaData dbmd, String extracted,
0985: List columns) throws SQLException {
0986:
0987: // Set useConditionalWhereClause based on driver
0988: String driverName = dbmd.getDriverName();
0989: if (driverName != null && driverName.equals("DB2")) {
0990: useConditionalWhereClause = false;
0991: } else {
0992: useConditionalWhereClause = true;
0993: }
0994:
0995: // Narrow our results down to the table of interest
0996: String schemaName = null;
0997: String tableName = extracted;
0998: int period = tableName.lastIndexOf('.');
0999: if (period >= 0) {
1000: schemaName = tableName.substring(0, period);
1001: tableName = tableName.substring(period + 1);
1002: }
1003: ResultSet trs = dbmd.getTables(null, schemaName, tableName,
1004: null);
1005: // if more than one table, take first one (what else can we do?)
1006: if (!trs.next()) {
1007: throw new SQLException(tableName + " not found");
1008: }
1009:
1010: // Retrieve the column names for the table of interest
1011: // and save the ones that match
1012: ResultSet crs = dbmd.getColumns(trs.getString("TABLE_CAT"), trs
1013: .getString("TABLE_SCHEM"), trs.getString("TABLE_NAME"),
1014: "%");
1015: trs.close();
1016: List results = new ArrayList();
1017: int n = columns.size();
1018: while (crs.next()) {
1019: String name = crs.getString("COLUMN_NAME");
1020: for (int i = 0; i < n; i++) {
1021: if (name.equalsIgnoreCase((String) columns.get(i))) {
1022: results.add(name);
1023: break;
1024: }
1025: }
1026: }
1027: crs.close();
1028: return results;
1029:
1030: }
1031:
1032: /**
1033: * <p>Connect ourselves to the specified new <code>RowSet</code>.</p>
1034: *
1035: * @param rowSet The new <code>RowSet</code> to connect to
1036: */
1037: private void connect(RowSet rowSet) {
1038:
1039: this .rowSet = rowSet;
1040: getRowSet().addRowSetListener(listener);
1041: synchronize();
1042:
1043: }
1044:
1045: /**
1046: * <p>Return <code>true</code> if we are currently connected to an
1047: * underlying <code>RowSet</code>.</p>
1048: */
1049: private boolean connected() {
1050:
1051: return (rowSet != null);
1052:
1053: }
1054:
1055: /**
1056: * <p>Create and return a new {@link DataCache.Row} representing the
1057: * row at the currently set <code>rowIndex</code>, if there is
1058: * actually such a row in the underlying rowset. If there is no
1059: * such row, return <code>null</code>. Assumes that we
1060: * are connected, and that rowIndex is non-negative.</p>
1061: *
1062: * @exception FacesException if an error occurs creating this item
1063: */
1064: private DataCache.Row create() {
1065:
1066: initialize();
1067:
1068: try {
1069:
1070: // Position based on the current row index
1071: if (!getRowSet().absolute(rowIndex + 1)) { // One relative
1072: return (null);
1073: }
1074:
1075: // Create a CacheItem for the contents of the current row
1076: DataCache.Column columns[] = new DataCache.Column[columnNames
1077: .size()];
1078: for (int i = 0; i < columnNames.size(); i++) {
1079: columns[i] = getDataCache().createColumn(
1080: (String) schemaNames.get(i),
1081: (String) tableNames.get(i),
1082: (String) columnNames.get(i),
1083: ((Integer) columnTypes.get(i)).intValue(),
1084: (Class) columnJavaTypes.get(i),
1085: getRowSet().getObject(i + 1));
1086: }
1087: DataCache.Row row = getDataCache().createRow(columns);
1088: return row;
1089:
1090: } catch (SQLException e) {
1091: throw new FacesException(e);
1092: }
1093:
1094: }
1095:
1096: /**
1097: * <p>Disconnect from the currently connected <code>RowSet</code>
1098: * (if any). If we are not currently connected, do nothing.</p>
1099: */
1100: private void disconnect() {
1101:
1102: if (!connected()) {
1103: return;
1104: }
1105:
1106: getRowSet().removeRowSetListener(listener);
1107: metadata = null;
1108: rowSet = null;
1109: // NOTE - leave columnNames etc. because the cache is still alive
1110: // NOTE - leave rowIndex where it was for persistence
1111: // (but that won't help much behind a Data Table, because
1112: // the component will move the cursor around on its own)
1113:
1114: }
1115:
1116: /**
1117: * <p>Extract and return a table (or schema.table) name from
1118: * the specified command, which should be an SQL select statement.</p>
1119: *
1120: * @param command SQL command for this rowset
1121: */
1122: private String extract(String command) {
1123:
1124: boolean next = false;
1125: command = command.trim();
1126: String word;
1127: while (command.length() > 0) {
1128: int space = command.indexOf(' ');
1129: if (space >= 0) {
1130: word = command.substring(0, space).trim();
1131: command = command.substring(space + 1).trim();
1132: ;
1133: } else {
1134: word = command.trim();
1135: command = "";
1136: }
1137: if (next) {
1138: int comma = word.indexOf(',');
1139: if (comma >= 0) {
1140: word = word.substring(0, comma);
1141: }
1142: return word;
1143: } else if ("FROM".equalsIgnoreCase(word)) {
1144: next = true;
1145: }
1146: }
1147: return "";
1148:
1149: }
1150:
1151: /**
1152: * <p>Execute the rowset if needed, to avoid the need
1153: * to manually execute it, if we not at design time.</p>
1154: */
1155: private void initialize() {
1156:
1157: if (Beans.isDesignTime()) {
1158: return;
1159: }
1160:
1161: RowSet rowSet = getRowSet();
1162: try {
1163: if (rowSet.isBeforeFirst()) {
1164: try {
1165: rowSet.first();
1166: } catch (SQLException x) {
1167: }
1168: }
1169: } catch (SQLException x1) {
1170: try {
1171: rowSet.execute();
1172: } catch (SQLException x2) {
1173: throw new FacesException(x2);
1174: }
1175: }
1176:
1177: }
1178:
1179: /**
1180: * <p>Return <code>true</code> if this column belongs to
1181: * the table we will be updating during a <code>commit()</code>.
1182: *
1183: * @param index Zero-relative index of the column to check
1184: * @param names Column names extracted from database metadata, or
1185: * <code>null</code> to check the specific column information
1186: */
1187: private boolean match(int index, List names) {
1188:
1189: if (names != null) {
1190: String name = (String) columnNames.get(index);
1191: for (int i = 0; i < names.size(); i++) {
1192: if (name.equalsIgnoreCase((String) names.get(i))) {
1193: return true;
1194: }
1195: }
1196: return false;
1197: }
1198:
1199: if ((schemaName != null)
1200: && !schemaName.equalsIgnoreCase((String) schemaNames
1201: .get(index))) {
1202: return false;
1203: }
1204: if ((tableName != null)
1205: && !tableName.equalsIgnoreCase((String) tableNames
1206: .get(index))) {
1207: return false;
1208: }
1209: return true;
1210:
1211: }
1212:
1213: /**
1214: * <p>Return the specified string in single quotes if it contains
1215: * any whitespace characters, or unchanged otherwise.</p>
1216: *
1217: * @param value String value to be optionally quoted
1218: */
1219: private String quoted(String value) {
1220: boolean required = false;
1221: for (int i = 0; i < value.length(); i++) {
1222: if (Character.isWhitespace(value.charAt(i))) {
1223: required = true;
1224: break;
1225: }
1226: }
1227: if (required) {
1228: return '"' + value + '"';
1229: } else {
1230: return value;
1231: }
1232: }
1233:
1234: /**
1235: * <p>Synchronize the metadata associated with the current
1236: * <code>RowSet</code>. If the set of columns has changed
1237: * from the previous contents (if any), the cache will be
1238: * cleared.</p>
1239: */
1240: private void synchronize() {
1241:
1242: // System.out.println("RSDM: synchronize()");
1243:
1244: // Update the column metadata, and detect any changes
1245: boolean changed = false;
1246: try {
1247: DataCache.Column previous[] = new DataCache.Column[0];
1248: DataCache.Row row = null;
1249: Iterator keys = getDataCache().iterator();
1250: if (keys.hasNext()) {
1251: row = getDataCache().get(
1252: ((Integer) keys.next()).intValue());
1253: previous = row.getColumns();
1254: }
1255: int m = previous.length;
1256: metadata = getRowSet().getMetaData();
1257: int n = metadata.getColumnCount();
1258: if (m != n) {
1259: changed = true;
1260: }
1261: columnNames.clear();
1262: columnTypes.clear();
1263: columnJavaTypes.clear();
1264: schemaNames.clear();
1265: tableNames.clear();
1266: for (int i = 1; i <= n; i++) {
1267: // Has this column changed?
1268: if (previous.length >= i) {
1269: if (!previous[i - 1].getColumnName().equals(
1270: metadata.getColumnName(i))
1271: || !previous[i - 1].getSchemaName().equals(
1272: metadata.getSchemaName(i))
1273: || !previous[i - 1].getTableName().equals(
1274: metadata.getTableName(i))) {
1275: changed = true;
1276: }
1277: }
1278: // Save the new column names
1279: columnNames.add(metadata.getColumnName(i));
1280: columnTypes.add(new Integer(metadata.getColumnType(i)));
1281: Class javaType = null;
1282: try {
1283: String javaTypeName = metadata
1284: .getColumnClassName(i);
1285: javaType = Class.forName(javaTypeName);
1286: } catch (Exception jte) {
1287: //let javaType be null
1288: }
1289: columnJavaTypes.add(javaType);
1290: schemaNames.add(metadata.getSchemaName(i));
1291: tableNames.add(metadata.getTableName(i));
1292: }
1293: } catch (SQLException e) {
1294: throw new FacesException(e);
1295: }
1296:
1297: // If the set of columns has changed, clear the cache
1298: if (!Beans.isDesignTime() && changed) {
1299: // System.out.println("RSDM: columns have changed, clear cache");
1300: getDataCache().clear();
1301: }
1302:
1303: // At design time only, cache some fake data
1304: // with the appropriate data types
1305: if (!Beans.isDesignTime()) {
1306: return;
1307: }
1308: for (int i = 0; i < DESIGN_TIME_ROWS; i++) {
1309: DataCache.Column columns[] = new DataCache.Column[columnNames
1310: .size()];
1311: for (int j = 0; j < columnNames.size(); j++) {
1312: String schemaName = "";
1313: String tableName = "";
1314: String columnName = (String) columnNames.get(j);
1315: int columnType = ((Integer) columnTypes.get(j))
1316: .intValue();
1317: Class columnJavaType = (Class) columnJavaTypes.get(j);
1318: Object fakeData;
1319: try {
1320: fakeData = getFakeData(metadata, columnName);
1321: } catch (SQLException e) {
1322: throw new FacesException(e);
1323: }
1324: columns[j] = getDataCache().createColumn(schemaName,
1325: tableName, columnName, columnType,
1326: columnJavaType, fakeData);
1327: }
1328: DataCache.Row row = getDataCache().createRow(columns);
1329: getDataCache().add(i, row);
1330: }
1331:
1332: }
1333:
1334: // --------------------------------------------------------- Private Classes
1335:
1336: /**
1337: * <p>Listener for significant changes on the <code>RowSet</code> our
1338: * parent is connected to.</p>
1339: */
1340: private class CachedRowSetListener implements RowSetListener {
1341:
1342: public void cursorMoved(RowSetEvent event) {
1343: ; // No action required
1344: }
1345:
1346: public void rowChanged(RowSetEvent event) {
1347: ; // No action required
1348: }
1349:
1350: public void rowSetChanged(RowSetEvent event) {
1351: // System.out.println("RSDM: rowSetChanged()");
1352: RowSetDataModel.this .synchronize();
1353: }
1354:
1355: }
1356:
1357: // -------------------------------------------------- Private Static Methods
1358:
1359: /**
1360: * <p>Return fake data of the appropriate type for use at design time.
1361: * (Snarfed from <code>ResultSetPropertyResolver</code>).</p>
1362: */
1363: private static Object getFakeData(ResultSetMetaData rsmd,
1364: String colName) throws SQLException {
1365:
1366: int colIndex = -1;
1367: for (int i = 1; i <= rsmd.getColumnCount(); i++) {
1368: if (rsmd.getColumnName(i).equals(colName)) {
1369: colIndex = i;
1370: break;
1371: }
1372: }
1373: switch (rsmd.getColumnType(colIndex)) {
1374: case Types.ARRAY:
1375: return new java.sql.Array() {
1376: public Object getArray() {
1377: return null;
1378: }
1379:
1380: public Object getArray(long index, int count) {
1381: return null;
1382: }
1383:
1384: public Object getArray(long index, int count, Map map) {
1385: return null;
1386: }
1387:
1388: public Object getArray(Map map) {
1389: return null;
1390: }
1391:
1392: public int getBaseType() {
1393: return Types.CHAR;
1394: }
1395:
1396: public String getBaseTypeName() {
1397: return "CHAR"; //NOI18N
1398: }
1399:
1400: public ResultSet getResultSet() {
1401: return null;
1402: }
1403:
1404: public ResultSet getResultSet(long index, int count) {
1405: return null;
1406: }
1407:
1408: public ResultSet getResultSet(long index, int count,
1409: Map map) {
1410: return null;
1411: }
1412:
1413: public ResultSet getResultSet(Map map) {
1414: return null;
1415: }
1416:
1417: public void free() {
1418: }
1419: };
1420: case Types.BIGINT:
1421:
1422: //return new Long(rowIndex);
1423: return new Long(123);
1424: case Types.BINARY:
1425: return new byte[] { 1, 2, 3, 4, 5 };
1426: case Types.BIT:
1427: return new Boolean(true);
1428: case Types.BLOB:
1429: return new javax.sql.rowset.serial.SerialBlob(new byte[] {
1430: 1, 2, 3, 4, 5 });
1431: case Types.BOOLEAN:
1432: return new Boolean(true);
1433: case Types.CHAR:
1434:
1435: //return new String(colName + rowIndex);
1436: return new String(bundle.getMessage("arbitraryCharData")); //NOI18N
1437: case Types.CLOB:
1438: return new javax.sql.rowset.serial.SerialClob(bundle
1439: .getMessage("arbitraryClobData").toCharArray());
1440: case Types.DATALINK:
1441: try {
1442: return new java.net.URL("http://www.sun.com"); //NOI18N
1443: } catch (java.net.MalformedURLException e) {
1444: return null;
1445: }
1446: case Types.DATE:
1447: return new java.sql.Date(new java.util.Date().getTime());
1448: case Types.DECIMAL:
1449: return new java.math.BigDecimal(java.math.BigInteger.ONE);
1450: case Types.DISTINCT:
1451: return null;
1452: case Types.DOUBLE:
1453:
1454: //return new Double(rowIndex);
1455: return new Double(123);
1456: case Types.FLOAT:
1457:
1458: //return new Double(rowIndex);
1459: return new Double(123);
1460: case Types.INTEGER:
1461:
1462: //return new Integer(rowIndex);
1463: return new Integer(123);
1464: case Types.JAVA_OBJECT:
1465:
1466: //return new String(colName + "_" + rowIndex); //NOI18N
1467: return new String(bundle.getMessage("arbitraryCharData")); //NOI18N
1468: case Types.LONGVARBINARY:
1469: return new byte[] { 1, 2, 3, 4, 5 };
1470: case Types.LONGVARCHAR:
1471:
1472: //return new String(colName + "_" + rowIndex); //NOI18N
1473: return new String(bundle.getMessage("arbitraryCharData")); //NOI18N
1474: case Types.NULL:
1475: return null;
1476: case Types.NUMERIC:
1477: return new java.math.BigDecimal(java.math.BigInteger.ONE);
1478: case Types.OTHER:
1479: return null;
1480: case Types.REAL:
1481:
1482: //return new Float(rowIndex);
1483: return new Float(123);
1484: case Types.REF:
1485: return new java.sql.Ref() {
1486: private Object data = new String(bundle
1487: .getMessage("arbitraryCharData")); //NOI18N
1488:
1489: public String getBaseTypeName() {
1490: return "CHAR"; //NOI18N
1491: }
1492:
1493: public Object getObject() {
1494: return data;
1495: }
1496:
1497: public Object getObject(Map map) {
1498: return data;
1499: }
1500:
1501: public void setObject(Object value) {
1502: data = value;
1503: }
1504: };
1505: case Types.SMALLINT:
1506:
1507: //return new Short((short)rowIndex);
1508: return new Short((short) 123);
1509: case Types.STRUCT:
1510: return new java.sql.Struct() {
1511: private String[] data = {
1512: bundle.getMessage("arbitraryCharData"),
1513: bundle.getMessage("arbitraryCharData2"),
1514: bundle.getMessage("arbitraryCharData3") }; //NOI18N
1515:
1516: public Object[] getAttributes() {
1517: return data;
1518: }
1519:
1520: public Object[] getAttributes(Map map) {
1521: return data;
1522: }
1523:
1524: public String getSQLTypeName() {
1525: return "CHAR"; //NOI18N
1526: }
1527: };
1528: case Types.TIME:
1529: return new java.sql.Time(new java.util.Date().getTime());
1530: case Types.TIMESTAMP:
1531: return new java.sql.Timestamp(new java.util.Date()
1532: .getTime());
1533: case Types.TINYINT:
1534:
1535: //return new Byte((byte)rowIndex);
1536: return new Byte((byte) 123);
1537: case Types.VARBINARY:
1538: return new byte[] { 1, 2, 3, 4, 5 };
1539: case Types.VARCHAR:
1540:
1541: //return new String(colName + "_" + rowIndex); //NOI18N
1542: return new String(bundle.getMessage("arbitraryCharData")); //NOI18N
1543: }
1544: return null;
1545: }
1546:
1547: /**
1548: * <p>Compose update and delete statements. This method returns an array of 2 Strings,
1549: * the first being an update statement and the second being a delete statement.
1550: *
1551: * If useConditionalWhereClause is true, the statements are composed in such a way that
1552: * they will work even if some columns are null. This is accomplished by checking for
1553: * null in the where clause. As a consequence of this, the column parameter in the where
1554: * clause must be set twice.
1555: * For example:
1556: * UPDATE table 1
1557: * SET col1 = ?, col2 = ?
1558: * WHERE ((? IS NULL AND col1 IS NULL) OR col1 = ?)
1559: * AND ((? IS NULL AND col2 IS NULL) OR col2 = ?)
1560: *
1561: * If useConditionalWhereClause is false, when called with null as the value of the
1562: * columns argument, these returned statements will be the "standard" statements that
1563: * are prepared and used for all rows where no original values are null. When called
1564: * with a non-null columns argument, this method returns statements that correctly deal
1565: * with null values in the where clause (that is, the where caluse contains a
1566: * "<column-name> IS NULL"
1567: */
1568: private String[] composeStatements(String extracted, List names,
1569: boolean useConditionalWhereClause,
1570: DataCache.Column[] columns) {
1571:
1572: // Construct the delete and update statements we will be using
1573: StringBuffer dsb = new StringBuffer("DELETE FROM "); // NOI18N
1574: if (extracted != null) {
1575: dsb.append(extracted);
1576: } else {
1577: if ((getSchemaName() != null)
1578: && (getSchemaName().length() > 0)) {
1579: dsb.append(quoted(getSchemaName()));
1580: dsb.append("."); // NOI18N
1581: }
1582: dsb.append(quoted(getTableName()));
1583: }
1584:
1585: StringBuffer usb = new StringBuffer("UPDATE "); // NOI18N
1586: if (extracted != null) {
1587: usb.append(extracted);
1588: } else {
1589: if ((getSchemaName() != null)
1590: && (getSchemaName().length() > 0)) {
1591: usb.append(quoted(getSchemaName()));
1592: usb.append("."); // NOI18N
1593: }
1594: usb.append(quoted(getTableName()));
1595: }
1596:
1597: usb.append(" SET "); // NOI18N
1598: int m = 0; // included columns
1599: int n = columnNames.size();
1600: for (int i = 0; i < n; i++) {
1601: if (!match(i, names)) {
1602: continue;
1603: }
1604: if (m > 0) {
1605: usb.append(", "); // NOI18N
1606: }
1607: usb.append(quoted((String) columnNames.get(i)));
1608: usb.append(" = ?"); // NOI18N
1609: m++;
1610: }
1611:
1612: dsb.append(" WHERE "); // NOI18N
1613: usb.append(" WHERE "); // NOI18N
1614: m = 0;
1615: for (int i = 0; i < n; i++) {
1616: if (!match(i, names)) {
1617: continue;
1618: }
1619: if (m > 0) {
1620: dsb.append(" AND "); // NOI18N
1621: usb.append(" AND "); // NOI18N
1622: }
1623: if (useConditionalWhereClause) {
1624: dsb.append("((? IS NULL AND ");
1625: usb.append("((? IS NULL AND ");
1626: dsb.append(quoted((String) columnNames.get(i)));
1627: usb.append(quoted((String) columnNames.get(i)));
1628: dsb.append(" IS NULL) OR ");
1629: usb.append(" IS NULL) OR ");
1630: dsb.append(quoted((String) columnNames.get(i)));
1631: usb.append(quoted((String) columnNames.get(i)));
1632: dsb.append(" = ?)");
1633: usb.append(" = ?)");
1634: } else if (columns != null
1635: && columns[i].getOriginal() == null) {
1636: dsb.append(quoted((String) columnNames.get(i)));
1637: usb.append(quoted((String) columnNames.get(i)));
1638: dsb.append(" IS NULL"); // NOI18N
1639: usb.append(" IS NULL"); // NOI18N
1640: } else {
1641: dsb.append(quoted((String) columnNames.get(i)));
1642: usb.append(quoted((String) columnNames.get(i)));
1643: dsb.append(" = ?"); // NOI18N
1644: usb.append(" = ?"); // NOI18N
1645: }
1646:
1647: m++;
1648: }
1649:
1650: String[] statements = new String[2];
1651: statements[0] = usb.toString();
1652: statements[1] = dsb.toString();
1653: //System.out.println("RSDM: " + ((columns == null)? "standard": "custom") + " update: " + statements[0]); //NOI18N
1654: //System.out.println("RSDM: " + ((columns == null)? "standard": "custom") + " delete: " + statements[1]); //NOI18N
1655:
1656: return statements;
1657: }
1658: }
|