Coverage Report - net.wotonomy.jdbcadaptor.JDBCChannel
 
Classes in this File Line Coverage Branch Coverage Complexity
JDBCChannel
0% 
0% 
4.6
 
 1  
 /*
 2  
  Wotonomy: OpenStep design patterns for pure Java applications.
 3  
  Copyright (C) 2001 Michael Powers
 4  
 
 5  
  This library is free software; you can redistribute it and/or
 6  
  modify it under the terms of the GNU Lesser General Public
 7  
  License as published by the Free Software Foundation; either
 8  
  version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
  This library is distributed in the hope that it will be useful,
 11  
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
  Lesser General Public License for more details.
 14  
 
 15  
  You should have received a copy of the GNU Lesser General Public
 16  
  License along with this library; if not, see http://www.gnu.org
 17  
  */
 18  
 package net.wotonomy.jdbcadaptor;
 19  
 
 20  
 import java.math.BigDecimal;
 21  
 import java.sql.Blob;
 22  
 import java.sql.CallableStatement;
 23  
 import java.sql.ResultSet;
 24  
 import java.sql.ResultSetMetaData;
 25  
 import java.sql.SQLException;
 26  
 import java.sql.Statement;
 27  
 
 28  
 import net.wotonomy.access.EOAdaptorChannel;
 29  
 import net.wotonomy.access.EOAttribute;
 30  
 import net.wotonomy.access.EOEntity;
 31  
 import net.wotonomy.access.EOGeneralAdaptorException;
 32  
 import net.wotonomy.access.EOSQLExpression;
 33  
 import net.wotonomy.access.EOStoredProcedure;
 34  
 import net.wotonomy.control.EOFetchSpecification;
 35  
 import net.wotonomy.control.EOQualifier;
 36  
 import net.wotonomy.foundation.NSArray;
 37  
 import net.wotonomy.foundation.NSData;
 38  
 import net.wotonomy.foundation.NSDictionary;
 39  
 import net.wotonomy.foundation.NSKeyValueCoding;
 40  
 import net.wotonomy.foundation.NSMutableDictionary;
 41  
 import net.wotonomy.foundation.NSTimestamp;
 42  
 
 43  
 /**
 44  
 * Concrete implementation of EOAdaptorChannel for use with JDBC.
 45  
 *
 46  
 * @author ezamudio@nasoft.com
 47  
 * @author $Author: cgruber $
 48  
 * @version $Revision: 903 $
 49  
 */
 50  
 public class JDBCChannel extends EOAdaptorChannel {
 51  
 
 52  
         protected boolean _fetchInProgress;
 53  
         protected ResultSet _resultSet;
 54  
         protected Statement _statement;
 55  
         protected NSArray _attsToFetch;
 56  
         protected NSArray _resultAttributes;
 57  
         protected boolean _transactionWasOpen;
 58  
         protected NSDictionary _spReturnValues;
 59  
         protected int _resultCount;
 60  
 
 61  
         /**
 62  
          * Creates a new JDBCChannel.
 63  
          * @param context The JDBCContext this channel belongs to.
 64  
          */
 65  
         public JDBCChannel(JDBCContext context) {
 66  0
                 super(context);
 67  0
         }
 68  
 
 69  
         protected JDBCContext _context() {
 70  0
                 return (JDBCContext)adaptorContext();
 71  
         }
 72  
 
 73  
         /* Sets the attributes to be fetched from the database.
 74  
          * @see net.wotonomy.access.EOAdaptorChannel#setAttributesToFetch(net.wotonomy.foundation.NSArray)
 75  
          */
 76  
         public void setAttributesToFetch(NSArray atts) {
 77  0
                 _attsToFetch = atts;
 78  0
         }
 79  
 
 80  
         /* Returns an array with the attributes that will be fetched.
 81  
          * @see net.wotonomy.access.EOAdaptorChannel#attributesToFetch()
 82  
          */
 83  
         public NSArray attributesToFetch() {
 84  0
                 return _attsToFetch;
 85  
         }
 86  
 
 87  
         /* Cancels the fetch, rolling back the transaction.
 88  
          * @see net.wotonomy.access.EOAdaptorChannel#cancelFetch()
 89  
          */
 90  
         public void cancelFetch() {
 91  0
                 if (_statement == null || _resultSet == null)
 92  0
                         return;
 93  
                 try {
 94  0
                         _resultSet.close();
 95  0
                         _statement.cancel();
 96  0
                 } catch (SQLException ex) {
 97  0
                         throw new JDBCAdaptorException("Cannot cancel fetch in database.", ex);
 98  0
                 }
 99  0
         }
 100  
 
 101  
         /* Closes the jdbc channel.
 102  
          * @see net.wotonomy.access.EOAdaptorChannel#closeChannel()
 103  
          */
 104  
         public void closeChannel() {
 105  0
                 if (_statement == null)
 106  0
                         return;
 107  
                 try {
 108  0
                         _statement.close();
 109  0
                 } catch (SQLException ex) {
 110  0
                         throw new JDBCAdaptorException("While trying to close the channel.", ex);
 111  0
                 }
 112  0
         }
 113  
 
 114  
         /* If the fetch was done with an array of EOAttributes, returns
 115  
          * that same array; otherwise it creates an array of EOAttributes
 116  
          * based on the column names that will be fetched.
 117  
          * @see net.wotonomy.access.EOAdaptorChannel#describeResults()
 118  
          */
 119  
         public NSArray describeResults() {
 120  0
                 if (_resultSet == null || !_fetchInProgress)
 121  0
                         throw new EOGeneralAdaptorException("Cannot describe results without a result set.");
 122  0
                 if (_resultAttributes == null) {
 123  
                         try {
 124  0
                                 ResultSetMetaData _rsmeta = _resultSet.getMetaData();
 125  0
                                 EOAttribute[] attarr = new EOAttribute[_rsmeta.getColumnCount()];
 126  0
                                 for (int i = 1; i <= attarr.length; i++) {
 127  0
                                         EOAttribute a = new EOAttribute();
 128  0
                                         a.setName("Attribute " + (i));
 129  0
                                         a.setColumnName(_rsmeta.getColumnName(i));
 130  0
                                         a.setClassName(_rsmeta.getColumnClassName(i));
 131  0
                                         a.setExternalType(_rsmeta.getColumnTypeName(i));
 132  0
                                         a.setPrecision(_rsmeta.getPrecision(i));
 133  0
                                         a.setScale(_rsmeta.getScale(i));
 134  0
                                         a.setAllowsNull(_rsmeta.isNullable(i) == ResultSetMetaData.columnNullable);
 135  0
                                         a.setWidth(_rsmeta.getColumnDisplaySize(i));
 136  0
                                         a.setReadOnly(_rsmeta.isReadOnly(i));
 137  0
                                         attarr[i-1] = a;
 138  
                                 }
 139  0
                                 _resultAttributes = new NSArray(attarr);
 140  0
                         } catch (SQLException ex) {
 141  0
                                 throw new JDBCAdaptorException("While trying to get the result set metadata.", ex);
 142  0
                         }
 143  
                 }
 144  0
                 return _resultAttributes;
 145  
         }
 146  
 
 147  
         /* Deletes from the database the rows described by the qualifier,
 148  
          * in the specified entity.
 149  
          * @see net.wotonomy.access.EOAdaptorChannel#deleteRowsDescribedByQualifier(net.wotonomy.control.EOQualifier, net.wotonomy.access.EOEntity)
 150  
          */
 151  
         public int deleteRowsDescribedByQualifier(EOQualifier q, EOEntity entity) {
 152  0
                 EOSQLExpression exp = adaptorContext().adaptor().expressionFactory().createExpression(entity);
 153  0
                 exp.prepareDeleteExpressionForQualifier(q);
 154  0
                 evaluateExpression(exp);
 155  0
                 return _resultCount;
 156  
         }
 157  
 
 158  
         /* Creates a java.sql.Statement object and executes it.
 159  
          * If there is an open transaction, the statement is executed inside it;
 160  
          * otherwise a transaction is started, the statement executed, and
 161  
          * the transaction is committed.
 162  
          * @see net.wotonomy.access.EOAdaptorChannel#evaluateExpression(net.wotonomy.access.EOSQLExpression)
 163  
          */
 164  
         public void evaluateExpression(EOSQLExpression sql) {
 165  0
                 if (!isOpen())
 166  0
                         throw new EOGeneralAdaptorException("Attempt to evaluate expression without opening the channel first.");
 167  
                 try {
 168  0
                         _statement = _context().connection().createStatement();
 169  0
                 } catch (SQLException ex) {
 170  0
                         throw new JDBCAdaptorException("Cannot create java.sql.Statement", ex);
 171  0
                 }
 172  0
                 _resultSet = null;
 173  0
                 boolean isQuery = false;
 174  0
                 String text = sql.statement();
 175  
                 try {
 176  
                         //run an executeUpdate with these prefixes
 177  0
                         if (text.startsWith("INSERT") || text.startsWith("DELETE") || text.startsWith("UPDATE")) {
 178  0
                                 conditionalBeginTransaction();
 179  0
                                 _resultCount = _statement.executeUpdate(text);
 180  0
                                 conditionalCommitTransaction();
 181  0
                                 return;
 182  0
                         } else if (text.startsWith("SELECT")) {
 183  
                                 //run an executeQuery with SELECT
 184  0
                                 if (_resultCount > 0)
 185  0
                                         _statement.setMaxRows(_resultCount);
 186  0
                                 _resultSet = _statement.executeQuery(text);
 187  0
                                 _fetchInProgress = true;
 188  0
                                 return;
 189  
                         } else { //just plain execute
 190  0
                                 conditionalBeginTransaction();
 191  0
                                 isQuery = _statement.execute(text);
 192  
                         }
 193  0
                 } catch (SQLException ex) {
 194  0
                         throw new JDBCAdaptorException("While trying to execute expression '" + text + "'", ex);
 195  0
                 }
 196  
                 try {
 197  0
                         if (isQuery) {
 198  0
                                 if (_resultCount > 0)
 199  0
                                         _statement.setMaxRows(_resultCount);
 200  0
                                 _resultSet = _statement.getResultSet();
 201  0
                         } else {
 202  0
                                 _resultCount = _statement.getUpdateCount();
 203  0
                                 conditionalCommitTransaction();
 204  
                         }
 205  0
                 } catch (SQLException ex) {
 206  0
                         throw new JDBCAdaptorException("While trying to get the result set.", ex);
 207  0
                 }
 208  0
         }
 209  
 
 210  
         /* Executes a stored procedure with the specified parameters.
 211  
          * Any results that the procedure returns should be obtained
 212  
          * by calling returnValuesForLastStoredProcedureInvocation.
 213  
          * @see net.wotonomy.access.EOAdaptorChannel#executeStoredProcedure(net.wotonomy.access.EOStoredProcedure, net.wotonomy.foundation.NSDictionary)
 214  
          */
 215  
         public void executeStoredProcedure(
 216  
                         EOStoredProcedure proc, NSDictionary values) {
 217  0
                 if (!isOpen())
 218  0
                         throw new EOGeneralAdaptorException("Attempt to execute a stored procedure on a closed channel.");
 219  0
                 conditionalBeginTransaction();
 220  
                 try {
 221  
                         //Assemble the procedure call
 222  0
                         StringBuffer buf = new StringBuffer("{ call ");
 223  0
                         buf.append(proc.externalName());
 224  0
                         NSArray args = proc.arguments();
 225  0
                         if (args != null && args.count() > 0) {
 226  0
                                 buf.append("[");
 227  0
                                 for (int i = 0; i < args.count(); i++) {
 228  0
                                         EOAttribute a = (EOAttribute)args.objectAtIndex(i);
 229  0
                                         if (a.parameterDirection() != EOAttribute.OutParameter) {
 230  0
                                                 buf.append('?');
 231  0
                                                 buf.append(", ");
 232  
                                         }
 233  
                                 }
 234  0
                                 buf.delete(buf.length()-2, buf.length());
 235  0
                                 buf.append("]");
 236  
                         }
 237  0
                         buf.append(" }");
 238  
                         //get the callable statement
 239  0
                         CallableStatement sp = _context().connection().prepareCall(buf.toString());
 240  0
                         if (args != null && args.count() > 0) {
 241  0
                                 int pos = 1;
 242  
                                 //set the in and inOut parameters
 243  0
                                 for (int i = 0; i < args.count(); i++) {
 244  0
                                         EOAttribute a = (EOAttribute)args.objectAtIndex(i);
 245  0
                                         if (a.parameterDirection() != EOAttribute.OutParameter) {
 246  0
                                                 Object val = values.objectForKey(a.name());
 247  0
                                                 if (val == NSKeyValueCoding.NullValue)
 248  0
                                                         sp.setNull(pos, 0); //TODO: check sql type
 249  0
                                                 if (val instanceof String)
 250  0
                                                         sp.setString(pos, (String)val);
 251  0
                                                 else if (val instanceof BigDecimal)
 252  0
                                                         sp.setBigDecimal(pos, (BigDecimal)val);
 253  0
                                                 else if (val instanceof NSTimestamp)
 254  0
                                                         sp.setTimestamp(pos, (NSTimestamp)val);
 255  0
                                                 else if (val instanceof NSData)
 256  0
                                                         sp.setBytes(pos, ((NSData)val).bytes());
 257  0
                                                 else if (val instanceof Integer)
 258  0
                                                         sp.setInt(pos, ((Integer)val).intValue());
 259  0
                                                 else if (val instanceof Long)
 260  0
                                                         sp.setLong(pos, ((Long)val).longValue());
 261  
                                                 else
 262  0
                                                         sp.setObject(pos, val);
 263  0
                                                 pos++;
 264  
                                         }
 265  
                                 }
 266  
                         }
 267  
                         //run the procedure
 268  0
                         sp.execute();
 269  
                         //get the return values
 270  0
                         if (args != null && args.count() > 0) {
 271  0
                                 int pos = 1;
 272  0
                                 NSMutableDictionary retvals = new NSMutableDictionary();
 273  0
                                 for (int i = 0; i < args.count(); i++) {
 274  0
                                         EOAttribute a = (EOAttribute)args.objectAtIndex(i);
 275  0
                                         if (a.parameterDirection() != EOAttribute.InParameter) {
 276  0
                                                 Object val = sp.getObject(pos);
 277  0
                                                 if (val == null)
 278  0
                                                         retvals.setObjectForKey(NSKeyValueCoding.NullValue, a.name());
 279  0
                                                 else if (val instanceof Blob) {
 280  
                                                         try {
 281  0
                                                                 retvals.setObjectForKey(new NSData(((Blob)val).getBinaryStream(), 1024), a.name());
 282  0
                                                         } catch (java.io.IOException ex) {
 283  
                                                                 //what should I do here?
 284  0
                                                                 retvals.setObjectForKey(NSData.EmptyData, a.name());
 285  0
                                                         }
 286  0
                                                 } else
 287  0
                                                         retvals.setObjectForKey(val, a.name());
 288  0
                                                 pos++;
 289  
                                         }
 290  
                                 }
 291  0
                                 _spReturnValues = retvals;
 292  
                         }
 293  0
                 } catch (SQLException ex) {
 294  0
                         throw new JDBCAdaptorException("While trying to execute stored procedure.", ex);
 295  0
                 }
 296  0
                 conditionalCommitTransaction();
 297  0
         }
 298  
 
 299  
         /* Fetches one row from the database
 300  
          * @see net.wotonomy.access.EOAdaptorChannel#fetchRow()
 301  
          */
 302  
         public NSMutableDictionary fetchRow() {
 303  0
                 if (_resultSet == null) {
 304  0
                         return null;
 305  
                 }
 306  0
                 if (attributesToFetch() == null)
 307  0
                         throw new EOGeneralAdaptorException("Attempt to fetchRow without setting attributes to fetch first.");
 308  
                 try {
 309  
                         //If the current result set ends, there may be another one
 310  0
                         if (!_resultSet.next()) {
 311  0
                                 _resultSet.close();
 312  0
                                 _resultAttributes = null;
 313  0
                                 _fetchInProgress = _statement.getMoreResults();
 314  0
                                 if (_fetchInProgress)
 315  0
                                         _resultSet = _statement.getResultSet();
 316  0
                                 return null;
 317  
                         }
 318  0
                 } catch (SQLException ex) {
 319  0
                         throw new JDBCAdaptorException("While trying to fetch row.", ex);
 320  0
                 }
 321  
 
 322  
                 //Assemble the dictionary
 323  0
                 NSMutableDictionary dict = new NSMutableDictionary(attributesToFetch().count());
 324  
                 try {
 325  0
                         for (int i = 0; i < attributesToFetch().count(); i++) {
 326  0
                                 EOAttribute a = (EOAttribute)attributesToFetch().objectAtIndex(i);
 327  0
                                 Object o = _resultSet.getObject(i+1);
 328  0
                                 if (o == null)
 329  0
                                         o = NSKeyValueCoding.NullValue;
 330  0
                                 dict.setObjectForKey(o, a.name());
 331  
                         }
 332  0
                 } catch (SQLException ex) {
 333  0
                         throw new JDBCAdaptorException("While trying to create row.", ex);
 334  0
                 }
 335  0
                 return dict;
 336  
         }
 337  
 
 338  
         /* Inserts a row into a table in the database.
 339  
          * @see net.wotonomy.access.EOAdaptorChannel#insertRow(net.wotonomy.foundation.NSDictionary, net.wotonomy.access.EOEntity)
 340  
          */
 341  
         public void insertRow(NSDictionary row, EOEntity entity) {
 342  0
                 EOSQLExpression exp = adaptorContext().adaptor().expressionFactory().createExpression(entity);
 343  0
                 exp.prepareInsertExpressionWithRow(row);
 344  0
                 evaluateExpression(exp);
 345  0
         }
 346  
 
 347  
         /* Indicates if a fetch is in progress; that is, if a SELECT statement
 348  
          * was executed and there are still rows to be fetched.
 349  
          * @see net.wotonomy.access.EOAdaptorChannel#isFetchInProgress()
 350  
          */
 351  
         public boolean isFetchInProgress() {
 352  0
                 return _fetchInProgress;
 353  
         }
 354  
 
 355  
         /* Indicates if the channel is open.
 356  
          * @see net.wotonomy.access.EOAdaptorChannel#isOpen()
 357  
          */
 358  
         public boolean isOpen() {
 359  0
                 boolean open = (_context().connection() != null);
 360  
                 try {
 361  0
                         open = open || !_context().connection().isClosed();
 362  0
                 } catch (SQLException ex) {
 363  0
                         open = false;
 364  0
                 }
 365  0
                 return open;
 366  
         }
 367  
 
 368  
         /* Opens the channel. If the adaptor context has not yet made
 369  
          * a connection to the database, this forces the context to
 370  
          * connect.
 371  
          * @see net.wotonomy.access.EOAdaptorChannel#openChannel()
 372  
          */
 373  
         public void openChannel() {
 374  
                 try {
 375  0
                         if (_context().connection() == null || _context().connection().isClosed())
 376  0
                                 _context().connect();
 377  0
                 } catch (SQLException ex) {
 378  0
                         throw new JDBCAdaptorException("Cannot open connection to database.", ex);
 379  0
                 }
 380  0
         }
 381  
 
 382  
         /* Returns the values obtained from the last stored procedure executed.
 383  
          * @see net.wotonomy.access.EOAdaptorChannel#returnValuesForLastStoredProcedureInvocation()
 384  
          */
 385  
         public NSDictionary returnValuesForLastStoredProcedureInvocation() {
 386  0
                 return _spReturnValues;
 387  
         }
 388  
 
 389  
         /* Creates a SELECT expression and executes it. If the attribute array is null,
 390  
          * then the result's metadata is used to dynamically create an array
 391  
          * of attributes.
 392  
          * @see net.wotonomy.access.EOAdaptorChannel#selectAttributes(net.wotonomy.foundation.NSArray, net.wotonomy.control.EOFetchSpecification, boolean, net.wotonomy.access.EOEntity)
 393  
          */
 394  
         public void selectAttributes(
 395  
                         NSArray atts, EOFetchSpecification fspec,
 396  
                         boolean lock, EOEntity entity) {
 397  0
                 _resultAttributes = atts;
 398  0
                 EOSQLExpression expr = adaptorContext().adaptor().expressionFactory().createExpression(entity);
 399  0
                 _fetchInProgress = true;
 400  0
                 expr.prepareSelectExpressionWithAttributes(atts, lock, fspec);
 401  
                 //for now we store the fetch limit here
 402  0
                 if (fspec != null)
 403  0
                         _resultCount = fspec.fetchLimit();
 404  0
                 evaluateExpression(expr);
 405  0
         }
 406  
 
 407  
         /* Creates and executes an UPDATE statement.
 408  
          * @see net.wotonomy.access.EOAdaptorChannel#updateValuesInRowsDescribedByQualifier(net.wotonomy.foundation.NSDictionary, net.wotonomy.control.EOQualifier, net.wotonomy.access.EOEntity)
 409  
          */
 410  
         public int updateValuesInRowsDescribedByQualifier(
 411  
                         NSDictionary row, EOQualifier q, EOEntity entity) {
 412  0
                 EOSQLExpression exp = adaptorContext().adaptor().expressionFactory().createExpression(entity);
 413  0
                 exp.prepareUpdateExpressionWithRow(row, q);
 414  0
                 evaluateExpression(exp);
 415  0
                 return _resultCount;
 416  
         }
 417  
 
 418  
         protected void conditionalBeginTransaction() {
 419  0
                 _transactionWasOpen = adaptorContext().hasOpenTransaction();
 420  0
                 if (!_transactionWasOpen)
 421  0
                         adaptorContext().beginTransaction();
 422  0
         }
 423  
 
 424  
         protected void conditionalCommitTransaction() {
 425  0
                 if (!_transactionWasOpen)
 426  0
                         adaptorContext().commitTransaction();
 427  0
                 _transactionWasOpen = false;
 428  0
         }
 429  
 
 430  
 }
 431  
 /*
 432  
 * $Log$
 433  
 * Revision 1.2  2006/02/18 22:59:22  cgruber
 434  
 * make it compile with maven dependencies and add a cvsignore.
 435  
 *
 436  
 * Revision 1.1  2006/02/16 13:22:23  cgruber
 437  
 * Check in all sources in eclipse-friendly maven-enabled packages.
 438  
 *
 439  
 * Revision 1.3  2003/08/14 02:15:11  chochos
 440  
 * added lots of comments
 441  
 *
 442  
 * Revision 1.2  2003/08/13 20:45:20  chochos
 443  
 * small fixes in evaluateExpression, which has been successfully tested with a SELECT statement.
 444  
 *
 445  
 * Revision 1.1  2003/08/13 20:12:48  chochos
 446  
 * a subclass of EOAdaptorChannel to be used with JDBC.
 447  
 *
 448  
 */
 449