View Javadoc

1   /*
2    * $Header: /cvsroot/jdbforms/dbforms/src/org/dbforms/config/Table.java,v 1.74 2006/02/17 11:40:17 hkollmann Exp $
3    * $Revision: 1.74 $
4    * $Date: 2006/02/17 11:40:17 $
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.config;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import org.dbforms.interfaces.DbEventInterceptorData;
30  import org.dbforms.interfaces.IDbEventInterceptor;
31  import org.dbforms.interfaces.IEscaper;
32  import org.dbforms.util.MessageResourcesInternal;
33  import org.dbforms.util.ParseUtil;
34  import org.dbforms.util.ReflectionUtil;
35  import org.dbforms.util.SqlUtil;
36  import org.dbforms.util.StringUtil;
37  import org.dbforms.util.Util;
38  
39  import java.sql.PreparedStatement;
40  import java.sql.ResultSet;
41  import java.sql.SQLException;
42  import java.sql.Statement;
43  
44  import java.util.Collection;
45  import java.util.Hashtable;
46  import java.util.Iterator;
47  import java.util.Locale;
48  import java.util.StringTokenizer;
49  import java.util.Vector;
50  
51  import java.io.Serializable;
52  
53  import javax.servlet.http.HttpServletRequest;
54  
55  /***
56   * This class represents a table tag in dbforms-config.xml (dbforms config xml
57   * file). <br>
58   * It also defines a lot of methods for preparing and actually performing
59   * operations (queries) on the table
60   * 
61   * @author Joe Peer
62   */
63  public class Table implements Serializable {
64  	/*** either "classic" or "interceptor", default is "interceptor" */
65  	public static final int BLOB_INTERCEPTOR = 0;
66  
67  	/*** DOCUMENT ME! */
68  	public static final int BLOB_CLASSIC = 1;
69  
70  	private static final int MAXFIELDS = 1000;
71  
72  	/*** DOCUMENT ME! */
73  	protected static final int DB_FIELD = 0;
74  
75  	/*** DOCUMENT ME! */
76  	protected static final int SEARCH_FIELD = 1;
77  
78  	/*** DOCUMENT ME! */
79  	protected static final int CALC_FIELD = 2;
80  
81  	/***
82  	 * access control list for this object (if null, then its open to all users
83  	 * for all operations). Defined in dbforms-config.xml
84  	 */
85  	private GrantedPrivileges grantedPrivileges = null;
86  
87  	private Hashtable calcFieldsNameHash = new Hashtable();
88  
89  	/*** structure for quick acessing of fields "by name" */
90  	private Hashtable fieldNameHash = new Hashtable();
91  
92  	/*** access foreign key by name */
93  	private Hashtable foreignKeyNameHash = new Hashtable();
94  
95  	private IEscaper escaper = null;
96  
97  	/*** log4j category */
98  	private static Log logCat = LogFactory.getLog(Table.class.getName());
99  
100 	/*** some sort of alias to set in dbforms-config, not used yet */
101 	private String alias = null;
102 
103 	private String blobHandling = null;
104 
105 	/*** reference to the DataAccess Class */
106 	private String dataAccessClass = null;
107 
108 	/*** Holds value of property defaultVisibleFields. */
109 	private String defaultVisibleFields;
110 
111 	/*** Holds value of property defaultVisibleFieldsFormat. */
112 	private String defaultVisibleFieldsFormat;
113 
114 	private String escaperClass = null;
115 
116 	/*** the name of the Table */
117 	private String name;
118 
119 	/***
120 	 * instance variables concerned with the ORDERING/SORTING characterstics of
121 	 * that table (ordering and sorting is used synonym here)
122 	 */
123 	private String orderBy;
124 
125 	/*** reference to a TableEvents object */
126 	private TableEvents tableEvents = null;
127 
128 	private Vector calcFields = new Vector();
129 
130 	/***
131 	 * subset of "fields", containting those keys which represent DISKBLOBs
132 	 * (wondering about that term? -> see docu)
133 	 */
134 	private Vector diskblobs = new Vector();
135 
136 	/*** the Field-Objects this table constists of */
137 	private Vector fields = new Vector();
138 
139 	private Vector foreignKeys = new Vector();
140 
141 	/*** application hookups */
142 	private Vector interceptors = new Vector();
143 
144 	/*** subset of "fields", containting those keys which represent keys */
145 	private Vector key = new Vector();
146 
147 	/*** the order-by clause, as specified in dbforms-config.xml (optional!) */
148 	private FieldValue[] defaultOrder;
149 
150 	private int blobHandlingStrategy = BLOB_INTERCEPTOR;
151 
152 	/***
153 	 * id of this table (generated by DbFormsConfig when parsing
154 	 * dbforms-config.xml)
155 	 */
156 	private int id;
157 
158 	/***
159 	 * DOCUMENT ME!
160 	 * 
161 	 * @param alias
162 	 *            the alias to set
163 	 */
164 	public void setAlias(String alias) {
165 		this.alias = alias;
166 	}
167 
168 	/***
169 	 * DOCUMENT ME!
170 	 * 
171 	 * @return String
172 	 */
173 	public String getAlias() {
174 		return alias;
175 	}
176 
177 	/***
178 	 * for digester only, see blobHandlingStrategy
179 	 * 
180 	 * @param blobHandling
181 	 *            config parameter
182 	 */
183 	public void setBlobHandling(String blobHandling) {
184 		this.blobHandling = blobHandling;
185 
186 		if ("classic".equals(blobHandling)) {
187 			this.blobHandlingStrategy = BLOB_CLASSIC;
188 		} else {
189 			this.blobHandlingStrategy = BLOB_INTERCEPTOR;
190 		}
191 	}
192 
193 	/***
194 	 * for digester only, see blobHandlingStrategy
195 	 * 
196 	 * @return String
197 	 */
198 	public String getBlobHandling() {
199 		return blobHandling;
200 	}
201 
202 	/***
203 	 * DOCUMENT ME!
204 	 * 
205 	 * @return DOCUMENT ME!
206 	 */
207 	public int getBlobHandlingStrategy() {
208 		return this.blobHandlingStrategy;
209 	}
210 
211 	/***
212 	 * DOCUMENT ME!
213 	 * 
214 	 * @return
215 	 */
216 	public Vector getCalcFields() {
217 		return calcFields;
218 	}
219 
220 	/***
221 	 * Sets the dataAccessClass.
222 	 * 
223 	 * @param dataAccessClass
224 	 *            The dataAccessClass to set
225 	 */
226 	public void setDataAccessClass(String dataAccessClass) {
227 		this.dataAccessClass = dataAccessClass;
228 	}
229 
230 	/***
231 	 * DOCUMENT ME!
232 	 * 
233 	 * @return String
234 	 */
235 	public String getDataAccessClass() {
236 		return dataAccessClass;
237 	}
238 
239 	/***
240 	 * Return the datastructure containing info about the default sorting
241 	 * behavior of this table.
242 	 * 
243 	 * @return the datastructure containing info about the default sorting
244 	 *         behavior of this table.
245 	 */
246 	public FieldValue[] getDefaultOrder() {
247 		return defaultOrder;
248 	}
249 
250 	/***
251 	 * Setter for property defaultVisibleFields.
252 	 * 
253 	 * @param defaultVisibleFields
254 	 *            New value of property defaultVisibleFields.
255 	 */
256 	public void setDefaultVisibleFields(String defaultVisibleFields) {
257 		this.defaultVisibleFields = defaultVisibleFields;
258 	}
259 
260 	/***
261 	 * Getter for property defaultVisibleFields.
262 	 * 
263 	 * @return Value of property defaultVisibleFields.
264 	 */
265 	public String getDefaultVisibleFields() {
266 		return this.defaultVisibleFields;
267 	}
268 
269 	/***
270 	 * Setter for property defaultVisibleFieldsFormat.
271 	 * 
272 	 * @param defaultVisibleFieldsFormat
273 	 *            New value of property defaultVisibleFieldsFormat.
274 	 */
275 	public void setDefaultVisibleFieldsFormat(String defaultVisibleFieldsFormat) {
276 		this.defaultVisibleFieldsFormat = defaultVisibleFieldsFormat;
277 	}
278 
279 	/***
280 	 * Getter for property defaultVisibleFieldsFormat.
281 	 * 
282 	 * @return Value of property defaultVisibleFieldsFormat.
283 	 */
284 	public String getDefaultVisibleFieldsFormat() {
285 		return this.defaultVisibleFieldsFormat;
286 	}
287 
288 	/***
289 	 * Returns SQL delete statement, used by deleteEvent.
290 	 * 
291 	 * @return the SQL delete statement
292 	 */
293 	public String getDeleteStatement(String keyValString) {
294 		// now we start building the DELETE statement
295 		StringBuffer queryBuf = new StringBuffer();
296 		queryBuf.append("DELETE FROM ");
297 		queryBuf.append(getQueryToChange());
298 		queryBuf.append(" WHERE ");
299 		queryBuf.append(getWhereClauseForKeyFields(keyValString));
300 
301 		logCat.info("::deleteQuery - [" + queryBuf.toString() + "]");
302 		return queryBuf.toString();
303 	}
304 
305 	/***
306 	 * Generates part of a field list for a SQL SELECT clause selecting the
307 	 * DISKBLOB fields from a table (used by DeleteEvent to maintain data
308 	 * consistence).
309 	 * 
310 	 * @return a part of a field list for a SQL SELECT clause selecting the
311 	 *         DISKBLOB fields from a table
312 	 */
313 	public String getDisblobSelectStatement() {
314 		StringBuffer buf = new StringBuffer();
315 		buf.append("SELECT ");
316 
317 		int cnt = diskblobs.size();
318 
319 		for (int i = 0; i < cnt; i++) {
320 			Field diskblobField = (Field) diskblobs.elementAt(i);
321 
322 			// get the name of the encoded key field
323 			buf.append(diskblobField.getName());
324 
325 			if (i < (cnt - 1)) {
326 				buf.append(", ");
327 			}
328 		}
329 
330 		buf.append(" FROM ");
331 		buf.append(getQueryFrom());
332 
333 		return buf.toString();
334 	}
335 
336 	/***
337 	 * Returns a Vector of Field-Objects representing fields of type "DISKBLOB"
338 	 * 
339 	 * @return a Vector of Field-Objects representing fields of type "DISKBLOB"
340 	 */
341 	public Vector getDiskblobs() {
342 		return diskblobs;
343 	}
344 
345 	/***
346 	 * Get the SQL ResultSet from the query builded using the input data. Order
347 	 * of parts: 1. sqlFilter 2. where condition generated from searching 3.
348 	 * where condition generated from ordering Generating the query in
349 	 * getSelectQuery() must match this order!
350 	 * 
351 	 * @param fvEqual
352 	 *            FieldValue array used to restrict a set in a subform where all
353 	 *            "childFields" in the resultset match their respective
354 	 *            "parentFields" in main form
355 	 * @param fvOrder
356 	 *            FieldValue array used to build a cumulation of rules for
357 	 *            ordering (sorting) and restricting fields
358 	 * @param sqlFilterParams
359 	 *            DOCUMENT ME!
360 	 * @param compareMode
361 	 *            the value of the compare mode
362 	 * @param ps
363 	 *            the PreparedStatement object
364 	 * 
365 	 * @return a ResultSet object
366 	 * 
367 	 * @throws SQLException
368 	 *             if any error occurs
369 	 */
370 	public ResultSet getDoSelectResultSet(FieldValue[] fvEqual,
371 			FieldValue[] fvOrder, FieldValue[] sqlFilterParams,
372 			int compareMode, PreparedStatement ps) throws SQLException {
373 		// the index of the first NOT POPULATED placeholder;
374 		int curCol = 1;
375 
376 		logCat.debug("###getDoSelectResultSet pos1");
377 
378 		if (!FieldValue.isNull(sqlFilterParams)) {
379 			curCol = populateWhereEqualsClause(sqlFilterParams, ps, curCol);
380 		}
381 
382 		logCat.debug("###getDoSelectResultSet pos2");
383 
384 		if (!FieldValue.isNull(fvEqual)) {
385 			curCol = populateWhereEqualsClause(fvEqual, ps, curCol);
386 		}
387 
388 		logCat.debug("###getDoSelectResultSet pos3");
389 
390 		if ((compareMode != Constants.COMPARE_NONE) && (fvOrder != null)
391 				&& (fvOrder.length > 0)) {
392 			populateWhereAfterClause(fvOrder, ps, curCol);
393 		}
394 
395 		logCat.debug("###getDoSelectResultSet pos3");
396 
397 		ResultSet result = null;
398 
399 		try {
400 			result = ps.executeQuery();
401 		} catch (SQLException sqle) {
402 			SqlUtil.logSqlException(sqle);
403 			throw sqle;
404 		}
405 
406 		return result;
407 	}
408 
409 	/***
410 	 * DOCUMENT ME!
411 	 * 
412 	 * @return DOCUMENT ME!
413 	 */
414 	public IEscaper getEscaper() {
415 		if (escaper == null) {
416 			String s = getEscaperClass();
417 
418 			if (!Util.isNull(s)) {
419 				try {
420 					escaper = (IEscaper) ReflectionUtil.newInstance(s);
421 				} catch (Exception e) {
422 					logCat
423 							.error("cannot create the new escaper [" + s + "]",
424 									e);
425 				}
426 			}
427 
428 			if ((escaper == null)) {
429 				try {
430 					escaper = DbFormsConfigRegistry.instance().lookup()
431 							.getEscaper();
432 				} catch (Exception e) {
433 					logCat.error("cannot create the new default escaper", e);
434 				}
435 			}
436 		}
437 
438 		return escaper;
439 	}
440 
441 	/***
442 	 * DOCUMENT ME!
443 	 * 
444 	 * @param string
445 	 */
446 	public void setEscaperClass(String string) {
447 		escaperClass = string;
448 	}
449 
450 	/***
451 	 * DOCUMENT ME!
452 	 * 
453 	 * @return
454 	 */
455 	public String getEscaperClass() {
456 		return escaperClass;
457 	}
458 
459 	/***
460 	 * Returns the Field-Objet with specified id.
461 	 * 
462 	 * @param fieldId
463 	 *            The id of the field to be returned
464 	 * 
465 	 * @return the Field object having the input id
466 	 */
467 	public Field getField(int fieldId) {
468 		Field f = null;
469 
470 		if (checkFieldId(CALC_FIELD, fieldId)) {
471 			f = (Field) calcFields
472 					.elementAt(decodeFieldId(CALC_FIELD, fieldId));
473 		} else {
474 			f = (Field) fields.elementAt(fieldId);
475 		}
476 
477 		return f;
478 	}
479 
480 	public boolean isCalcField(int fieldId) {
481 		return checkFieldId(CALC_FIELD, fieldId); 
482 	}
483 
484 	/***
485 	 * Returns the field-objects as specified by name (or null if no field with
486 	 * the specified name exists in this table).
487 	 * 
488 	 * @param name
489 	 *            The name of the field
490 	 * 
491 	 * @return Filed object having the input name
492 	 */
493 	public Field getFieldByName(String aname) {
494 		Field f = (Field) fieldNameHash.get(aname);
495 		if (f == null) {
496 			f = (Field) calcFieldsNameHash.get(aname);
497 		}
498 		return f;
499 	}
500 
501 	/***
502 	 * We have the field ID - we need the field name
503 	 * 
504 	 * @param fieldID
505 	 *            fieldID to get field name from
506 	 * 
507 	 * @return the field name
508 	 */
509 	public String getFieldName(int fieldID) {
510 		Field f = (Field) getFields().elementAt(fieldID);
511 
512 		return (f.getName());
513 	}
514 
515 	/***
516 	 * This method parses a position string and build a data structure
517 	 * representing the values of the fields decoded from the position. <br>
518 	 * #fixme: replace seperator-based tokenization by better algoithm!
519 	 * 
520 	 * @param position
521 	 *            the position string
522 	 * 
523 	 * @return the HashTable containing the FieldValues of the fields decoded,
524 	 *         key of the HashTable is the fieldName!
525 	 */
526 	public FieldValues getFieldValues(String position) {
527 		// 20020705-HKK: Position maybe string with length = 0!!!!
528 		if (Util.isNull(position)) {
529 			return null;
530 		}
531 
532 		// trailing blanks are significant for CHAR database fields
533 		// position = position.trim();
534 		// 20021128-HKK: catch errors!
535 		// 2003-03-29 HKK: Change from Hashtable to FieldValueTable
536 		FieldValues result = new FieldValues();
537 
538 		try {
539 			int startIndex = 0;
540 			boolean endOfString = false;
541 
542 			// looping through the string
543 			while (!endOfString) {
544 				int firstColon = position.indexOf(':', startIndex);
545 				int secondColon = position.indexOf(':', firstColon + 1);
546 
547 				if ((firstColon == -1) && (secondColon == -1)) {
548 					return null;
549 				}
550 
551 				String fieldIdStr = position.substring(startIndex, firstColon);
552 				int fieldId = Integer.parseInt(fieldIdStr);
553 
554 				String valueLengthStr = position.substring(firstColon + 1,
555 						secondColon);
556 				int valueLength = Integer.parseInt(valueLengthStr);
557 
558 				int controlIndex = secondColon + 1 + valueLength;
559 
560 				// make already be trimmed ... avoid substring exception
561 				String valueStr = (controlIndex < position.length()) ? position
562 						.substring(secondColon + 1, controlIndex) : position
563 						.substring(secondColon + 1);
564 
565 				Field f = getField(fieldId);
566 				FieldValue fv = new FieldValue(f, valueStr);
567 				result.put(fv);
568 
569 				if (controlIndex == position.length()) {
570 					endOfString = true;
571 				} else if (controlIndex > position.length()) {
572 					logCat.warn("Controlbyte wrong but continuing execution");
573 					endOfString = true;
574 				} else {
575 					char controlByte = position.charAt(controlIndex);
576 
577 					if (controlByte != '-') {
578 						logCat.error("Controlbyte wrong, abandon execution");
579 						throw new IllegalArgumentException();
580 					}
581 
582 					startIndex = controlIndex + 1;
583 
584 					if (position.length() == startIndex) {
585 						endOfString = true;
586 					}
587 				}
588 			}
589 		} catch (Exception e) {
590 			logCat.error("::getFieldValuesFromPositionAsHt - exception:", e);
591 			result = null;
592 		}
593 
594 		return result;
595 	}
596 
597 	/***
598 	 * Returns the vector of fields this table constists of
599 	 * 
600 	 * @return the vector of fields this table constists of
601 	 */
602 	public Vector getFields() {
603 		return fields;
604 	}
605 
606 	/***
607 	 * Initialize the filterFieldValues array.
608 	 * 
609 	 * @param filter
610 	 *            the filter string
611 	 * @param locale
612 	 *            the table object
613 	 * 
614 	 * @return an initialized FieldValue array
615 	 * 
616 	 * @todo add MORE docs here !!!
617 	 */
618 	public FieldValue[] getFilterFieldArray(String filter, Locale locale) {
619 		if (Util.isNull(filter)) {
620 			return null;
621 		}
622 		// 1 to n fields may be mapped
623 		Vector keyValPairs = StringUtil.splitString(filter, ",;");
624 
625 		// ~ no longer used as separator!
626 		int len = keyValPairs.size();
627 
628 		FieldValue[] result = new FieldValue[len];
629 
630 		for (int i = 0; i < len; i++) {
631 			int operator = 0;
632 			boolean isLogicalOR = false;
633 			int jump = 1;
634 			String aKeyValPair = (String) keyValPairs.elementAt(i);
635 
636 			// i.e "id=2"
637 			logCat.debug("initFilterFieldValues: aKeyValPair = " + aKeyValPair);
638 
639 			// Following code could be optimized, however I did not want to make
640 			// too many changes...
641 			int n;
642 
643 			// Check for Not Equal
644 			if ((n = aKeyValPair.indexOf("<>")) != -1) {
645 				// Not Equal found! - Store the operation for use later on
646 				operator = Constants.FILTER_NOT_EQUAL;
647 				jump = 2;
648 			} else if ((n = aKeyValPair.indexOf(">=")) != -1) {
649 				// Check for GreaterThanEqual
650 				// GreaterThenEqual found! - Store the operation for use later
651 				// on
652 				operator = Constants.FILTER_GREATER_THEN_EQUAL;
653 				jump = 2;
654 			} else if ((n = aKeyValPair.indexOf('>')) != -1) {
655 				// Check for GreaterThan
656 				// GreaterThen found! - Store the operation for use later on
657 				operator = Constants.FILTER_GREATER_THEN;
658 			} else if ((n = aKeyValPair.indexOf("<=")) != -1) {
659 				// Check for SmallerThenEqual
660 				// SmallerThenEqual found! - Store the operation for use later
661 				// on
662 				operator = Constants.FILTER_SMALLER_THEN_EQUAL;
663 				jump = 2;
664 			} else if ((n = aKeyValPair.indexOf('<')) != -1) {
665 				// Check for SmallerThen
666 				// SmallerThen found! - Store the operation for use later on
667 				operator = Constants.FILTER_SMALLER_THEN;
668 			} else if ((n = aKeyValPair.indexOf('=')) != -1) {
669 				// Check for equal
670 				// Equal found! - Store the operator for use later on
671 				operator = Constants.FILTER_EQUAL;
672 			} else if ((n = aKeyValPair.indexOf('~')) != -1) {
673 				// Check for LIKE
674 				// LIKE found! - Store the operator for use later on
675 				operator = Constants.FILTER_LIKE;
676 			} else if ((n = aKeyValPair.toUpperCase().indexOf("NOTISNULL")) != -1) {
677 				// Check for not is null
678 				// LIKE found! - Store the operator for use later on
679 				jump = -1;
680 				operator = Constants.FILTER_NOT_NULL;
681 			} else if ((n = aKeyValPair.toUpperCase().indexOf("ISNULL")) != -1) {
682 				// Check for null
683 				// LIKE found! - Store the operator for use later on
684 				jump = -1;
685 				operator = Constants.FILTER_NULL;
686 			}
687 
688 			// PG - At this point, I have set my operator and I should have a
689 			// valid index.
690 			// Note that the original code did not handle the posibility of not
691 			// finding an index
692 			// (value = -1)...
693 			String fieldName = aKeyValPair.substring(0, n).trim();
694 
695 			// i.e "id"
696 			logCat.debug("Filter field=" + fieldName);
697 
698 			if (fieldName.charAt(0) == '|') {
699 				// This filter must be associated to a logical OR, clean out the
700 				// indicator...
701 				fieldName = fieldName.substring(1);
702 				isLogicalOR = true;
703 			}
704 
705 			Field filterField = getFieldByName(fieldName);
706 
707 			// Increment by 1 or 2 depending on operator
708 		    String value  = "";
709 		    if (jump >= 0)
710 			   value = aKeyValPair.substring(n + jump).trim();
711 
712 			// i.e. "2"
713 			logCat.debug("Filter value=" + value);
714 
715 			// Create a new instance of FieldValue and set the operator variable
716 			result[i] = FieldValue.createFieldValueForSearching(filterField,
717 					value, locale, operator, Constants.SEARCHMODE_NONE,
718 					Constants.SEARCH_ALGO_SHARP, isLogicalOR);
719 			logCat.debug("and fv is =" + result[i].toString());
720 		}
721 
722 		return result;
723 	}
724 
725 	/***
726 	 * Get all the ForeignKey objects related to this table.
727 	 * 
728 	 * @return a vector containing all the ForeignKey objects related to this
729 	 *         table.
730 	 */
731 	public Collection getForeignKeys() {
732 		return foreignKeys;
733 	}
734 
735 	/***
736 	 * Prepares the Querystring for the free form select statement
737 	 * 
738 	 * @param fieldsToSelect
739 	 *            vector of fields to be selected
740 	 * @param whereClause
741 	 *            free-form whereClause to be appended to query
742 	 * @param tableList
743 	 *            the list of tables involved into the query
744 	 * 
745 	 * @return the query string
746 	 */
747 	public String getFreeFormSelectQuery(Vector fieldsToSelect,
748 			String whereClause, String tableList) {
749 		StringBuffer buf = new StringBuffer();
750 		buf.append("SELECT ");
751 		buf.append(getQuerySelect(fieldsToSelect));
752 		buf.append(" FROM ");
753 
754 		if (Util.isNull(tableList)) {
755 			buf.append(getQueryFrom());
756 		} else {
757 			buf.append(tableList);
758 		}
759 
760 		buf.append(" ");
761 		buf.append(whereClause);
762 		logCat.info("::getFreeFormSelectQuery -- [" + buf.toString() + "]");
763 
764 		return buf.toString();
765 	}
766 
767 	/***
768 	 * Set GrantedPrivileges, if defined in dbforms-config-xml (this method gets
769 	 * called from XML-digester).
770 	 * 
771 	 * @param grantedPrivileges
772 	 *            the grantedPrivileges object
773 	 */
774 	public void setGrantedPrivileges(GrantedPrivileges grantedPrivileges) {
775 		this.grantedPrivileges = grantedPrivileges;
776 	}
777 
778 	/***
779 	 * returns object containing info about rights mapped to user-roles.
780 	 * (context: this table object!)
781 	 * 
782 	 * @return the GrantedPrivileges object
783 	 */
784 	public GrantedPrivileges getGrantedPrivileges() {
785 		return grantedPrivileges;
786 	}
787 
788 	/***
789 	 * Sets the ID of this table (this method gets called from DbFormsConfig).
790 	 * 
791 	 * @param id
792 	 *            the id value to set
793 	 */
794 	public void setId(int id) {
795 		this.id = id;
796 	}
797 
798 	/***
799 	 * Returns ID of this table.
800 	 * 
801 	 * @return the id value
802 	 */
803 	public int getId() {
804 		return id;
805 	}
806 
807 	/***
808 	 * Returns SQL insert statement, used by insertEvent.
809 	 * 
810 	 * @param fieldValues
811 	 *            the Hashtable containing the field values
812 	 * 
813 	 * @return the SQL insert statement
814 	 */
815 	public String getInsertStatement(FieldValues fieldValues) {
816 		StringBuffer queryBuf = new StringBuffer();
817 		queryBuf.append("INSERT INTO ");
818 		queryBuf.append(getQueryToChange());
819 		queryBuf.append(" (");
820 
821 		// list the names of fields we'll include into the insert operation
822 		Iterator e = fieldValues.keys();
823 		while (e.hasNext()) {
824 			String fieldName = (String) e.next();
825 			FieldValue fv = fieldValues.get(fieldName);
826 			if (!isCalcField(fv.getField().getId()) && Util.isNull(fv.getField().getExpression()) ) {
827 				queryBuf.append(fieldName);
828 				if (e.hasNext()) {
829 					queryBuf.append(",");
830 				}
831 			}
832 		}
833 
834 		// list the place-holders for the fields to include
835 		queryBuf.append(") VALUES (");
836 		e = fieldValues.keys();
837 		while (e.hasNext()) {
838 			String fieldName = (String) e.next();
839 			FieldValue fv = fieldValues.get(fieldName);
840 			if (!isCalcField(fv.getField().getId()) && Util.isNull(fv.getField().getExpression()) ) {
841 				queryBuf.append("?");
842 				if (e.hasNext()) {
843 					queryBuf.append(",");
844 				}
845 			}
846 		}
847 
848 		queryBuf.append(")");
849 		logCat.info("::insertQuery - [" + queryBuf.toString() + "]");
850 
851 		return queryBuf.toString();
852 	}
853 
854 	/***
855 	 * Get all the interceptor objects related to this table.
856 	 * 
857 	 * @return a vector containing all the interceptor objects related to this
858 	 *         table.
859 	 */
860 	public Vector getInterceptors() {
861 
862 		// [20050228 - fossato@pow2.com] changed this code to avoid duplicated
863 		// inserts of table's interceptors;
864 		Vector res = null;
865 
866 		// has the system got any global interceptor ?
867 		if ((getConfig() != null) && getConfig().hasInterceptors()) {
868 
869 			// insert the table's interceptors (if any) and then the global
870 			// interceptor(s);
871 			res = new Vector(interceptors);
872 			res.addAll(getConfig().getInterceptors());
873 		} else {
874 			res = interceptors;
875 		}
876 
877 		return res;
878 	}
879 
880 	/***
881 	 * Returns the key of this table (consisting of Field-Objects representing
882 	 * key-fields).
883 	 * 
884 	 * @return the key of this table (consisting of Field-Objects representing
885 	 *         key-fields)
886 	 */
887 	public Vector getKey() {
888 		return key;
889 	}
890 
891 	/***
892 	 * Does basically the same as getPositionString but only for key-fields.
893 	 * <br>
894 	 * #checkme: could be merged with getPositionString <br>
895 	 * #fixme: replace seperator-based tokenization by better algoithm!
896 	 * 
897 	 * @param rsv
898 	 *            the ResultSetVector object
899 	 * 
900 	 * @return the position string for key fields
901 	 */
902 	public String getKeyPositionString(ResultSetVector rsv) {
903 		if (ResultSetVector.isNull(rsv)) {
904 			return null;
905 		}
906 
907 		String[] currentRow = rsv.getCurrentRow();
908 
909 		if (currentRow == null) {
910 			return null;
911 		}
912 
913 		return getKeyPositionString(currentRow);
914 	}
915 
916 	/***
917 	 * Does basically the same as getPositionString but only for key-fields.
918 	 * 
919 	 * @param currentRow
920 	 *            the currentRow as String[]
921 	 * 
922 	 * @return the position string
923 	 */
924 	public String getKeyPositionString(String[] currentRow) {
925 		StringBuffer buf = new StringBuffer();
926 
927 		if (currentRow != null) {
928 			for (int i = 0; i < getKey().size(); i++) {
929 				Field f = (Field) getKey().elementAt(i);
930 
931 				if (i > 0) {
932 					buf.append("-"); // control byte
933 				}
934 
935 				buf.append(createToken(f, currentRow[f.getId()]));
936 			}
937 		}
938 
939 		return buf.toString();
940 	}
941 
942 	/***
943 	 * Get key position from the input hash table
944 	 * 
945 	 * @param fvHT
946 	 *            has field as key and FieldValue as value!
947 	 * 
948 	 * @return the key position string
949 	 * 
950 	 * @throws IllegalArgumentException
951 	 *             DOCUMENT ME!
952 	 */
953 	public String getKeyPositionString(FieldValues fvHT) {
954 		if (fvHT == null) {
955 			return null;
956 		}
957 
958 		StringBuffer buf = new StringBuffer();
959 		int cnt = 0;
960 
961 		for (int i = 0; i < getKey().size(); i++) {
962 			Field f = (Field) getKey().elementAt(i);
963 			FieldValue fv = fvHT.get(f.getName());
964 
965 			if (fv != null) {
966 				String value = fv.getFieldValue();
967 
968 				if (value == null) {
969 					throw new IllegalArgumentException("wrong fields provided");
970 				}
971 
972 				if (cnt > 0) {
973 					buf.append("-"); // control byte
974 				}
975 
976 				buf.append(createToken(f, value));
977 				cnt++;
978 			}
979 		}
980 
981 		return buf.toString();
982 	}
983 
984 	/***
985 	 * Sets the name of the table (this method gets called from XML-digester)
986 	 * 
987 	 * @param name
988 	 *            the name of the table
989 	 */
990 	public void setName(String name) {
991 		this.name = name;
992 	}
993 
994 	/***
995 	 * Returns name of the table
996 	 * 
997 	 * @return the name of this table
998 	 */
999 	public String getName() {
1000 		return name;
1001 	}
1002 
1003 	/***
1004 	 * returns the hash table. Moved from dbFormTag to table, so that you can
1005 	 * overload it!
1006 	 * 
1007 	 * @param core
1008 	 *            starting tag for the fields
1009 	 * 
1010 	 * @return hash table of names in PHP slang we would call that an
1011 	 *         "associative array" :=)
1012 	 */
1013 	public Hashtable getNamesHashtable(String core) {
1014 		Hashtable result = new Hashtable();
1015 		Iterator e = getFields().iterator();
1016 
1017 		while (e.hasNext()) {
1018 			Field f = (Field) e.next();
1019 			result.put(f.getName(), f.getFieldName(core));
1020 		}
1021 
1022 		return result;
1023 	}
1024 
1025 	/***
1026 	 * Sets a default-orderBy clause from xml config (this method gets called
1027 	 * from XML-digester).
1028 	 * 
1029 	 * @param orderBy
1030 	 *            the orderBy clause
1031 	 */
1032 	public void setOrderBy(String orderBy) {
1033 		this.orderBy = orderBy;
1034 	}
1035 
1036 	/***
1037 	 * Return default-orderBy clause from xml config or null if not specified.
1038 	 * 
1039 	 * @return the default-orderBy clause from xml config or null if not
1040 	 *         specified.
1041 	 */
1042 	public String getOrderBy() {
1043 		return orderBy;
1044 	}
1045 
1046 	/***
1047 	 * Builds a "position- string" representing the values of the current row in
1048 	 * the given ResultSetVector. <br>
1049 	 * Not all field-values get explicitl listed in this string. only fields
1050 	 * important for navigation and sorting are listed. <br>
1051 	 * Position strings are used as request parameters allowing the framework to
1052 	 * keep track of the position the user comes from or goes to. <br>
1053 	 * Look into com.itp.tablib.DbFormTag for better understanding changed
1054 	 * 0-04-2001 by joe #note: enhanced algorithm since version 0.9!
1055 	 * 
1056 	 * @param rsv
1057 	 *            the ResultSetVector object
1058 	 * 
1059 	 * @return the position string
1060 	 */
1061 	public String getPositionString(ResultSetVector rsv) {
1062 		if (ResultSetVector.isNull(rsv)) {
1063 			return null;
1064 		}
1065 
1066 		String[] currentRow = rsv.getCurrentRow();
1067 
1068 		if (currentRow == null) {
1069 			return null;
1070 		}
1071 
1072 		return getPositionString(currentRow);
1073 	}
1074 
1075 	/***
1076 	 * Builds a "position- string" representing the values of the current row in
1077 	 * the given ResultSetVector. <br>
1078 	 * Not all field-values get explicitly listed in this string. only fields
1079 	 * important for navigation and sorting are listed. <br>
1080 	 * Position strings are used as request parameters allowing the framework to
1081 	 * keep track of the position the user comes from or goes to. <br>
1082 	 * 
1083 	 * @param currentRow
1084 	 *            the currentRow as String[]
1085 	 * 
1086 	 * @return the position string
1087 	 */
1088 	public String getPositionString(String[] currentRow) {
1089 		StringBuffer buf = new StringBuffer();
1090 		int cnt = 0;
1091 
1092 		for (int i = 0; i < getFields().size(); i++) {
1093 			Field f = (Field) getFields().elementAt(i);
1094 
1095 			if (f.hasIsKeySet() || f.hasSortableSet()) {
1096 				if (cnt > 0) {
1097 					buf.append("-"); // control byte
1098 				}
1099 
1100 				buf.append(createToken(f, currentRow[f.getId()]));
1101 				cnt++;
1102 			}
1103 		}
1104 
1105 		return buf.toString();
1106 	}
1107 
1108 	/***
1109 	 * Used for instance by goto with prefix
1110 	 * 
1111 	 * @param ht
1112 	 *            the Hashtable object containing the field names used to build
1113 	 *            the position string ht has fieldName as key and valueStr as
1114 	 *            value!
1115 	 * 
1116 	 * @return the position string
1117 	 */
1118 	public String getPositionString(Hashtable ht) {
1119 		StringBuffer buf = new StringBuffer();
1120 		int cnt = 0;
1121 		Iterator e = ht.keySet().iterator();
1122 
1123 		while (e.hasNext()) {
1124 			String fieldName = (String) e.next();
1125 			Field aField = getFieldByName(fieldName);
1126 
1127 			if (aField != null) {
1128 				if (aField.hasIsKeySet() || aField.hasSortableSet()) {
1129 					String fieldValue = (String) ht.get(fieldName);
1130 
1131 					if (cnt > 0) {
1132 						buf.append('-'); // control byte
1133 					}
1134 
1135 					buf.append(createToken(aField, fieldValue));
1136 					cnt++;
1137 				} else {
1138 					logCat.debug("provided goto field " + fieldName
1139 							+ " is not key/search field!");
1140 				}
1141 			} else {
1142 				logCat
1143 						.error("provided goto field " + fieldName
1144 								+ " not found!");
1145 			}
1146 		}
1147 
1148 		return buf.toString();
1149 	}
1150 
1151 	/***
1152 	 * Get key position from the input hash table
1153 	 * 
1154 	 * @param fvHT
1155 	 *            has field as key and FieldValue as value!
1156 	 * 
1157 	 * @return the key position string
1158 	 * 
1159 	 * @throws IllegalArgumentException
1160 	 *             DOCUMENT ME!
1161 	 */
1162 	public String getPositionString(FieldValues fvHT) {
1163 		String res = null;
1164 		if (fvHT != null) {
1165 			StringBuffer buf = new StringBuffer();
1166 			int cnt = 0;
1167 			Iterator e = fvHT.keys();
1168 
1169 			while (e.hasNext()) {
1170 				String fieldName = (String) e.next();
1171 				FieldValue fv = fvHT.get(fieldName);
1172 				Field f = fv.getField();
1173 
1174 				if (f.hasIsKeySet() || f.hasSortableSet()) {
1175 					String value = fv.getFieldValue();
1176 
1177 					if (value == null) {
1178 						throw new IllegalArgumentException(
1179 								"wrong fields provided");
1180 					}
1181 
1182 					if (cnt > 0) {
1183 						buf.append("-"); // control byte
1184 					}
1185 
1186 					buf.append(createToken(f, value));
1187 					cnt++;
1188 				}
1189 			}
1190 
1191 			res = buf.toString();
1192 		}
1193 		return res;
1194 	}
1195 
1196 	/***
1197 	 * Returns the FROM part of a query.
1198 	 * 
1199 	 * @return the FROM part of a query
1200 	 */
1201 	public String getQueryFrom() {
1202 		String res = getAlias();
1203 		if (Util.isNull(res)) {
1204 			res = name;
1205 		}
1206 		return res;
1207 	}
1208 
1209 	/***
1210 	 * Returns the select part of a query.
1211 	 * 
1212 	 * @param fieldsToSelect
1213 	 *            the vector containing the Field objects used to build the
1214 	 *            elect part of the query
1215 	 * 
1216 	 * @return the select part of a query
1217 	 */
1218 	public String getQuerySelect(Vector fieldsToSelect) {
1219 		if (fieldsToSelect != null) {
1220 			StringBuffer buf = new StringBuffer();
1221 			int fieldsToSelectSize = fieldsToSelect.size();
1222 
1223 			// #checkme: do i need this when using Hotspot ?
1224 			// we scroll through vector directly (no enumeration!)
1225 			// to maintain correct order of elements
1226 			for (int i = 0; i < fieldsToSelectSize; i++) {
1227 				Field f = (Field) fieldsToSelect.elementAt(i);
1228 				buf.append(f.getName());
1229 				buf.append(", ");
1230 			}
1231 
1232 			buf.deleteCharAt(buf.length() - 2);
1233 
1234 			return buf.toString();
1235 		}
1236 
1237 		return "*";
1238 	}
1239 
1240 	/***
1241 	 * Prepares the Querystring for the select statement Order of parts: 1.
1242 	 * sqlFilter (fild in getDoSelectResultSet!) 2. where condition generated
1243 	 * from having / ordering fields (fild in populateWhereEqualsClause)
1244 	 * Retrieving the parameters in getDoSelectResultSet() must match this
1245 	 * order!
1246 	 * 
1247 	 * @param fieldsToSelect
1248 	 *            vector of fields to be selected
1249 	 * @param fvEqual
1250 	 *            fieldValues representing values we are looking for
1251 	 * @param fvOrder
1252 	 *            fieldValues representing needs for order clauses
1253 	 * @param sqlFilter
1254 	 *            sql condition to and with the where clause
1255 	 * @param compareMode
1256 	 *            compare mode value for generating the order clause
1257 	 * 
1258 	 * @return the query string
1259 	 */
1260 	public String getSelectQuery(Vector fieldsToSelect, FieldValue[] fvEqual,
1261 			FieldValue[] fvOrder, String sqlFilter, int compareMode) {
1262 		StringBuffer buf = new StringBuffer();
1263 
1264 		buf.append("SELECT ");
1265 		buf.append(getQuerySelect(fieldsToSelect));
1266 		buf.append(" FROM ");
1267 		buf.append(getQueryFrom());
1268 
1269 		String s = getQueryWhere(fvEqual, fvOrder, compareMode);
1270 
1271 		if (!Util.isNull(s) || !Util.isNull(sqlFilter)) {
1272 			buf.append(" WHERE ");
1273 		}
1274 
1275 		// where condition from DbFormTag's sqlFilter attribute
1276 		if (!Util.isNull(sqlFilter)) {
1277 			buf.append(" ( ");
1278 			buf.append(sqlFilter);
1279 			buf.append(" ) ");
1280 		}
1281 
1282 		// where condition generated from searching / ordering
1283 		if (!Util.isNull(s)) {
1284 			if (!Util.isNull(sqlFilter)) {
1285 				buf.append(" AND ");
1286 			}
1287 
1288 			buf.append(" ( ");
1289 			buf.append(s);
1290 			buf.append(" ) ");
1291 		}
1292 
1293 		s = getQueryOrderBy(fvOrder);
1294 
1295 		if (s.length() > 0) {
1296 			buf.append(" ORDER BY ");
1297 			buf.append(s);
1298 		}
1299 
1300 		logCat.info("::getSelectQuery - [" + buf.toString() + "]");
1301 
1302 		return buf.toString();
1303 	}
1304 
1305 	// ------------------------------ SQL methods
1306 	// ---------------------------------
1307 
1308 	/***
1309 	 * Get the SQL select statement.
1310 	 * 
1311 	 * @return the SQL select statement
1312 	 */
1313 	public String getSelectStatement() {
1314 		StringBuffer queryBuf = new StringBuffer();
1315 		queryBuf.append("SELECT ");
1316 		queryBuf.append(getQuerySelect(fields));
1317 		queryBuf.append(" FROM ");
1318 		queryBuf.append(getQueryFrom());
1319 		logCat.info(queryBuf.toString());
1320 
1321 		return queryBuf.toString();
1322 	}
1323 
1324 	/***
1325 	 * Set the table events object related to this table.
1326 	 * 
1327 	 * @param tableEvents
1328 	 *            the table events object related to this table
1329 	 */
1330 	public void setTableEvents(TableEvents tableEvents) {
1331 		this.tableEvents = tableEvents;
1332 		tableEvents.setTable(this);
1333 	}
1334 
1335 	/***
1336 	 * Get the table events object related to this table. <br>
1337 	 * If it is null (because user didn't specify custom events), set a new
1338 	 * TableEvents object and return its reference.
1339 	 * 
1340 	 * @return the table events object related to this table
1341 	 */
1342 	public TableEvents getTableEvents() {
1343 		if (tableEvents == null) {
1344 			tableEvents = new TableEvents();
1345 		}
1346 
1347 		return tableEvents;
1348 	}
1349 
1350 	/***
1351 	 * Returns SQL update statement, used by updateEvent.
1352 	 * 
1353 	 * @param fieldValues
1354 	 *            the Hashtable object containing the field values
1355 	 * 
1356 	 * @return the SQL update statement
1357 	 */
1358 	public String getUpdateStatement(FieldValues fieldValues, String keyValStr) {
1359 		StringBuffer queryBuf = new StringBuffer();
1360 		queryBuf.append("UPDATE ");
1361 		queryBuf.append(getQueryToChange());
1362 		queryBuf.append(" SET ");
1363 
1364 		// list the names of fields and the place holder for their new values
1365 		// important: these are the fields which are sent through the current
1366 		// request;
1367 		// this list may be only a subset of the field list,
1368 		// it is not necessarily the complete field list of a table!
1369 		boolean kommaNeeded = false;
1370 		Iterator e = fieldValues.keys();
1371 		while (e.hasNext()) {
1372 			String fieldName = (String) e.next();
1373 			FieldValue fv = fieldValues.get(fieldName);
1374 			if (!isCalcField(fv.getField().getId()) && Util.isNull(fv.getField().getExpression())) {
1375 				if (kommaNeeded) {
1376 					queryBuf.append(", ");
1377 				} else {
1378 					kommaNeeded = true;
1379 				}
1380 				queryBuf.append(fieldName);
1381 				queryBuf.append("= ?");
1382 			}
1383 		}
1384 		queryBuf.append(" WHERE ");
1385 		queryBuf.append(getWhereClauseForKeyFields(keyValStr));
1386 		logCat.info("::updateQuery - [" + queryBuf.toString() + "]");
1387 
1388 		return queryBuf.toString();
1389 	}
1390 
1391 	/***
1392 	 * Build the WHERE clause string using the input field values.
1393 	 * 
1394 	 * @param fv
1395 	 *            the array of FieldValue objects
1396 	 * 
1397 	 * @return the WHERE clause string protected so that it can be tested
1398 	 */
1399 	public String getWhereClause(FieldValue[] fv) {
1400 		StringBuffer buf = new StringBuffer();
1401 
1402 		if ((fv != null) && (fv.length > 0)) {
1403 			// SM 2003-08-08: added brackets for each and-ed condition
1404 			buf.append(" ( ");
1405 
1406 			for (int i = 0; i < fv.length; i++) {
1407 				// depending on the value of isLogicalOR in FieldValue,
1408 				// prefix the filter definition with either OR or AND
1409 				// (Skip first entry!)
1410 				if (i != 0) {
1411 					if (fv[i].getLogicalOR()) {
1412 						buf.append(" OR ");
1413 					} else {
1414 						buf.append(" ) AND ( ");
1415 					}
1416 				}
1417 
1418 				buf.append(getSQLExpression(fv[i]));
1419 			}
1420 
1421 			buf.append(" ) ");
1422 		}
1423 
1424 		return buf.toString();
1425 	}
1426 
1427 	// ----------------- some convenience methods
1428 	// ---------------------------------------------
1429 
1430 	/***
1431 	 * Generates a part of the SQL where clause needed to select a distinguished
1432 	 * row form the table. This is done by querying for KEY VALUES !
1433 	 * 
1434 	 * @return a part of the SQL where clause needed to select a distinguished
1435 	 *         row form the table
1436 	 */
1437 	public String getWhereClauseForKeyFields(String keyValuesStr) {
1438 		StringBuffer buf = new StringBuffer();
1439 		FieldValues keyValuesHt = getFieldValues(keyValuesStr);
1440 
1441 		int cnt = this.getKey().size();
1442 
1443 		for (int i = 0; i < cnt; i++) {
1444 			Field keyField = (Field) this.getKey().elementAt(i);
1445 
1446 			FieldValue aFieldValue = keyValuesHt.get(keyField.getName());
1447 			Object value = aFieldValue.getFieldValueAsObject();
1448 			// get the name of the encoded key field
1449 			buf.append(keyField.getName());
1450 			if (value == null) {
1451 				buf.append(" is null");
1452 			} else {
1453 				buf.append(" = ?");
1454 			}
1455 			if (i < (cnt - 1)) {
1456 				buf.append(" AND ");
1457 			}
1458 		}
1459 
1460 		return buf.toString();
1461 	}
1462 
1463 	/***
1464 	 * adds a Field-Object to this table and puts it into othere datastructure
1465 	 * for further references (this method gets called from DbFormsConfig)
1466 	 * 
1467 	 * @param field
1468 	 *            field to add
1469 	 * 
1470 	 * @throws Exception
1471 	 *             DOCUMENT ME!
1472 	 */
1473 	public void addCalcField(Field field) throws Exception {
1474 		field.setId(encodeFieldId(CALC_FIELD, calcFields.size()));
1475 		field.setTable(this);
1476 		calcFields.addElement(field);
1477 
1478 		// for quicker lookup by name:
1479 		calcFieldsNameHash.put(field.getName(), field);
1480 	}
1481 
1482 	/***
1483 	 * Adds a Field-Object to this table and puts it into othere datastructure
1484 	 * for further references (this method gets called from DbFormsConfig)
1485 	 * 
1486 	 * @param field
1487 	 *            the Field object to add
1488 	 * 
1489 	 * @throws Exception
1490 	 *             DOCUMENT ME!
1491 	 */
1492 	public void addField(Field field) throws Exception {
1493 		if (field.getType() == 0) {
1494 			throw new Exception("Table " + getName() + " Field "
1495 					+ field.getName() + ": no type!");
1496 		}
1497 
1498 		field.setId(encodeFieldId(DB_FIELD, fields.size()));
1499 		field.setTable(this);
1500 		fields.addElement(field);
1501 
1502 		// if the field is (part of) the key
1503 		if (field.hasIsKeySet()) {
1504 			logCat.info("wow - field " + getName() + "." + field.getName()
1505 					+ " is a key");
1506 			// use key not getKey! getKey will return key Vector of parent
1507 			// table in case of query!
1508 			key.addElement(field);
1509 		} else {
1510 			logCat.info("field " + getName() + "." + field.getName()
1511 					+ " is NO key");
1512 		}
1513 
1514 		// for quicker lookup by name:
1515 		fieldNameHash.put(field.getName(), field);
1516 
1517 		// for quicker check for diskblobs
1518 		if (field.getType() == FieldTypes.DISKBLOB) {
1519 			diskblobs.addElement(field);
1520 		}
1521 	}
1522 
1523 	/***
1524 	 * Adds a ForeignKey-Object to this table and puts it into othere
1525 	 * datastructure for further references (this method gets called from
1526 	 * DbFormsConfig)
1527 	 * 
1528 	 * @param fk
1529 	 *            the foreign key object
1530 	 */
1531 	public void addForeignKey(ForeignKey fk) {
1532 		fk.setId(foreignKeys.size());
1533 
1534 		// add to vector containing all foreign keys:
1535 		foreignKeys.addElement(fk);
1536 
1537 		// for quicker lookup by name:
1538 		foreignKeyNameHash.put(fk.getName(), fk);
1539 	}
1540 
1541 	/***
1542 	 * Add an interceptor to this table.
1543 	 * 
1544 	 * @param interceptor
1545 	 *            the interceptor to add
1546 	 */
1547 	public void addInterceptor(Interceptor interceptor) {
1548 		interceptors.addElement(interceptor);
1549 	}
1550 
1551 	/***
1552 	 * Determinates if this table contains a diskblob field. (this method is
1553 	 * used by DeleteEvent which needs to delete files referenced by a diskblob
1554 	 * field).
1555 	 * 
1556 	 * @return true if this table contains a diskblob field, false otherwise
1557 	 */
1558 	public boolean containsDiskblob() {
1559 		return diskblobs.size() > 0;
1560 	}
1561 
1562 	/***
1563 	 * Column ["ASC" | "DESC"] {"," Column ["ASC" | "DESC"] } (if neither ASC
1564 	 * nor DESC follow "Col", then ASC is choosen as default). <br>
1565 	 * this method assures, that ALL KEY FIELDs are part of the order criteria,
1566 	 * in any case (independly from the order-Str). if necessary it appends
1567 	 * them. WHY: to ensure correct scrollig (not getting STUCK if the search
1568 	 * criteria are not "sharp" enough). <br>
1569 	 * #fixme - better explaination #fixme - determinate illegal input and throw
1570 	 * IllegalArgumentException
1571 	 * 
1572 	 * @param order
1573 	 *            a String from JSP provided by the user in SQL-Style
1574 	 * @param request
1575 	 *            the request object
1576 	 * @param includeKeys
1577 	 *            true to include key fields, false otherwise
1578 	 * 
1579 	 * @return ???
1580 	 */
1581 	public FieldValue[] createOrderFieldValues(String order,
1582 			HttpServletRequest request, boolean includeKeys) {
1583 		Vector result = null;
1584 
1585 		if (request != null) {
1586 			String paramStub = Constants.FIELDNAME_SORT + this.getId();
1587 			Vector sortFields = ParseUtil.getParametersStartingWith(request,
1588 					paramStub);
1589 
1590 			if (sortFields.size() > 0) {
1591 				result = createOrderFVFromRequest(request, paramStub,
1592 						sortFields);
1593 			}
1594 		}
1595 
1596 		// 20020703-HKK: use the default order if result.size == 0, not only if
1597 		// result == null
1598 		// This happens if all parameters with sort_ are set to none
1599 		if (((result == null) || result.isEmpty())) {
1600 			// 20021104-HKK: use default order from table if form has no order!
1601 			if (order == null) {
1602 				order = getOrderBy();
1603 			}
1604 
1605 			result = createOrderFVFromAttribute(order);
1606 			logCat.debug("@@@ 1");
1607 
1608 			for (int i = 0; i < result.size(); i++) {
1609 				FieldValue fieldVal = (FieldValue) result.elementAt(i);
1610 				logCat.debug("fieldValue " + fieldVal.toString());
1611 			}
1612 		}
1613 
1614 		if (includeKeys) {
1615 			// scroll through keys and append to order criteria, if not already
1616 			// included
1617 			for (int i = 0; i < this.getKey().size(); i++) {
1618 				Field keyField = (Field) getKey().elementAt(i);
1619 				boolean found = false;
1620 				int j = 0;
1621 
1622 				while (!found && (j < result.size())) {
1623 					FieldValue fv = (FieldValue) result.elementAt(j);
1624 
1625 					if (fv.getField() == keyField) {
1626 						found = true;
1627 					}
1628 
1629 					j++;
1630 				}
1631 
1632 				if (!found) {
1633 					result.addElement(FieldValue.createFieldValueForSorting(
1634 							keyField, Constants.ORDER_ASCENDING));
1635 				}
1636 			}
1637 		}
1638 
1639 		FieldValue[] resultArray = new FieldValue[result.size()];
1640 		result.copyInto(resultArray);
1641 		logCat.debug("@@@ 2");
1642 
1643 		for (int i = 0; i < resultArray.length; i++) {
1644 			logCat.debug("fieldValue " + resultArray[i].toString());
1645 		}
1646 
1647 		return resultArray;
1648 	}
1649 
1650 	// ------------------------------ Old ResultSetVector stuff
1651 	// ---------------------------------
1652 
1653 	/***
1654 	 * Do a constrained select.
1655 	 * 
1656 	 * @param fvEqual
1657 	 *            FieldValue array used to restrict a set in a subform where all
1658 	 *            "childFields" in the resultset match their respective
1659 	 *            "parentFields" in main form
1660 	 * @param fvOrder
1661 	 *            FieldValue array used to build a cumulation of rules for
1662 	 *            ordering (sorting) and restricting fields
1663 	 * @param sqlFilter
1664 	 *            sql condition to add to where clause
1665 	 * @param sqlFilterParams
1666 	 *            DOCUMENT ME!
1667 	 * @param compareMode
1668 	 *            the value of the compare mode
1669 	 * @param maxRows
1670 	 *            the max number of rows to manage
1671 	 * @param interceptorData
1672 	 *            the connection object
1673 	 * 
1674 	 * @return a ResultSetVector object
1675 	 * 
1676 	 * @throws SQLException
1677 	 *             if any error occurs
1678 	 */
1679 	public ResultSetVector doConstrainedSelect(FieldValue[] fvEqual,
1680 			FieldValue[] fvOrder, String sqlFilter,
1681 			FieldValue[] sqlFilterParams, int compareMode, int maxRows,
1682 			DbEventInterceptorData interceptorData) throws SQLException {
1683 		String query = getSelectQuery(getFields(), fvEqual, fvOrder, sqlFilter,
1684 				compareMode);
1685 		PreparedStatement ps = interceptorData.getConnection()
1686 				.prepareStatement(query);
1687 		ps.setMaxRows(maxRows); // important when quering huge tables
1688 
1689 		ResultSet rs = getDoSelectResultSet(fvEqual, fvOrder, sqlFilterParams,
1690 				compareMode, ps);
1691 		ResultSetVector result = new ResultSetVector(this);
1692 		result.addResultSet(interceptorData, rs);
1693 		ps.close();
1694 		logCat.info("::doConstrainedSelect - rsv size = " + result.size());
1695 
1696 		return result;
1697 	}
1698 
1699 	/***
1700 	 * perform free-form select query
1701 	 * 
1702 	 * @param whereClause
1703 	 *            free-form whereClause to be appended to query
1704 	 * @param tableList
1705 	 *            the list of tables involved into the query
1706 	 * @param maxRows
1707 	 *            how many rows should be stored in the resultSet (zero means
1708 	 *            unlimited)
1709 	 * @param interceptorData
1710 	 *            the active db connection to use
1711 	 * 
1712 	 * @return the ResultSetVector object
1713 	 * 
1714 	 * @throws SQLException
1715 	 *             if any error occurs
1716 	 */
1717 	public ResultSetVector doFreeFormSelect(String whereClause,
1718 			String tableList, int maxRows,
1719 			DbEventInterceptorData interceptorData) throws SQLException {
1720 		Statement stmt = interceptorData.getConnection().createStatement();
1721 		String query = getFreeFormSelectQuery(getFields(), whereClause,
1722 				tableList);
1723 		stmt.setMaxRows(maxRows); // important when quering huge tables
1724 
1725 		ResultSet rs;
1726 
1727 		try {
1728 			rs = stmt.executeQuery(query);
1729 		} catch (SQLException sqle) {
1730 			SqlUtil.logSqlException(sqle);
1731 			throw new SQLException(sqle.getMessage());
1732 		}
1733 
1734 		ResultSetVector result = new ResultSetVector(this);
1735 		result.addResultSet(interceptorData, rs);
1736 
1737 		// 20021115-HKK: resultset is closed in ResultSetVector()
1738 		// rs.close();
1739 		stmt.close();
1740 		logCat.info("rsv size=" + result.size());
1741 
1742 		return result;
1743 	}
1744 
1745 	/***
1746 	 * in version 0.9 this method moved from FieldValue.fillWithValues to
1747 	 * Table.fillWithValues
1748 	 * 
1749 	 * @param orderConstraint
1750 	 *            FieldValue array used to build a cumulation of rules for
1751 	 *            ordering (sorting) and restricting fields
1752 	 * @param aPosition
1753 	 *            resultset position
1754 	 */
1755 	public void fillWithValues(FieldValue[] orderConstraint, String aPosition) {
1756 		// 2003-03-29 HKK: Change from Hashtable to FieldValueTable
1757 		FieldValues ht = getFieldValues(aPosition);
1758 
1759 		// 20021104-HKK: Error handling if aPosition is not given!
1760 		if (ht != null) {
1761 			logCat.info("*** parsing through: " + aPosition);
1762 
1763 			// then we copy some of those values into the orderConstraint
1764 			for (int i = 0; i < orderConstraint.length; i++) {
1765 				Field f = orderConstraint[i].getField();
1766 
1767 				if (f != null) {
1768 					FieldValue aFieldValue = ht.get(f.getName());
1769 
1770 					if (aFieldValue != null) {
1771 						orderConstraint[i].setFieldValue(aFieldValue
1772 								.getFieldValue());
1773 					} else {
1774 						logCat.warn("position entry has null value:"
1775 								+ f.getName());
1776 					}
1777 				}
1778 			}
1779 		}
1780 	}
1781 
1782 	/***
1783 	 * Check if this table has got interceptors.
1784 	 * 
1785 	 * @return true if the table contains interceptors, false otherwise
1786 	 */
1787 	public boolean hasInterceptors() {
1788 		return (getConfig() != null && getConfig().hasInterceptors())
1789 				|| ((interceptors != null) && (interceptors.size() > 0));
1790 	}
1791 
1792 	/***
1793 	 * Checks if there exists a granted-privileges object and if so it queries
1794 	 * if access/operation is possible
1795 	 * 
1796 	 * @param request
1797 	 *            the request object
1798 	 * @param privileg
1799 	 *            the privilege value
1800 	 * 
1801 	 * @return true if the user has got privileges over this table, false
1802 	 *         otherwise
1803 	 */
1804 	public boolean hasUserPrivileg(HttpServletRequest request, int privileg) {
1805 		return (grantedPrivileges == null) ? true : grantedPrivileges
1806 				.hasUserPrivileg(request, privileg);
1807 	}
1808 
1809 	/***
1810 	 * This method generates a datastructure holding sorting information from
1811 	 * "orderBy" clause in XML-config.
1812 	 */
1813 	public void initDefaultOrder() {
1814 		// if developer specified no orderBy in XML, then we set the KEYs as
1815 		// DEFAULT ORDER
1816 		if (orderBy == null) {
1817 			initDefaultOrderFromKeys();
1818 		} else {
1819 			// build the datastructure, containing Fields, and infos about sort
1820 			defaultOrder = createOrderFieldValues(orderBy, null, true);
1821 		}
1822 
1823 		logCat.info("Table.initDefaultOrder done.");
1824 	}
1825 
1826 	/***
1827 	 * maps child fields to parent fields
1828 	 * 
1829 	 * @param parentTable
1830 	 *            the parent table
1831 	 * @param parentFieldString
1832 	 *            field names in parent table
1833 	 * @param childFieldString
1834 	 *            field names in child table
1835 	 * @param aPosition
1836 	 *            position to map as position string
1837 	 * 
1838 	 * @return FieldValues with result
1839 	 * 
1840 	 * @throws IllegalArgumentException
1841 	 *             DOCUMENT ME!
1842 	 */
1843 	public FieldValues mapChildFieldValues(Table parentTable,
1844 			String parentFieldString, String childFieldString, String aPosition) {
1845 		// 1 to n fields may be mapped
1846 		Vector childFieldNames = StringUtil
1847 				.splitString(childFieldString, ",;~");
1848 		Vector parentFieldNames = StringUtil.splitString(parentFieldString,
1849 				",;~");
1850 
1851 		// do some basic checks
1852 		// deeper checks like Datatyp-compatibility,etc not done yet
1853 		int len = childFieldNames.size();
1854 
1855 		if ((len == 0) || (len != parentFieldNames.size())) {
1856 			return null;
1857 		}
1858 
1859 		// 2003-03-29 HKK: Change from Hashtable to FieldValueTable
1860 		FieldValues ht = parentTable.getFieldValues(aPosition);
1861 
1862 		if (ht == null) {
1863 			return null;
1864 		}
1865 
1866 		FieldValues childFieldValues = new FieldValues();
1867 
1868 		for (int i = 0; i < len; i++) {
1869 			String parentFieldName = (String) parentFieldNames.elementAt(i);
1870 			Field parentField = parentTable.getFieldByName(parentFieldName);
1871 			String childFieldName = (String) childFieldNames.elementAt(i);
1872 			Field childField = this.getFieldByName(childFieldName);
1873 			FieldValue aFieldValue = ht.get(parentField.getName());
1874 
1875 			if (aFieldValue == null) {
1876 				throw new IllegalArgumentException(
1877 						"ERROR: Make sure that field "
1878 								+ parentField.getName()
1879 								+ " is a KEY of the table "
1880 								+ parentTable.getName()
1881 								+ "! Otherwise you can not use it as PARENT/CHILD LINK argument!");
1882 			}
1883 
1884 			String currentParentFieldValue = aFieldValue.getFieldValue();
1885 			childFieldValues.put(new FieldValue(childField,
1886 					currentParentFieldValue));
1887 		}
1888 
1889 		return childFieldValues;
1890 	}
1891 
1892 	/***
1893 	 * POPULATES a part of the SQL where clause needed to select a distinguished
1894 	 * row form the table using values endcoded in a string. <br>
1895 	 * #fixme: replace seperator-based tokenization by better algoithm!
1896 	 * 
1897 	 * @param keyValuesStr
1898 	 *            the position string
1899 	 * @param ps
1900 	 *            the PreparedStatement object
1901 	 * @param startColumn
1902 	 *            PreparedStatement start column
1903 	 * 
1904 	 * @throws SQLException
1905 	 *             if any error occurs
1906 	 */
1907 	public void populateWhereClauseWithKeyFields(String keyValuesStr,
1908 			PreparedStatement ps, int startColumn) throws SQLException {
1909 		int col = startColumn;
1910 
1911 		// then we list the values of the key-fields, so that the WHERE clause
1912 		// matches the right dataset
1913 		// 2003-03-29 HKK: Change from Hashtable to FieldValueTable
1914 		FieldValues keyValuesHt = getFieldValues(keyValuesStr);
1915 		int keyLength = this.getKey().size();
1916 
1917 		for (int i = 0; i < keyLength; i++) {
1918 			Field curField = (Field) this.getKey().elementAt(i);
1919 			FieldValue aFieldValue = keyValuesHt.get(curField.getName());
1920 			Object value = aFieldValue.getFieldValueAsObject();
1921 			if (value != null) {
1922 				JDBCDataHelper.fillWithData(ps, curField.getEscaper(), col, value,
1923 					curField.getType(), this.getBlobHandlingStrategy());
1924 				col++;
1925 			}
1926 		}
1927 	}
1928 
1929 	/***
1930 	 * situation: we have built a query (involving the getWhereEqualsClause()
1931 	 * method) and now we want to prepare the statemtent - provide actual values
1932 	 * for the the '?' placeholders
1933 	 * 
1934 	 * @param fv
1935 	 *            the array of FieldValue objects
1936 	 * @param ps
1937 	 *            the PreparedStatement object
1938 	 * @param curCol
1939 	 *            the current PreparedStatement column; points to a
1940 	 *            PreparedStatement xxx value
1941 	 * 
1942 	 * @return the current column value
1943 	 * 
1944 	 * @exception SQLException
1945 	 *                if any error occurs
1946 	 */
1947 	public int populateWhereEqualsClause(FieldValue[] fv, PreparedStatement ps,
1948 			int curCol) throws SQLException {
1949 		if ((fv != null) && (fv.length > 0)) {
1950 			for (int i = 0; i < fv.length; i++) {
1951 				curCol = fillPreparedStatement(fv[i], ps, curCol);
1952 			}
1953 		}
1954 
1955 		return curCol;
1956 	}
1957 
1958 	/***
1959 	 * Process the interceptor objects related to this table.
1960 	 * 
1961 	 * @param action
1962 	 *            the DbEventInterceptor identifier. See the DbEventInterceptor
1963 	 *            class for the real values. Example:
1964 	 *            <code>DbEventInterceptor.PRE_UPDATE</code>
1965 	 * @param data
1966 	 *            the DbEventInterceptorData object
1967 	 * 
1968 	 * @return the value that identifies if an operation should be granted,
1969 	 *         denied or ignored. See
1970 	 *         <code>DbEventInterceptor.GRANT_OPERATION</code>,
1971 	 *         <code>DbEventInterceptor.DENY_OPERATION</code>,
1972 	 *         <code>DbEventInterceptor.IGNORE_OPERATION</code>
1973 	 * 
1974 	 * @throws SQLException
1975 	 *             if any error occurs
1976 	 */
1977 	public int processInterceptors(int action, DbEventInterceptorData data)
1978 			throws MultipleValidationException {
1979 		String s;
1980 		try {
1981 			Vector allInterceptors = getInterceptors();
1982 			int interceptorsCnt = allInterceptors.size();
1983 
1984 			for (int i = 0; i < interceptorsCnt; i++) {
1985 				Interceptor interceptor = (Interceptor) allInterceptors
1986 						.elementAt(i);
1987 				Class interceptorClass = Class.forName(interceptor
1988 						.getClassName());
1989 				IDbEventInterceptor dbi = (IDbEventInterceptor) interceptorClass
1990 						.newInstance();
1991 
1992 				// J.Peer 03/18/2004 - plug in some additional config data for
1993 				// interceptor
1994 				dbi.setParameterMap(interceptor.getParameterMap());
1995 
1996 				// (Sunil_Mishra@adp.com) - The return type to check for the
1997 				// IGNORE_OPERATION
1998 				int operation = IDbEventInterceptor.GRANT_OPERATION;
1999 				String denyMessage = null;
2000 
2001 				if (action == IDbEventInterceptor.PRE_INSERT) {
2002 					operation = dbi.preInsert(data);
2003 					denyMessage = "dbforms.events.insert.nogrant";
2004 				} else if (action == IDbEventInterceptor.POST_INSERT) {
2005 					dbi.postInsert(data);
2006 				} else if (action == IDbEventInterceptor.PRE_UPDATE) {
2007 					operation = dbi.preUpdate(data);
2008 					denyMessage = "dbforms.events.update.nogrant";
2009 				} else if (action == IDbEventInterceptor.POST_UPDATE) {
2010 					dbi.postUpdate(data);
2011 				} else if (action == IDbEventInterceptor.PRE_DELETE) {
2012 					operation = dbi.preDelete(data);
2013 					denyMessage = "dbforms.events.delete.nogrant";
2014 				} else if (action == IDbEventInterceptor.POST_DELETE) {
2015 					dbi.postDelete(data);
2016 				} else if (action == IDbEventInterceptor.PRE_SELECT) {
2017 					operation = dbi.preSelect(data);
2018 					denyMessage = "dbforms.events.view.nogrant";
2019 				} else if (action == IDbEventInterceptor.POST_SELECT) {
2020 					dbi.postSelect(data);
2021 				} else if (action == IDbEventInterceptor.PRE_ADDROW) {
2022 					operation = dbi.preAddRow(data);
2023 					denyMessage = "dbforms.events.addrow.nogrant";
2024 				} else if (action == IDbEventInterceptor.POST_ADDROW) {
2025 					dbi.postAddRow(data);
2026 				}
2027 
2028 				switch (operation) {
2029 				case IDbEventInterceptor.DENY_OPERATION:
2030 					s = MessageResourcesInternal.getMessage(denyMessage, data
2031 							.getRequest().getLocale(),
2032 							new String[] { getName() });
2033 					throw new MultipleValidationException(s);
2034 
2035 				case IDbEventInterceptor.IGNORE_OPERATION:
2036 					return operation;
2037 
2038 				default:
2039 					break;
2040 				}
2041 			}
2042 		} catch (ClassNotFoundException cnfe) {
2043 			logCat.warn("ClassNotFoundException: " + cnfe.getMessage());
2044 			throw new MultipleValidationException(cnfe.getMessage());
2045 		} catch (InstantiationException ie) {
2046 			logCat.warn(" InstantiationException : " + ie.getMessage());
2047 			throw new MultipleValidationException(ie.getMessage());
2048 		} catch (IllegalAccessException iae) {
2049 			logCat.warn(" IllegalAccessException : " + iae.getMessage());
2050 			throw new MultipleValidationException(iae.getMessage());
2051 		} catch (MultipleValidationException mve) {
2052 			throw mve;
2053 		} catch (ValidationException ve) {
2054 			throw new MultipleValidationException(ve.getMessage());
2055 		}
2056 
2057 		return IDbEventInterceptor.GRANT_OPERATION;
2058 	}
2059 
2060 	// ------------------------------ utility / helper methods
2061 	// ---------------------------------
2062 
2063 	/***
2064 	 * This metod is useful for logging / debugging purposes only.
2065 	 * 
2066 	 * @return a string containing the Table name and field values
2067 	 */
2068 	public String toString() {
2069 		StringBuffer buf = new StringBuffer();
2070 		buf.append("\nname=");
2071 		buf.append(name);
2072 		buf.append(" ");
2073 		buf.append("\nid=");
2074 		buf.append(String.valueOf(getId()));
2075 		buf.append(" ");
2076 
2077 		if (getFields() != null) {
2078 			for (int i = 0; i < getFields().size(); i++) {
2079 				Field f = (Field) getFields().elementAt(i);
2080 				buf.append("\nfield: ");
2081 				buf.append(f.toString());
2082 			}
2083 		}
2084 
2085 		return buf.toString();
2086 	}
2087 
2088 	/***
2089 	 * Returns the part of the orderby-clause represented by this FieldValue
2090 	 * object. <br>
2091 	 * (ASC will be not printed because it is defined DEFAULT in SQL if there
2092 	 * are RDBMS which do not tolerate this please let me know; then i'll change
2093 	 * it).
2094 	 * 
2095 	 * @param fvOrder
2096 	 *            FieldValue array used to build a cumulation of rules for
2097 	 *            ordering (sorting) and restricting fields
2098 	 * 
2099 	 * @return the part of the orderby-clause represented by this FieldValue
2100 	 *         object
2101 	 */
2102 	protected String getQueryOrderBy(FieldValue[] fvOrder) {
2103 		StringBuffer buf = new StringBuffer();
2104 
2105 		if (fvOrder != null) {
2106 			for (int i = 0; i < fvOrder.length; i++) {
2107 				buf.append(fvOrder[i].getField().getName());
2108 
2109 				if (fvOrder[i].getSortDirection() == Constants.ORDER_DESCENDING) {
2110 					buf.append(" DESC");
2111 				}
2112 
2113 				if (i < (fvOrder.length - 1)) {
2114 					buf.append(",");
2115 				}
2116 			}
2117 		}
2118 
2119 		return buf.toString();
2120 	}
2121 
2122 	/***
2123 	 * Returns the FROM part of a insert/delete/update query.
2124 	 * 
2125 	 * @return the FROM part of a insert/delete/update query
2126 	 */
2127 	protected String getQueryToChange() {
2128 		return getQueryFrom();
2129 	}
2130 
2131 	/***
2132 	 * Returns the WHERE part of a query.
2133 	 * 
2134 	 * @param fvEqual
2135 	 *            FieldValue array used to restrict a set in a subform where all
2136 	 *            "childFields" in the resultset match their respective
2137 	 *            "parentFields" in main form
2138 	 * @param fvOrder
2139 	 *            FieldValue array used to build a cumulation of rules for
2140 	 *            ordering (sorting) and restricting fields
2141 	 * @param compareMode
2142 	 *            compare mode value for generating the order clause
2143 	 * 
2144 	 * @return the WHERE part of a query
2145 	 */
2146 	protected String getQueryWhere(FieldValue[] fvEqual, FieldValue[] fvOrder,
2147 			int compareMode) {
2148 		boolean firstTermExists = false;
2149 		StringBuffer buf = new StringBuffer();
2150 
2151 		// build the first term;
2152 		if (!FieldValue.isNull(fvEqual)) {
2153 			// check if the fieldvalues contain _search_ information
2154 			buf.append(" ( ");
2155 
2156 			if (fvEqual[0].getSearchMode() == Constants.SEARCHMODE_NONE) {
2157 				buf.append(getWhereClause(fvEqual));
2158 			} else {
2159 				buf.append(getWhereEqualsSearchClause(fvEqual));
2160 			}
2161 
2162 			buf.append(" ) ");
2163 			firstTermExists = true;
2164 		}
2165 
2166 		// build the second term;
2167 		// this SHOULD be the WHERE clause which restricts
2168 		// the query to rows coming AFTER the row containing the actual data.
2169 		if (!FieldValue.isNull(fvOrder)
2170 				&& (compareMode != Constants.COMPARE_NONE)) {
2171 			buf.append(firstTermExists ? " AND ( " : "");
2172 			buf.append(getWhereAfterClause(fvOrder, compareMode));
2173 			buf.append(firstTermExists ? " ) " : "");
2174 		}
2175 
2176 		return buf.toString();
2177 	}
2178 
2179 	/***
2180 	 * DOCUMENT ME!
2181 	 * 
2182 	 * @param type
2183 	 *            DOCUMENT ME!
2184 	 * @param id
2185 	 *            DOCUMENT ME!
2186 	 * 
2187 	 * @return DOCUMENT ME!
2188 	 */
2189 	protected boolean checkFieldId(int type, int aid) {
2190 		int i = aid / MAXFIELDS;
2191 		return i == type;
2192 	}
2193 
2194 	/***
2195 	 * DOCUMENT ME!
2196 	 * 
2197 	 * @param type
2198 	 *            DOCUMENT ME!
2199 	 * @param id
2200 	 *            DOCUMENT ME!
2201 	 * 
2202 	 * @return DOCUMENT ME!
2203 	 */
2204 	protected int decodeFieldId(int type, int aid) {
2205 		return aid - (type * MAXFIELDS);
2206 	}
2207 
2208 	/***
2209 	 * DOCUMENT ME!
2210 	 * 
2211 	 * @param type
2212 	 *            DOCUMENT ME!
2213 	 * @param id
2214 	 *            DOCUMENT ME!
2215 	 * 
2216 	 * @return DOCUMENT ME!
2217 	 */
2218 	protected int encodeFieldId(int type, int aid) {
2219 		return (type * MAXFIELDS) + aid;
2220 	}
2221 
2222 	/***
2223 	 * returns an SQLExpression based on the given FieldValue
2224 	 * 
2225 	 * @param fv
2226 	 *            the FieldValue
2227 	 * 
2228 	 * @return string holding the SQL Expression
2229 	 */
2230 	private String getSQLExpression(FieldValue fv) {
2231 		StringBuffer buf = new StringBuffer();
2232 
2233 		// 20021104-HKK: Check for expression.
2234 		Field f = fv.getField();
2235 		String fieldName = Util.isNull(f.getExpression()) ? f.getName() : f
2236 				.getExpression();
2237 		buf.append(fieldName);
2238 
2239 		// Check what type of operator is required
2240 		switch (fv.getOperator()) {
2241 		case Constants.FILTER_EQUAL:
2242 			buf.append(" = ");
2243 			buf.append(" ? ");
2244 
2245 			break;
2246 
2247 		case Constants.FILTER_NOT_EQUAL:
2248 			buf.append(" <> ");
2249 			buf.append(" ? ");
2250 
2251 			break;
2252 
2253 		case Constants.FILTER_GREATER_THEN:
2254 			buf.append(" > ");
2255 			buf.append(" ? ");
2256 
2257 			break;
2258 
2259 		case Constants.FILTER_SMALLER_THEN:
2260 			buf.append(" < ");
2261 			buf.append(" ? ");
2262 
2263 			break;
2264 
2265 		case Constants.FILTER_GREATER_THEN_EQUAL:
2266 			buf.append(" >= ");
2267 			buf.append(" ? ");
2268 
2269 			break;
2270 
2271 		case Constants.FILTER_SMALLER_THEN_EQUAL:
2272 			buf.append(" <= ");
2273 			buf.append(" ? ");
2274 
2275 			break;
2276 
2277 		case Constants.FILTER_LIKE:
2278 
2279 			if (FieldTypes.isCHAR(f.getType())) {
2280 				buf.append(" LIKE ");
2281 			} else {
2282 				buf.append(" = ");
2283 			}
2284 
2285 			buf.append(" ? ");
2286 
2287 			break;
2288 
2289 		case Constants.FILTER_NULL:
2290 			buf.append(" IS NULL ");
2291 
2292 			break;
2293 
2294 		case Constants.FILTER_NOT_NULL:
2295 			buf.append(" IS NOT NULL ");
2296 
2297 			break;
2298 
2299 		case Constants.FILTER_EMPTY:
2300 
2301 			if (FieldTypes.isCHAR(f.getType())) {
2302 				buf.append(" = '' ");
2303 				buf.append(" OR ");
2304 			}
2305 
2306 			buf.append(fieldName);
2307 			buf.append(" IS NULL ");
2308 
2309 			break;
2310 
2311 		case Constants.FILTER_NOT_EMPTY:
2312 
2313 			if (FieldTypes.isCHAR(f.getType())) {
2314 				buf.append(" <> '' ");
2315 				buf.append(" OR ");
2316 			}
2317 
2318 			buf.append(fieldName);
2319 			buf.append(" IS NOT NULL ");
2320 
2321 			break;
2322 		}
2323 
2324 		return buf.toString();
2325 	}
2326 
2327 	/***
2328 	 * situation: we have an array of fieldvalues which represents actual values
2329 	 * of order-determinating-fields. we want to build a part of the WHERE
2330 	 * clause which restricts the query to rows coming AFTER the row containing
2331 	 * the actual data. <br>
2332 	 * shortly described the following rule is applied:
2333 	 * 
2334 	 * <pre>
2335 	 *  
2336 	 *  
2337 	 *  
2338 	 *  
2339 	 *  
2340 	 *  
2341 	 *  
2342 	 *  
2343 	 *  
2344 	 *           +--------------------------------------------------------------------------------------------------+
2345 	 *           |  RULE = R1 AND R2 AND ... AND Rn                                                                 |
2346 	 *           |  Ri = fi OpA(i) fi* OR  f(i-1) OpB(i-1) f(i-1)* OR f(i-2) OpB(i-2) f(i-2)* OR ... OR f1 OpB f1*  |
2347 	 *           +--------------------------------------------------------------------------------------------------+
2348 	 *           For background info email joepeer@wap-force.net
2349 	 *  
2350 	 *  
2351 	 *  
2352 	 *  
2353 	 *  
2354 	 *  
2355 	 *  
2356 	 *  
2357 	 *  
2358 	 * </pre>
2359 	 * 
2360 	 * IMPORTANT NOTE: the indizes of the fv-array indicate implicitly the
2361 	 * order-priority of the fields. <br>
2362 	 * example: if we have ORDER BY id,name,age -> then fv[0] should contain
2363 	 * field id, fv[1] should contain field name, fv[2] should contain field age
2364 	 * 
2365 	 * @param fv
2366 	 *            the array of FieldValue objects
2367 	 * @param compareMode
2368 	 *            the comparison mode
2369 	 * 
2370 	 * @return _part_ of a WHERE-clause
2371 	 */
2372 	private String getWhereAfterClause(FieldValue[] fv, int compareMode) {
2373 		String conj;
2374 		String disj;
2375 		String opA1;
2376 		String opA2;
2377 		String opB1;
2378 		String opB2;
2379 
2380 		// COMPARE_INCLUSIVE
2381 		if (compareMode == Constants.COMPARE_INCLUSIVE) {
2382 			opA1 = ">=";
2383 			opA2 = "<=";
2384 			opB1 = ">";
2385 			opB2 = "<";
2386 			conj = " AND ";
2387 			disj = " OR ";
2388 		} else {
2389 			opA1 = ">";
2390 			opA2 = "<";
2391 			opB1 = ">=";
2392 			opB2 = "<=";
2393 			conj = " OR ";
2394 			disj = " AND ";
2395 		}
2396 
2397 		StringBuffer buf = new StringBuffer();
2398 
2399 		if ((fv != null) && (fv.length > 0)) {
2400 			// generate the Ri's
2401 			for (int i = 0; i < fv.length; i++) {
2402 				// generate a "fi OpA(i) fi*"
2403 				buf.append("(");
2404 				buf.append(fv[i].getField().getName());
2405 				buf
2406 						.append((fv[i].getSortDirection() == Constants.ORDER_ASCENDING) ? opA1
2407 								: opA2);
2408 
2409 				// OpA
2410 				buf.append(" ? ");
2411 
2412 				// generate the "f(i-1) OpB(i-1) f(i-1)* OR f(i-2) OpB(i-2)
2413 				// f(i-2)* OR ... OR f1 OpB f1*"
2414 				if (i > 0) {
2415 					for (int j = i - 1; j >= 0; j--) {
2416 						buf.append(disj);
2417 						buf.append(fv[j].getField().getName());
2418 						buf
2419 								.append((fv[j].getSortDirection() == Constants.ORDER_ASCENDING) ? opB1
2420 										: opB2);
2421 
2422 						// OpB
2423 						buf.append(" ? ");
2424 					}
2425 				}
2426 
2427 				buf.append(" ) ");
2428 
2429 				if (i < (fv.length - 1)) {
2430 					buf.append(conj);
2431 
2432 					// link the R's together (conjunction)
2433 				}
2434 			}
2435 		}
2436 
2437 		return buf.toString();
2438 	}
2439 
2440 	/***
2441 	 * situation: we have an array of fieldvalues (== fields + actual value )
2442 	 * with search information and we want to build a where - clause [that
2443 	 * should restrict the resultset in matching to the search fields].
2444 	 * 
2445 	 * <pre>
2446 	 *  
2447 	 *  
2448 	 *  
2449 	 *  
2450 	 *  
2451 	 *  
2452 	 *  
2453 	 *  
2454 	 *  
2455 	 *           convention:    index 0-n =&gt; AND
2456 	 *                          index (n+1)-m =&gt; OR
2457 	 *           examples
2458 	 *                      (A = 'meier' AND X = 'joseph') AND (AGE = '10')
2459 	 *                      (A = 'meier' ) AND (X = 'joseph' OR AGE = '10')
2460 	 *                      (X = 'joseph' OR AGE = '10')
2461 	 *                      (A = 'meier' AND X = 'joseph')
2462 	 *           for comparing to code:
2463 	 *             §1     §2        §3      §2          §4    §5   §6      §2      §7
2464 	 *             (   A = 'smith' AND   X LIKE 'jose%' )    AND    (  AGE = '10'   )
2465 	 *  
2466 	 *  
2467 	 *  
2468 	 *  
2469 	 *  
2470 	 *  
2471 	 *  
2472 	 *  
2473 	 *  
2474 	 * </pre>
2475 	 * 
2476 	 * @param fv
2477 	 *            Description of the Parameter
2478 	 * 
2479 	 * @return _part_ of a WHERE-clause
2480 	 * 
2481 	 * @todo hkk checkme: implementation is different to comment! comment says
2482 	 *       that all or fields will be anded to all and fields (second
2483 	 *       example!) implementation do an or instead????
2484 	 */
2485 	private String getWhereEqualsSearchClause(FieldValue[] fv) {
2486 		StringBuffer buf = new StringBuffer();
2487 
2488 		if ((fv != null) && (fv.length > 0)) {
2489 			int mode;
2490 			int oldMode = -1;
2491 
2492 			for (int i = 0; i < fv.length; i++) {
2493 				mode = fv[i].getSearchMode();
2494 
2495 				if (oldMode != mode) {
2496 					oldMode = mode;
2497 					buf.append("(");
2498 
2499 					// §1, §6
2500 				}
2501 
2502 				// §2, i.e "A = 'smith'" or "X LIKE 'jose%'"
2503 				buf.append(getSQLExpression(fv[i]));
2504 
2505 				if ((i < (fv.length - 1))
2506 						&& (fv[i + 1].getSearchMode() == mode)) {
2507 					buf.append((mode == Constants.SEARCHMODE_AND) ? "AND "
2508 							: "OR ");
2509 
2510 					// §3
2511 				} else {
2512 					// if(i==fv.length-1 || fv[i+1].getSearchMode()!=mode) {
2513 					buf.append(")");
2514 
2515 					// §4, §7
2516 					if (i != (fv.length - 1)) {
2517 						buf.append(" OR ");
2518 
2519 						// §5 #checkme
2520 					}
2521 				}
2522 			}
2523 		}
2524 
2525 		return buf.toString();
2526 	}
2527 
2528 	/***
2529 	 * The orderBy clause usually defaults to ASCending order. A user may add,
2530 	 * if we/she wishes the keyword ASC (ascending) or DESC (descending) to
2531 	 * specify a particular direction. <br>
2532 	 * Code in this method parses the orderBy clause and finds an occurence of
2533 	 * either ASC or DESC. Suppose your field name is "DESCRIPTION" !<br>
2534 	 * This name contains "DESC" therefore causing unexpected behaviour. This
2535 	 * bug fix consists of fine-tunning the parsing function to take into
2536 	 * consideration the sequence of parameters: 1-Field 2-Command
2537 	 * 
2538 	 * @param order
2539 	 *            order string
2540 	 * 
2541 	 * @return a vector of Field objects
2542 	 */
2543 	private Vector createOrderFVFromAttribute(String order) {
2544 		Vector result = new Vector();
2545 
2546 		if (order != null) {
2547 			StringTokenizer st = new StringTokenizer(order, ",");
2548 
2549 			while (st.hasMoreTokens()) {
2550 				// Remove leading and trailing white space characters.
2551 				String token = st.nextToken().trim();
2552 				logCat.info("token = " + token);
2553 
2554 				int sortDirection = Constants.ORDER_ASCENDING;
2555 
2556 				// we propose the default
2557 				// Separate field from command
2558 				int index = token.indexOf(" ");
2559 
2560 				// Blank space used between field and command
2561 				if (index != -1) // Do we have a command, if not assume ASC
2562 									// order
2563 				{
2564 					String command = token.substring(index).toUpperCase();
2565 					int pos = command.indexOf("ASC");
2566 
2567 					if (pos == -1) // ASC not found, try descending
2568 					{
2569 						pos = command.indexOf("DESC");
2570 
2571 						if (index != -1) {
2572 							sortDirection = Constants.ORDER_DESCENDING;
2573 
2574 							// ... we set DESC.
2575 						}
2576 					}
2577 				}
2578 
2579 				String fieldName;
2580 
2581 				if (index == -1) {
2582 					fieldName = token.trim();
2583 				} else {
2584 					fieldName = token.substring(0, index).trim();
2585 				}
2586 
2587 				Field f = this.getFieldByName(fieldName);
2588 
2589 				if (f != null) {
2590 					FieldValue fv = FieldValue.createFieldValueForSorting(f,
2591 							sortDirection);
2592 					logCat.info("Field '" + fieldName + "' is ordered in mode:"
2593 							+ sortDirection);
2594 					result.addElement(fv);
2595 				}
2596 			}
2597 		}
2598 
2599 		return result;
2600 	}
2601 
2602 	/***
2603 	 * DOCUMENT ME!
2604 	 * 
2605 	 * @param request
2606 	 * @param paramStub
2607 	 * @param sortFields
2608 	 * 
2609 	 * @return
2610 	 */
2611 	private Vector createOrderFVFromRequest(HttpServletRequest request,
2612 			String paramStub, Vector sortFields) {
2613 		Vector result = new Vector();
2614 		int fieldIndex = paramStub.length() + 1;
2615 
2616 		// "sort_1" -> fieldindex= 8 (length of paramStub "order_1" is 7)
2617 		for (int i = 0; i < sortFields.size(); i++) {
2618 			String dataParam = (String) sortFields.elementAt(i);
2619 			int fieldId = Integer.parseInt(dataParam.substring(fieldIndex));
2620 			String sortState = ParseUtil.getParameter(request, dataParam);
2621 			logCat.info("### dataparam=" + dataParam);
2622 			logCat.info("### fieldId=" + fieldId);
2623 			logCat.info("### sortState=" + sortState);
2624 
2625 			if (sortState.equalsIgnoreCase("asc")
2626 					|| sortState.equalsIgnoreCase("desc")) {
2627 				int sortDirection = sortState.equalsIgnoreCase("asc") ? Constants.ORDER_ASCENDING
2628 						: Constants.ORDER_DESCENDING;
2629 				FieldValue fv = FieldValue.createFieldValueForSorting(
2630 						getField(fieldId), sortDirection);
2631 				result.addElement(fv);
2632 			}
2633 		}
2634 
2635 		return result;
2636 	}
2637 
2638 	// ------------------------------ dealing with Postion and key Strings
2639 	// ---------------------------------
2640 
2641 	/***
2642 	 * Creates a token string with the format:
2643 	 * 
2644 	 * <pre>
2645 	 *  
2646 	 *  
2647 	 *  
2648 	 *  
2649 	 *  
2650 	 *  
2651 	 *  
2652 	 *  
2653 	 *  
2654 	 *              field.id : field.length : field.value
2655 	 *  
2656 	 *  
2657 	 *  
2658 	 *  
2659 	 *  
2660 	 *  
2661 	 *  
2662 	 *  
2663 	 *  
2664 	 * </pre>
2665 	 * 
2666 	 * @param field
2667 	 *            the field object
2668 	 * @param fieldValue
2669 	 *            the field value
2670 	 * 
2671 	 * @return the token string
2672 	 */
2673 	private String createToken(Field field, String fieldValue) {
2674 		StringBuffer buf = new StringBuffer();
2675 
2676 		buf.append(field.getId());
2677 		buf.append(":");
2678 
2679 		if (!Util.isNull(fieldValue)) {
2680 			buf.append(fieldValue.length());
2681 			buf.append(":");
2682 			buf.append(fieldValue);
2683 		} else {
2684 			buf.append(0);
2685 			buf.append(":");
2686 		}
2687 
2688 		return buf.toString();
2689 	}
2690 
2691 	/***
2692 	 * Fill the input PreparedStatement object
2693 	 * 
2694 	 * @param cur
2695 	 *            the FieldValue object
2696 	 * @param ps
2697 	 *            the PreparedStatement object
2698 	 * @param curCol
2699 	 *            the current PreparedStatement column; points to a
2700 	 *            PreparedStatement xxx value
2701 	 * 
2702 	 * @return DOCUMENT ME!
2703 	 * 
2704 	 * @exception SQLException
2705 	 *                if any error occurs
2706 	 */
2707 	private int fillPreparedStatement(FieldValue cur, PreparedStatement ps,
2708 			int curCol) throws SQLException {
2709 		logCat.info("setting col " + curCol + " with name "
2710 				+ cur.getField().getName() + " to value " + cur.getFieldValue()
2711 				+ " of type " + cur.getField().getType() + " operator "
2712 				+ cur.getOperator());
2713 
2714 		Field curField = cur.getField();
2715 		Object curValue = cur.getFieldValueAsObject();
2716 
2717 		// 20020703-HKK: Extending search algorithm with WEAK_START, WEAK_END,
2718 		// WEAK_START_END
2719 		// results in like '%search', 'search%', '%search%'
2720 		if (FieldTypes.isCHAR(curField.getType())) {
2721 			String valueStr = cur.getFieldValue();
2722 
2723 			switch (cur.getSearchAlgorithm()) {
2724 			case Constants.SEARCH_ALGO_WEAK_START:
2725 				valueStr = '%' + valueStr;
2726 
2727 				break;
2728 
2729 			case Constants.SEARCH_ALGO_WEAK_END:
2730 				valueStr = valueStr + '%';
2731 
2732 				break;
2733 
2734 			case Constants.SEARCH_ALGO_WEAK_START_END:
2735 				valueStr = '%' + valueStr + '%';
2736 
2737 				break;
2738 			}
2739 
2740 			curValue = valueStr;
2741 		}
2742 
2743 		switch (cur.getOperator()) {
2744 		case Constants.FILTER_NULL:
2745 			break;
2746 
2747 		case Constants.FILTER_NOT_NULL:
2748 			break;
2749 
2750 		case Constants.FILTER_EMPTY:
2751 			break;
2752 
2753 		case Constants.FILTER_NOT_EMPTY:
2754 			break;
2755 
2756 		default:
2757 			JDBCDataHelper.fillWithData(ps, curField.getEscaper(), curCol,
2758 					curValue, curField.getType(), getBlobHandlingStrategy());
2759 			curCol++;
2760 		}
2761 
2762 		return curCol;
2763 	}
2764 
2765 	/***
2766 	 * This method generated a datastructure holding sorting information from
2767 	 * "orderBy" only the keys are used as order criteria. By default all
2768 	 * ascending (check SQL spec + docu).
2769 	 */
2770 	private void initDefaultOrderFromKeys() {
2771 		defaultOrder = new FieldValue[getKey().size()];
2772 
2773 		for (int i = 0; i < this.getKey().size(); i++) {
2774 			Field keyField = (Field) getKey().elementAt(i);
2775 			defaultOrder[i] = FieldValue.createFieldValueForSorting(keyField,
2776 					Constants.ORDER_ASCENDING);
2777 		}
2778 
2779 		logCat.info("Table.initDefaultOrderfromKey done.");
2780 	}
2781 
2782 	/***
2783 	 * situation: we have built a query (involving the getWhereEqualsClause()
2784 	 * method) and now we want to prepare the statemtent - provide actual values
2785 	 * for the the '?' placeholders.
2786 	 * 
2787 	 * @param fv
2788 	 *            the array of FieldValue objects
2789 	 * @param ps
2790 	 *            the PreparedStatement object
2791 	 * @param curCol
2792 	 *            the current PreparedStatement column; points to a
2793 	 *            PreparedStatement xxx value
2794 	 * 
2795 	 * @return the value of the current column
2796 	 * 
2797 	 * @exception SQLException
2798 	 *                if any error occurs
2799 	 */
2800 	private int populateWhereAfterClause(FieldValue[] fv, PreparedStatement ps,
2801 			int curCol) throws SQLException {
2802 		if ((fv != null) && (fv.length > 0)) {
2803 			// populate the Ri's
2804 			for (int i = 0; i < fv.length; i++) {
2805 				// populate a "fi OpA(i) fi*"
2806 				curCol = fillPreparedStatement(fv[i], ps, curCol);
2807 
2808 				// populate the "f(i-1) OpB(i-1) f(i-1)* OR f(i-2) OpB(i-2)
2809 				// f(i-2)* OR ... OR f1 OpB f1*"
2810 				if (i > 0) {
2811 					for (int j = i - 1; j >= 0; j--) {
2812 						curCol = fillPreparedStatement(fv[j], ps, curCol);
2813 					}
2814 				}
2815 			}
2816 		}
2817 
2818 		return curCol;
2819 	}
2820 
2821 	protected static DbFormsConfig getConfig() {
2822 		DbFormsConfig config = null;
2823 		try {
2824 			config = DbFormsConfigRegistry.instance().lookup();
2825 		} catch (Exception e) {
2826 			logCat.error("no config object", e);
2827 		}
2828 		return config;
2829 	}
2830 
2831 }