View Javadoc

1   /*
2    * $Header: /cvsroot/jdbforms/dbforms/src/org/dbforms/event/datalist/dao/DataSourceJDBC.java,v 1.67 2006/02/05 13:37:55 hkollmann Exp $
3    * $Revision: 1.67 $
4    * $Date: 2006/02/05 13:37:55 $
5    *
6    * DbForms - a Rapid Application Development Framework
7    * Copyright (C) 2001 Joachim Peer <joepeer@excite.com>
8    *
9    * This library is free software; you can redistribute it and/or
10   * modify it under the terms of the GNU Lesser General Public
11   * License as published by the Free Software Foundation; either
12   * version 2.1 of the License, or (at your option) any later version.
13   *
14   * This library is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this library; if not, write to the Free Software
21   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
22   */
23  
24  package org.dbforms.event.datalist.dao;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import org.dbforms.config.Constants;
30  import org.dbforms.config.DbFormsConfigRegistry;
31  import org.dbforms.config.Field;
32  import org.dbforms.config.FieldTypes;
33  import org.dbforms.config.FieldValue;
34  import org.dbforms.config.FieldValues;
35  import org.dbforms.config.JDBCDataHelper;
36  import org.dbforms.config.ResultSetVector;
37  import org.dbforms.interfaces.DbEventInterceptorData;
38  
39  import org.dbforms.util.FileHolder;
40  import org.dbforms.util.UniqueIDGenerator;
41  import org.dbforms.util.Util;
42  
43  import java.sql.Connection;
44  import java.sql.PreparedStatement;
45  import java.sql.ResultSet;
46  import java.sql.ResultSetMetaData;
47  import java.sql.SQLException;
48  import java.sql.Statement;
49  
50  import java.util.ArrayList;
51  import java.util.HashMap;
52  import java.util.Iterator;
53  import java.util.List;
54  import java.util.Map;
55  import java.util.Vector;
56  
57  /***
58   * Special implementation of DataSource. This is the default class and deals
59   * with JDBC Connections.
60   * 
61   * @author hkk
62   */
63  public class DataSourceJDBC extends AbstractDataSource {
64  	private static Log logCat = LogFactory.getLog(DataSourceJDBC.class);
65  
66  	private Connection connection;
67  
68  	private List data;
69  
70  	private Map keys;
71  
72  	private ResultSet rs;
73  
74  	private Statement stmt;
75  
76  	private String connectionName;
77  
78  	private String query;
79  
80  	private String sqlFilter;
81  
82  	private String tableList;
83  
84  	private String whereClause;
85  
86  	private FieldValue[] filterConstraint;
87  
88  	private FieldValue[] orderConstraint;
89  
90  	private FieldValue[] sqlFilterParams;
91  
92  	private boolean calcRowCount = false;
93  
94  	private boolean fetchedAll = false;
95  
96  	private int colCount;
97  
98  	private int rowCount = 0;
99  
100 	/***
101 	 * Creates a new DataSourceJDBC object.
102 	 */
103 	public DataSourceJDBC() {
104 		data = new ArrayList();
105 		keys = new HashMap();
106 	}
107 
108 	/***
109 	 * DOCUMENT ME!
110 	 * 
111 	 * @param calcRowCount
112 	 *            The calcRowCount to set.
113 	 */
114 	public void setCalcRowCount(boolean calcRowCount) {
115 		this.calcRowCount = calcRowCount;
116 	}
117 
118 	/***
119 	 * DOCUMENT ME!
120 	 * 
121 	 * @return Returns the calcRowCount.
122 	 */
123 	public boolean isCalcRowCount() {
124 		return calcRowCount;
125 	}
126 
127 	/***
128 	 * Set the tableList and whererClause attributes used to build the SQL
129 	 * Select condition.
130 	 * 
131 	 * @param tableList
132 	 *            the table list string
133 	 * @param whereClause
134 	 *            the SQL where clause string
135 	 */
136 	public void setSelect(String tableList, String whereClause) {
137 		this.tableList = tableList;
138 		this.whereClause = whereClause;
139 	}
140 
141 	/***
142 	 * Set the filterConstraint and orderConstraint used to build the SQL Select
143 	 * condition.
144 	 * 
145 	 * @param filterConstraint
146 	 *            FieldValue array used to build a cumulation of rules for
147 	 *            filtering fields.
148 	 * @param orderConstraint
149 	 *            FieldValue array used to build a cumulation of rules for
150 	 *            ordering (sorting) and restricting fields.
151 	 * @param sqlFilter
152 	 *            sql condition to add to where clause
153 	 * @param sqlFilterParams
154 	 *            list of FieldValues to fill the sqlFilter with
155 	 */
156 	public void setSelect(FieldValue[] filterConstraint,
157 			FieldValue[] orderConstraint, String sqlFilter,
158 			FieldValue[] sqlFilterParams) {
159 		this.filterConstraint = filterConstraint;
160 		this.orderConstraint = orderConstraint;
161 		this.sqlFilter = sqlFilter;
162 		this.sqlFilterParams = sqlFilterParams;
163 	}
164 
165 	/***
166 	 * performs a delete in the DataSource
167 	 * 
168 	 * @param interceptorData
169 	 *            DOCUMENT ME!
170 	 * @param keyValuesStr
171 	 *            keyValueStr to the row to update <br>
172 	 *            key format: FieldID ":" Length ":" Value <br>
173 	 *            example: if key id = 121 and field id=2 then keyValueStr
174 	 *            contains "2:3:121" <br>
175 	 *            If the key consists of more than one fields, the key values
176 	 *            are seperated through "-" <br>
177 	 *            example: value of field 1=12, value of field 3=1992, then
178 	 *            we'll get "1:2:12-3:4:1992"
179 	 * 
180 	 * @throws SQLException
181 	 */
182 	public int doDelete(DbEventInterceptorData interceptorData,
183 			String keyValuesStr) throws SQLException {
184 		int res = 0;
185 		FieldValues fieldValues = null;
186 
187 		// get current blob files from database
188 		if (getTable().containsDiskblob()) {
189 			ResultSet diskblobs = null;
190 			StringBuffer queryBuf = new StringBuffer();
191 			queryBuf.append(getTable().getDisblobSelectStatement());
192 			queryBuf.append(" WHERE ");
193 			queryBuf.append(getTable().getWhereClauseForKeyFields(keyValuesStr));
194 
195 			PreparedStatement diskblobsPs = interceptorData.getConnection()
196 					.prepareStatement(queryBuf.toString());
197 
198 			try {
199 				getTable().populateWhereClauseWithKeyFields(keyValuesStr,
200 						diskblobsPs, 1);
201 
202 				diskblobs = diskblobsPs.executeQuery();
203 
204 				try {
205 					ResultSetVector rsv = new ResultSetVector(getTable(),
206 							getTable().getDiskblobs());
207 					rsv.addResultSet(interceptorData, diskblobs);
208 
209 					if (!ResultSetVector.isNull(rsv)) {
210 						rsv.moveFirst();
211 						fieldValues = rsv.getCurrentRowAsFieldValues();
212 					}
213 				} finally {
214 					diskblobs.close();
215 				}
216 			} finally {
217 				diskblobsPs.close();
218 			}
219 		}
220 
221 		// 20021031-HKK: Build in table!!
222 		PreparedStatement ps = interceptorData.getConnection()
223 				.prepareStatement(getTable().getDeleteStatement(keyValuesStr));
224 
225 		try {
226 			// now we provide the values
227 			// of the key-fields, so that the WHERE clause matches the right
228 			// dataset!
229 			getTable().populateWhereClauseWithKeyFields(keyValuesStr, ps, 1);
230 
231 			// finally execute the query
232 			res = ps.executeUpdate();
233 
234 			if (fieldValues != null) {
235 				deleteBlobFilesFromDisk(fieldValues);
236 			}
237 		} finally {
238 			ps.close();
239 		}
240 		return res;
241 	}
242 
243 	/***
244 	 * Performs an insert into the DataSource
245 	 * 
246 	 * @param interceptorData
247 	 *            DOCUMENT ME!
248 	 * @param fieldValues
249 	 *            FieldValues to insert
250 	 * 
251 	 * @throws SQLException
252 	 */
253 	public int doInsert(DbEventInterceptorData interceptorData,
254 			FieldValues fieldValues) throws SQLException {
255 		String query = getTable().getInsertStatement(fieldValues);
256 
257 		PreparedStatement ps = interceptorData.getConnection()
258 				.prepareStatement(query);
259 
260 		int res = 0;
261 		try {
262 			// execute the query & throws an exception if something goes wrong
263 			fillWithData(ps, fieldValues);
264 			res = ps.executeUpdate();
265 		} finally {
266 			ps.close();
267 		}
268 
269 		// now handle blob files
270 		saveBlobFilesToDisk(fieldValues);
271 		return res;
272 	}
273 
274 	/***
275 	 * Performs an update into the DataSource
276 	 * 
277 	 * @param interceptorData
278 	 *            DOCUMENT ME!
279 	 * @param fieldValues
280 	 *            FieldValues to update
281 	 * @param keyValuesStr
282 	 *            keyValueStr to the row to update <br>
283 	 *            key format: FieldID ":" Length ":" Value <br>
284 	 *            example: if key id = 121 and field id=2 then keyValueStr
285 	 *            contains "2:3:121" <br>
286 	 *            If the key consists of more than one fields, the key values
287 	 *            are seperated through "-" <br>
288 	 *            example: value of field 1=12, value of field 3=1992, then
289 	 *            we'll get "1:2:12-3:4:1992"
290 	 * 
291 	 * @throws SQLException
292 	 */
293 	public int doUpdate(DbEventInterceptorData interceptorData,
294 			FieldValues fieldValues, String keyValuesStr) throws SQLException {
295 		int res = 0;
296 
297 		String query = getTable().getUpdateStatement(fieldValues, keyValuesStr);
298 		PreparedStatement ps = interceptorData.getConnection()
299 				.prepareStatement(query);
300 		try {
301 			int col = fillWithData(ps, fieldValues);
302 			getTable().populateWhereClauseWithKeyFields(keyValuesStr, ps, col);
303 
304 			// we are now ready to execute the query
305 			res = ps.executeUpdate();
306 			logCat.info("update rows: " + String.valueOf(res));
307 
308 		} finally {
309 			ps.close();
310 		}
311 
312 		// now handle blob files
313 		saveBlobFilesToDisk(fieldValues);
314 		return res;
315 	}
316 
317 	/***
318 	 * set the connection parameter for the DataSouce. virtual method, if you
319 	 * need the connection data you must override the method In this special
320 	 * case we need our own connection to save it in the session.
321 	 * 
322 	 * @param con
323 	 *            the JDBC Connection object
324 	 * @param dbConnectionName
325 	 *            name of the used db connection. Can be used to get an own db
326 	 *            connection, e.g. to hold it during the session (see
327 	 *            DataSourceJDBC for example!)
328 	 */
329 	protected void setConnection(Connection con, String dbConnectionName) {
330 		close();
331 
332 		// To prevent empty connection name. We always need our own connection!
333 		connectionName = Util.isNull(dbConnectionName) ? "default"
334 				: dbConnectionName;
335 	}
336 
337 	/***
338 	 * Get the requested row as array of objects.
339 	 * 
340 	 * @param i
341 	 *            the row number
342 	 * 
343 	 * @return the requested row as array of objects
344 	 * 
345 	 * @throws SQLException
346 	 *             if any error occurs
347 	 */
348 	protected final Object[] getRow(int i) throws SQLException {
349 		Object[] result = null;
350 
351 		if (i >= 0) {
352 			if (i < data.size()) {
353 				result = (Object[]) data.get(i);
354 			} else {
355 				if (!fetchedAll) {
356 					while (rs.next()) {
357 						addRow();
358 						if (i < data.size()) {
359 							result = (Object[]) data.get(i);
360 							break;
361 						}
362 					}
363 					checkResultSetEnd();
364 				}
365 			}
366 		}
367 
368 		return result;
369 	}
370 
371 	/***
372 	 * Release all the resources holded by this datasource. <br>
373 	 * Clean the underlying data and keys vectors, then close the JDBC
374 	 * resultSet, statement and connection objects.
375 	 */
376 	protected final void close() {
377 		if (data != null) {
378 			data.clear();
379 		}
380 
381 		if (keys != null) {
382 			keys.clear();
383 		}
384 
385 		closeConnection();
386 
387 		// reset fetched all flag. So DataSource can be reopened after close!
388 		fetchedAll = false;
389 	}
390 
391 	/***
392 	 * Find the first row of the internal data vector.
393 	 * 
394 	 * @param startRow
395 	 *            the string identifying the initial row
396 	 * 
397 	 * @return the start row position
398 	 * 
399 	 * @throws SQLException
400 	 *             if any error occurs
401 	 */
402 	protected final int findStartRow(String startRow) throws SQLException {
403 		int result = 0;
404 		boolean found = false;
405 
406 		if (startRow != null) {
407 			Integer i = (Integer) keys.get(startRow);
408 
409 			if (i != null) {
410 				result = i.intValue();
411 				found = true;
412 			}
413 
414 			if (!found && !fetchedAll) {
415 				while (rs.next()) {
416 					String key = addRow();
417 
418 					if (startRow.equals(key)) {
419 						result = data.size() - 1;
420 
421 						break;
422 					}
423 				}
424 
425 				checkResultSetEnd();
426 			}
427 		}
428 
429 		return result;
430 	}
431 
432 	/***
433 	 * return true if there are more records to fetch then the given record
434 	 * number
435 	 * 
436 	 * @param i
437 	 *            index of last fetched row.
438 	 * 
439 	 * @return true if there are more records to fetch then the given record
440 	 *         number
441 	 * 
442 	 * @throws SQLException
443 	 */
444 	protected final boolean hasMore(int i) throws SQLException {
445 		return !fetchedAll || (i < data.size());
446 	}
447 
448 	/***
449 	 * Open this datasource and initialize its resources.
450 	 * 
451 	 * @throws SQLException
452 	 *             if any error occurs
453 	 */
454 	protected void open() throws SQLException {
455 		if (!fetchedAll && (rs == null)) {
456 			if ((connection == null) || connection.isClosed()) {
457 				try {
458 					this.connection = DbFormsConfigRegistry.instance().lookup()
459 							.getConnection(connectionName);
460 				} catch (Exception e) {
461 					logCat.error("open", e);
462 				}
463 			}
464 
465 			if (connection == null) {
466 				throw new SQLException("no connection found!");
467 			}
468 
469 			if (Util.isNull(whereClause)) {
470 				query = getTable().getSelectQuery(getTable().getFields(),
471 						filterConstraint, orderConstraint, sqlFilter,
472 						Constants.COMPARE_NONE);
473 				stmt = connection.prepareStatement(query);
474 
475 				if (stmt == null) {
476 					throw new SQLException("no statement: " + query);
477 				}
478 
479 				// 20040730-HKK: To workaround a bug inside mysql driver
480 				// stmt.setFetchSize(Integer.MIN_VALUE);
481 				rs = getTable().getDoSelectResultSet(filterConstraint,
482 						orderConstraint, sqlFilterParams,
483 						Constants.COMPARE_NONE, (PreparedStatement) stmt);
484 			} else {
485 				query = getTable().getFreeFormSelectQuery(
486 						getTable().getFields(), whereClause, tableList);
487 				stmt = connection.createStatement();
488 
489 				if (stmt == null) {
490 					throw new SQLException("no statement");
491 				}
492 
493 				rs = stmt.executeQuery(query);
494 			}
495 
496 			ResultSetMetaData rsmd = rs.getMetaData();
497 			colCount = rsmd.getColumnCount();
498 
499 			if (isCalcRowCount()) {
500 				Field f = new Field();
501 				f.setName("count(*) cnt");
502 				Vector v = new Vector();
503 				v.add(f);
504 				// v.addAll(getTable().getFields());
505 				ResultSet prs = null;
506 				if (Util.isNull(whereClause)) {
507 					String pquery = getTable().getSelectQuery(v,
508 							filterConstraint, orderConstraint, sqlFilter,
509 							Constants.COMPARE_NONE);
510 					PreparedStatement pstmt = connection
511 							.prepareStatement(pquery);
512 
513 					if (pstmt == null) {
514 						throw new SQLException("no statement: " + pquery);
515 					}
516 
517 					prs = getTable().getDoSelectResultSet(filterConstraint,
518 							orderConstraint, sqlFilterParams,
519 							Constants.COMPARE_NONE, pstmt);
520 				} else {
521 					String pquery = getTable().getFreeFormSelectQuery(v,
522 							whereClause, tableList);
523 					Statement pstmt = connection.createStatement();
524 
525 					if (pstmt == null) {
526 						throw new SQLException("no statement");
527 					}
528 
529 					prs = pstmt.executeQuery(pquery);
530 				}
531 				prs.next();
532 				rowCount = prs.getInt(prs.findColumn("cnt"));
533 			}
534 		}
535 	}
536 
537 	/***
538 	 * Get the size of the data vector.
539 	 * 
540 	 * @return the size of the data vector
541 	 * 
542 	 * @throws SQLException
543 	 *             if any error occurs
544 	 */
545 	protected final int size() throws SQLException {
546 		// Workaround for bug in firebird driver: After reaching next the next
547 		// call
548 		// to next will start at the beginning of the resultset.
549 		// rs.next will return true, fetching data will get an
550 		// NullPointerException.
551 		// Catch this error and do an break!
552 		if (!fetchedAll) {
553 			while (rs.next()) {
554 				try {
555 					addRow();
556 				} catch (Exception e) {
557 					logCat.error("size", e);
558 					break;
559 				}
560 			}
561 			closeConnection();
562 		}
563 		return data.size();
564 	}
565 
566 	/***
567 	 * DOCUMENT ME!
568 	 * 
569 	 * @return Returns the rowCount.
570 	 */
571 	protected int getRowCount() {
572 		return rowCount;
573 	}
574 
575 	private String addRow() throws SQLException {
576 		Integer j = new Integer(data.size());
577 		Object[] objectRow = new Object[colCount];
578 		String[] stringRow = new String[colCount];
579 
580 		for (int i = 0; i < colCount; i++) {
581 			objectRow[i] = JDBCDataHelper.getData(rs, getTable().getField(i)
582 					.getEscaper(), i + 1);
583 			stringRow[i] = (objectRow[i] != null) ? objectRow[i].toString()
584 					: null;
585 		}
586 
587 		data.add(objectRow);
588 
589 		String key = getTable().getKeyPositionString(stringRow);
590 		keys.put(key, j);
591 
592 		return key;
593 	}
594 
595 	private void checkResultSetEnd() throws SQLException {
596 		if (rs.next()) {
597 			addRow();
598 		} else {
599 			closeConnection();
600 		}
601 	}
602 
603 	private void closeConnection() {
604 		fetchedAll = true;
605 
606 		if (rs != null) {
607 			try {
608 				rs.close();
609 			} catch (SQLException e) {
610 				logCat.info("closeConnection", e);
611 			}
612 			rs = null;
613 		}
614 
615 		if (stmt != null) {
616 			try {
617 				stmt.close();
618 			} catch (SQLException e) {
619 				logCat.info("closeConnection", e);
620 			}
621 			stmt = null;
622 		}
623 
624 		if (connection != null) {
625 			try {
626 				if (!connection.isClosed()) {
627 					connection.close();
628 				}
629 			} catch (SQLException e) {
630 				logCat.info("closeConnection", e);
631 			}
632 			connection = null;
633 		}
634 	}
635 
636 	// ------------------------------ DAO methods
637 	// ---------------------------------
638 	private int fillWithData(PreparedStatement ps, FieldValues fieldValues)
639 			throws SQLException {
640 		// now we provide the values;
641 		// every key is the parameter name from of the form page;
642 		Iterator e = fieldValues.keys();
643 		int col = 1;
644 
645 		while (e.hasNext()) {
646 			String fieldName = (String) e.next();
647 			Field curField = getTable().getFieldByName(fieldName);
648 
649 			if ((curField != null) && !getTable().isCalcField(curField.getId()) && Util.isNull(curField.getExpression())) {
650 				FieldValue fv = fieldValues.get(fieldName);
651 
652 				logCat.debug("Retrieved curField:" + curField.getName()
653 						+ " type:" + curField.getType());
654 
655 				int fieldType = curField.getType();
656 				Object value = null;
657 
658 				if (fieldType == FieldTypes.BLOB) {
659 					// in case of a BLOB we supply the FileHolder object to
660 					// SqlUtils for further operations
661 					if (fv.getFileHolder() == null) { // if the blob field is
662 						// updated from within
663 						// textarea
664 						value = fv.getFieldValue();
665 					} else { // if we have a file upload
666 						value = fv.getFileHolder();
667 					}
668 				} else if (fieldType == FieldTypes.DISKBLOB) {
669 					FileHolder fileHolder = fv.getFileHolder();
670 
671 					// encode fileName
672 					String fileName = fileHolder.getFileName();
673 
674 					// check if we need to store it encoded or not
675 					if (curField.hasEncodedSet()) {
676 						int dotIndex = fileName.lastIndexOf('.');
677 						String suffix = (dotIndex != -1) ? fileName
678 								.substring(dotIndex) : "";
679 						fileHolder.setFileName(UniqueIDGenerator.getUniqueID()
680 								+ suffix);
681 
682 						// a diskblob gets stored to db as an ordinary string
683 						// (it's only the reference!)
684 						value = fileHolder.getFileName();
685 					} else {
686 						// a diskblob gets stored to db as an ordinary string
687 						// (it's only the reference!)
688 						value = fileName;
689 					}
690 				} else {
691 					// in case of simple db types we just supply a string
692 					// representing the value of the fields
693 					value = fv.getFieldValueAsObject();
694 				}
695 
696 				logCat.info("field=" + curField.getName() + " col=" + col
697 						+ " value=" + value + " type=" + fieldType);
698 				JDBCDataHelper.fillWithData(ps, curField.getEscaper(), col,
699 						value, fieldType, getTable().getBlobHandlingStrategy());
700 				col++;
701 			}
702 		}
703 
704 		return col;
705 	}
706 }