Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
JDBCChannel |
|
| 4.6;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 |