001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org)
002 // The Molly framework is freely distributable under the terms of an
003 // MIT-style license. For details, see the molly pages web site at:
004 // http://www.mollypages.org/. Use, modify, have fun !
005
006 package fc.jdbc.dbo;
007
008 import java.io.*;
009 import java.sql.*;
010 import java.util.*;
011
012 import fc.jdbc.*;
013 import fc.io.*;
014 import fc.util.*;
015 import fc.web.forms.*;
016
017 /**
018 Generates java objects that represent tables in a database. Each object
019 represents 1 table in the database.
020 <p>
021 This framework is great for inserting and updating data in various tables
022 and even fetching one or more rows form individual tables.
023 <p>
024 For each table <font color="blue"><tt>foo</tt></font> in our
025 database, the following classes are generated:
026 <ol>
027 <li><font color="blue">class <tt>Foo</tt></font> which contains all
028 columns of table <tt>foo</tt> as fields and represents a row in that table.
029 Accessor (get/set) methods are provided to modify the values of fields
030 in this class (Note, all generated fields are themselves declared private and
031 we always go through accessor methods so we can keep track of various
032 modifications etc.)
033 <li><font color="blue">class <tt>FooMgr</tt></font> which contains
034 "manager" type functions to read, save, create, etc., instances of class
035 Foo from/to the database. The methods of <tt>FooMgr</tt> could
036 equivalently have been implemented as static methods in <tt>Foo</tt>
037 but they have been separated out in a separate manager class to reduce
038 clutter.
039 <p>
040 This framework is <i>not</i> intended to transparently allow
041 arbitrary joins and data from multiple tables. A <i>better</i> way is
042 to use prepared statements directly to run ad-hoc SQL queries
043 including those containing arbitrary joins.
044 <p>
045 However, to somewhat facilitate arbitrary select queries/joins across multiple tables,
046 each generated "Mgr" class has a <font color=blue><code>columns()</code></font>
047 method that returns a list of columns for the corresponding table. For
048 example, in say a Molly Server Page where information from two tables
049 (table1, table2) is displayed from both tables on the same page.
050 <blockquote><pre style="background: #ccccc;">
051 String my_query = "select "
052 + <font color=blue>table1Mgr.columns()</font> + ", " + <font color=blue>table2.columns()</font>
053 + " from table1, table2 WHERE table1.x = table2.x";
054
055 PreparedStatement ps = connection.prepareStatement(my_query);
056 ResultSet rs = connection.executeQuery();
057 while (rs.next())
058 {
059 table1 t1 = table1Mgr.getFromRS(rs); //create a new table1 from the rs
060 table2 t2 = table2Mgr.getFromRS(rs); //ditto for table2
061 //..use t1 and t2...
062 //....
063 }
064 </pre></blockquote>
065 </ol>
066 <hr>
067 <h2>Configuration</h2>
068 This program uses a user specified configuration file that allows for
069 many code generation options. This file takes the following
070 <a href='doc-files/Generate_usage.txt'> configuration options</a>
071 <p>
072 Here is a minimal <a href='doc-files/sample.conf'>sample
073 configuration</a> file.
074 <hr>
075 <h3>Notes</h3>
076 <b>Note 1</b>: This framework always retrieves and saves data directly to
077 and fro from the database and never caches data internally. This is a
078 design feature and keeps this framework orthogonal to caching
079 issues/implementations. The results returned by the framework can always
080 be cached as needed via say, the {@link fc.util.cache.Cache} utility
081 class(es).
082 <p>
083 <b>Note 2</b>: <b>MySQL</b> 3.x, 4.x or 5.x does not have true boolean types and
084 silently converts bool types to TINYINT. This wreaks havoc with
085 auto-generated code which creates methods with the wrong signature
086 (TINYINT as opposed to bool).
087 <p>
088 There are 2 approaches to solving this mysql-specific problem:
089 <blockquote>
090 a) Require all boolean columns to begin with some keyword (say bool_)
091 and if a column begins with this word, then treat it as a boolean,
092 regardless of the type returned by the database meta data.<br><br>
093 b) Treat all TINYINT's as boolean types. This is the approach I have
094 chosen since TINYINT's are NOT portable across databases (for example
095 PostgresQL does not have TINYINT's). Therefore we should not use
096 TINYINT's in physical database models; if booleans are turned into
097 TINYINT's by MySQL then so be it..since that will not clash with any of
098 our modelled types.
099 </blockquote>
100 If the flag <tt>mysqlBooleanHack</tt> is set to <tt>false</tt> in the
101 configuration file, then TINYINT's are <b>not</b> transformed to booleans.
102 There should be no practical need to do this however.
103 <p>
104 <b>Note 3</b>: <b>MySQL</b> allows its tables and columns to start
105 with a numeral. For example: <i>52_weeks</i>, <i>3_col</i>, etc. This is wrong,
106 not-standard and not-supported. From the spec:
107 </p>
108 <blockquote>
109 SQL identifiers and key words must begin with a letter (a-z, but also letters with diacritical marks and non-Latin letters) or an underscore (_). Subsequent characters in an identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
110 </blockquote>
111 <p>
112 So compiling these wrongly named tables (which only MySQL and no other
113 database allows) results in a bunch of java compiler errors, which may
114 confuse neophytes into believing that the generator is outputting buggy
115 code. No, the generated code is proper and exactly the way its intended to
116 be. Java variables/classes <b>cannot</b> start with a number. Hence,
117 compiler errors. So if you <i>must</i> use MySQL, at least don't name your tables
118 with a number.
119 </p>
120
121 @author hursh jain
122 **/
123 public final class Generate
124 {
125 static final String nl = IOUtil.LINE_SEP;
126 static final String mgrSuffix = "Mgr";
127 static final String DBO_BaseClassName = "fc.jdbc.dbo.DBO";
128 static final String DBO_MgrBaseClassName = "fc.jdbc.dbo.DBOMgr";
129
130 java.util.Date rundate;
131 DatabaseMetaData md;
132 PropertyMgr props;
133 SystemLog log;
134 Connection con;
135 String url;
136 DBspecific dbspecific;
137 File outputDir;
138 String catalogName;
139 String packageName;
140 String classVis;
141 String fieldVis;
142 boolean accessors;
143 String[] tableTypesToProcess;
144 List tableNamesToInclude;
145 Map tableNamesToIncludeAction;
146 final int INCLUDE_ANY_PREFIX = 1,
147 INCLUDE_ANY_SUFFIX = 2,
148 INCLUDE_CONTAINS = 3,
149 INCLUDE_EXACT = 4;
150 List tableNamesToExclude;
151 Map tableNamesToExcludeAction;
152 PrintWriter out;
153 NameWrangle wrangler;
154 int processedTablesCount;
155
156 //TODO: These need to come from a i18n resource file
157 //validate errors/messages
158 String validateNull_ErrorMsg = "Error: Required field, please enter a value";
159 String validateIntegerOnly_ErrorMsg = "Error: Please enter only numbers in this field";
160 String validateText_ErrorMsg_MaxSize = "Not enough or too many characters";
161
162 //list of tables in our database
163 List tableList;
164 //used as a temp string buffer by various methods, can
165 //be reset by setLength(0);
166 StringBuffer strbuf = new StringBuffer(2048);
167
168 //Changed/set for the CURRENT TABLE being processed
169 String beanClassName; //FOO
170 String mgrClassName; //FOO_Mgr
171 Table table; //foo
172 List pklist; //[a, b]
173 List fklist; //[b]
174 String colsCommaDelimString; //"a, b, c, d, e"
175 String pkCommaDelimString; //"a, b"
176 String fkCommaDelimString; //"b, c"
177 String pkFormalParamString; //"int a, byte b....."
178
179 //not enabled -- don't think this is good
180 //enabled again
181 boolean modifiableAutoIncrementColumns;
182
183 /*
184 INTERNAL NOTES:
185 - if a new mgr method is added, also update
186 mgrWriteMethodFields and mgrWriteMethodStats
187 */
188
189 public Generate(String[] args) throws Exception
190 {
191 log = Log.getDefault();
192 log.printTimestamp(true);
193 log.printRelativeTimestamp(true);
194
195 //turn on to debug connection manager etc.
196 //log.setLevel(SystemLog.DEBUG);
197
198 Args myargs = new Args(args);
199 myargs.setDefaultUsage(this);
200 String conf = myargs.getRequired("conf");
201
202 props = new FilePropertyMgr(new File(conf));
203
204 //default logging level for the rest of our app
205 String loglevel = props.get("log.level");
206
207 if (loglevel != null) {
208 log.setLevel(loglevel);
209 }
210
211 ConnectionMgr cmgr = new SimpleConnectionMgr(props);
212
213 String driver_name = cmgr.getDriver().getClass().getName();
214 DBName dbname = DBName.fromDriver(driver_name);
215
216 if (dbname == null) {
217 log.bug("dbname=", dbname);
218 log.error("Could not understand the name of the target database. See documentation for more info.");
219 System.exit(1);
220 }
221
222 dbspecific = (DBspecific)
223 Class.forName("fc.jdbc.dbo.DB" + dbname.toString().toLowerCase())
224 .newInstance();
225
226 url = cmgr.getURL();
227 con = cmgr.getConnection();
228 md = con.getMetaData();
229
230 rundate = new java.util.Date();
231
232 String catalogName = props.get("jdbc.catalog");
233 if (catalogName == null) {
234 catalogName = "";
235 }
236
237 //Generate options
238 String output_dir = props.getRequired("generate.output.dir");
239 outputDir = new File(output_dir);
240
241 if (! outputDir.isDirectory() || ! outputDir.canWrite())
242 {
243 log.error("Specified output location '" + output_dir + "' is not a directory and/or is not writable");
244 System.exit(1);
245 }
246
247 if (! outputDir.exists())
248 {
249 System.out.print("Output directory: " +
250 output_dir + " does not exist. Creating..");
251 outputDir.mkdirs();
252 System.out.println("..done");
253 }
254
255 log.info("Output Directory: ", outputDir);
256
257 modifiableAutoIncrementColumns =
258 Boolean.valueOf(
259 props.get("generate.modifiableAutoIncrementColumns", "false")
260 ).booleanValue();
261
262 packageName = props.get("generate.class_package");
263
264 accessors = Boolean.valueOf(
265 props.get("generate.accessors", "true")).
266 booleanValue();
267
268 classVis = props.get("generate.class_vis", "public");
269 fieldVis = (accessors) ?
270 props.get("generate.field_vis", "private") :
271 /* public by default if no accessors */
272 props.get("generate.field_vis", "public");
273
274
275 String tabletypes = props.get("target.types_to_process");
276 if (tabletypes != null)
277 {
278 tableTypesToProcess = tabletypes.split(",\\s*");
279 for (int n=0; n < tableTypesToProcess.length; n++) {
280 tableTypesToProcess[n] = tableTypesToProcess[n].toUpperCase();
281 }
282 }
283
284 String table_include = props.get("target.tables_to_process");
285 if (table_include != null)
286 {
287 tableNamesToInclude = new ArrayList();
288 tableNamesToIncludeAction = new HashMap();
289 createActions(table_include, tableNamesToInclude, tableNamesToIncludeAction);
290 log.bug("Table names to include:", tableNamesToInclude);
291 log.bug("Table names to include action:", tableNamesToIncludeAction);
292 }
293
294 String table_exclude = props.get("target.tables_to_ignore");
295 if (table_exclude != null)
296 {
297 tableNamesToExclude = new ArrayList();
298 tableNamesToExcludeAction = new HashMap();
299 createActions(table_exclude, tableNamesToExclude, tableNamesToExcludeAction);
300 log.bug("Table names to exclude:", tableNamesToExclude);
301 log.bug("Table names to exclude action:", tableNamesToExcludeAction);
302 }
303
304
305 wrangler = new NameWrangle(props, log);
306
307 Watch w = new Watch();
308 w.start();
309 readTables();
310 generateCode();
311 cmgr.close();
312 System.out.println("Generator processed "
313 + processedTablesCount
314 + " table"
315 + ((processedTablesCount > 1) ? "s" : "")
316 + " in "
317 + w.timeInSeconds()
318 + " seconds.");
319 }
320
321 void createActions(String parameter, List names, Map actionMap) throws Exception
322 {
323 String[] temp = parameter.split(",\\s*");
324 for (int n = 0; n < temp.length; n++)
325 {
326 //we match table names from database to those specified
327 //in the config file, both are lowercased before matching
328 //trim() in case table_to_process="foo "
329 temp[n] = temp[n].trim().toLowerCase();
330
331 boolean startsWithStar = false, endsWithStar = false;
332 if (temp[n].startsWith("*"))
333 {
334 if (temp[n].length() == 1) {
335 throw new Exception("Bad option in config file.\nIn line: " + parameter + "\nA star must be a prefix/suffix to a tablename, not standalone.");
336 }
337 temp[n] = temp[n].substring(1, temp[n].length());
338 startsWithStar = true;
339 }
340 if (temp[n].endsWith("*"))
341 {
342 if (temp[n].length() == 1) {
343 throw new Exception("Bad option in config file.\nIn line: " + parameter + "\nA star must be a prefix/suffix to a tablename, not standalone.");
344 }
345 temp[n] = temp[n].substring(0, temp[n].length()-1);
346 endsWithStar = true;
347 }
348
349 if (startsWithStar)
350 {
351 if (endsWithStar) { //both start/end star
352 actionMap.put(temp[n], INCLUDE_CONTAINS);
353 }
354 else{ //start_star
355 actionMap.put(temp[n], INCLUDE_ANY_PREFIX);
356 }
357 }
358 else {
359 if (endsWithStar) { //end_star
360 actionMap.put(temp[n], INCLUDE_ANY_SUFFIX);
361 }
362 else{ //exact_match
363 actionMap.put(temp[n], INCLUDE_EXACT);
364 }
365 }
366
367 names.add(temp[n]);
368 }
369 }
370
371 boolean matchTable(String tablename, List names, Map actions) throws IOException
372 {
373 boolean match = false;
374
375 for (int p = 0; p < names.size(); p++)
376 {
377 String target = (String) names.get(p);
378 int action = (Integer) actions.get(target);
379
380 //target=foo*, lower_tablename = foo_xxx, foo etc
381 if (action == INCLUDE_ANY_SUFFIX)
382 {
383 if (tablename.startsWith(target)) {
384 match = true;
385 break;
386 }
387 }
388 //target=*foo, lower_tablename = xxx_foo, foo etc
389 else if (action == INCLUDE_ANY_PREFIX)
390 { //*<tablee>
391 if (tablename.endsWith(target)) {
392 match = true;
393 break;
394 }
395 }
396 else if (action == INCLUDE_CONTAINS)
397 {
398 if (tablename.indexOf(target) != -1) {
399 match = true;
400 break;
401 }
402 }
403 else if (action == INCLUDE_EXACT)
404 {
405 if (tablename.equals(target)) {
406 match = true;
407 break;
408 }
409 }
410 else{
411 throw new IOException("Internal error. Unrecognized action = " + action);
412 }
413 } //for
414
415 return match;
416 }
417
418 void readTables() throws IOException, SQLException
419 {
420 tableList = new ArrayList();
421
422 Table.init(md, log, props, dbspecific);
423
424 //not useful generally at least with mysql, postgresql
425 String schemaPattern = null;
426 //we get all tables, maybe if this is needed at all
427 //we can add this as a config file option
428 String tableNamePattern = "%";
429
430 ResultSet rs = md.getTables(catalogName, schemaPattern,
431 tableNamePattern, tableTypesToProcess);
432
433 while (rs.next())
434 {
435 String tablename = rs.getString("TABLE_NAME");
436 //tablename cannot be null if the driver is unbroken
437 //but can it be an empty string ?
438 if (tablename.intern() == "") {
439 throw new SQLException("The returned tablename was an empty string, looks like the JDBC driver is broken");
440 }
441
442 String lower_tablename = tablename.toLowerCase();
443
444 boolean include = false;
445
446 if (tableNamesToInclude != null)
447 {
448 //including only certain tables, if it is NOT in
449 //include list we continue to the next table
450 include = matchTable(lower_tablename, tableNamesToInclude, tableNamesToIncludeAction);
451
452 if (! include) {
453 log.bug("Ignoring table: ", tablename);
454 continue;
455 }
456 }
457
458
459 boolean exclude = false;
460
461 if (tableNamesToExclude != null)
462 {
463 exclude = matchTable(lower_tablename, tableNamesToExclude, tableNamesToExcludeAction);
464
465 if (exclude) {
466 log.bug("Ignoring table (via exclude): ", tablename);
467 continue;
468 }
469 }
470
471 log.info(">>>> Processing table: ", tablename);
472 processedTablesCount++;
473
474 String tabletype = rs.getString("TABLE_TYPE");
475 String remarks = rs.getString("REMARKS");
476
477 Table table = new Table(con,
478 catalogName, schemaPattern,
479 tablename, tabletype, remarks
480 );
481 tableList.add(table);
482 } //~while rs.next
483 } //~process
484
485 void generateCode() throws IOException, SQLException
486 {
487 for (int n = 0; n < tableList.size(); n++)
488 {
489 table = (Table) tableList.get(n);
490 pklist = (List) table.getPKList();
491 fklist = (List) table.getFKList();
492
493 //list of pk's for this table as formal parameters
494 //in a generated method, for example:
495 // int col_a, String col_b, Date col_c
496 int size = pklist.size();
497 StringBuffer buf = new StringBuffer(128);
498 for (int m = 0; m < size; m++)
499 {
500 ColumnData cd = (ColumnData) pklist.get(m);
501 buf.append(cd.getJavaTypeFromSQLType());
502 buf.append(" ");
503 buf.append(cd.getName());
504 if ( m < (size-1))
505 buf.append(", ");
506 }
507 pkFormalParamString = buf.toString();
508
509 colsCommaDelimString = Table.getListAsString(table.getColumnList());
510 pkCommaDelimString = Table.getListAsString(pklist);
511 fkCommaDelimString = Table.getListAsString(fklist);
512 writeBeanCode();
513 writeMgrCode();
514 }
515 }
516
517 void writeBeanCode() throws IOException, SQLException
518 {
519 beanClassName = wrangler.getClassName(table.getName());
520 String filename = beanClassName + ".java";
521 File f = new File(outputDir, filename);
522
523 /* this ensures that the new file will not have the same case as the
524 old file, jdk1.5 on osx 10.4 and possibly others keep the existing case
525 of the file if it already exists.
526 */
527 if (f.exists())
528 f.delete();
529
530 out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
531
532 writePackage();
533 writePrologue();
534 writeImports();
535 beanWriteClass();
536 out.close();
537 }
538
539 void writeMgrCode() throws IOException, SQLException
540 {
541 mgrClassName =
542 wrangler.getClassName(table.getName()) + mgrSuffix;
543
544 String filename = mgrClassName + ".java";
545 File f = new File(outputDir, filename);
546
547 /* this ensures that the new file will not have the same case as the
548 old file, jdk1.5 on osx 10.4 and possibly others keep the existing case
549 of the file if it already exists.
550 */
551 if (f.exists())
552 f.delete();
553
554 out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
555
556 writePackage();
557 writePrologue();
558 writeImports();
559 mgrWriteClass();
560 out.close();
561 }
562
563
564 void writePrologue()
565 {
566 String name = getClass().getName();
567 ol("/*");
568 ol(" * Auto generated on: " + rundate);
569 ol(" * JDBC url: [" + url + "]");
570 ol(" * WARNING: Manual edits will be lost if/when this file is regenerated.");
571 ol(" */");
572 }
573
574 void writePackage()
575 {
576 if (packageName != null)
577 {
578 ol("package " + packageName + ";");
579 ol();
580 }
581 }
582
583 void writeImports()
584 {
585 ol("import java.io.*;");
586 ol("import java.math.*;");
587 ol("import java.sql.*;");
588 ol("import java.util.*;");
589 ol();
590 ol("import fc.io.*;");
591 ol("import fc.jdbc.*;");
592 ol("import fc.jdbc.dbo.*;");
593 ol("import fc.util.*;");
594 ol("import fc.web.forms.*;");
595 ol();
596 }
597
598
599 /**
600 returns the comment used for get/set methods and
601 for fields in the bean class
602 */
603 final HashMap commentMap = new HashMap();
604 String getBeanComment(ColumnData item) throws SQLException
605 {
606 String comment = (String) commentMap.get(item);
607 if (comment != null)
608 return comment;
609
610 comment =
611 "/** " + item.getSQLTypeDriverSpecificName() +
612 " (" + item.getSQLTypeName() + ")";
613 if (item.isPK()) {
614 comment += "; PK=yes";
615 }
616 if (item.isFK()) {
617 ColumnData.FKData fkdata = item.getFK();
618 comment += "; FK=yes, refers to: "
619 + fkdata.getPKTableName() + "."
620 + fkdata.getPKColName();
621 }
622 comment += "; Nullable=" + item.isNullable();
623 comment += "; AutoInc=" + item.isAutoIncrement();
624 comment += "; MaxSize=" + item.getSize();
625 if (item.hasRemarks()) {
626 comment += "; Remarks: " + item.getRemarks();
627 }
628 comment += " */";
629
630 commentMap.put(item, comment);
631 return comment;
632 }
633
634
635 void beanWriteClass() throws SQLException
636 {
637 ol("/**");
638 o ("Represents a row in the ");
639 o ( table.getName());
640 ol(" table. ");
641
642 String remarks = table.getRemarks();
643 if ( remarks != null)
644 {
645 ol("<p><b>Table Remarks: </b>");
646 ol(remarks);
647 }
648
649 ol("*/");
650 o(classVis + " class ");
651 o(beanClassName); //classname
652 o(" extends ");
653 ol(DBO_BaseClassName);
654 ol("{");
655
656 beanWriteConstructor();
657 beanWriteDBFields();
658 beanWriteDBFieldsTracking();
659 beanWriteMiscMethods();
660 beanWriteGetSet();
661 ol("}");
662 }
663
664 void beanWriteConstructor() {
665 ol("/* Default constructor */");
666 o(classVis);
667 o(" ");
668 o(beanClassName);
669 ol("()");
670 ol(" {");
671 ol(" this.__isNew = true;");
672 ol(" }");
673 ol();
674 }
675
676 void beanWriteDBFields() throws SQLException
677 {
678 List cols = table.getColumnList();
679
680 ol("/*--------------------- Columns ------------------------*/");
681 //write database fields
682 TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();
683 config.setPrintBorders(false);
684 config.setCellSpacing(1);
685 config.setCellPadding(0);
686 config.setAutoFit(true);
687 TablePrinter p = new TablePrinter(4, out, config);
688 p.startTable();
689 for (int n = 0; n < cols.size(); n++)
690 {
691 ColumnData cd = (ColumnData) cols.get(n);
692 log.bug("Processing column: ", cd);
693 p.startRow();
694 p.printCell(fieldVis);
695 p.printCell(cd.getJavaTypeFromSQLType());
696 p.printCell(cd.getName() + ";");
697 p.printCell(getBeanComment(cd));
698 p.endRow();
699 }
700 p.endTable();
701 ol("/*------------------------------------------------------*/");
702 }
703
704 void beanWriteDBFieldsTracking() throws SQLException
705 {
706 if (pklist.size() > 0)
707 {
708 ol("/*");
709 ol("Original PK saved here for updates. If a row is retrieved from the database and");
710 ol("the PK value is changed, and then if the object is saved, we need the orignal PK");
711 ol("value to find the row in the db for our update to work.");
712 ol("*/");
713 for (int n = 0; n < pklist.size(); n++)
714 {
715 ColumnData cd = (ColumnData) pklist.get(n);
716 o (cd.getJavaTypeFromSQLType());
717 o (" __orig_");
718 o (cd.getName());
719 ol(";");
720 }
721 }
722
723 ol();
724 ol(" boolean __force_update = false;");
725 ol("private Map __extra_data; ");
726 ol("private boolean __isNew = false;");
727 ol("private boolean __isModified = false;");
728
729 List cols = table.getColumnList();
730 for (int n = 0; n < cols.size(); n++)
731 {
732 //modified column ?
733 ol();
734 ColumnData cd = (ColumnData) cols.get(n);
735 String colname = cd.getName();
736 o("private boolean __isModified_");
737 o(colname);
738 ol(" = false;");
739 o("protected boolean __isNullInDB_");
740 o(colname);
741 ol(" = false;");
742 o("/**returns <tt>true</tt> if ");
743 o(colname);
744 o(" has changed since it was created/loaded, <tt>false</tt> otherwise");
745 ol("*/");
746 o("public boolean ");
747 o(wrangler.getIsModifiedName(cd));
748 o("() { return this.__isModified_");
749 o(colname);
750 ol("; }");
751 //column null in database ?
752 o("/**returns <tt>true</tt> if ");
753 o(colname);
754 o(" is null in the database");
755 ol("*/");
756 o("public boolean ");
757 o(wrangler.getIsNullInDBName(cd));
758 o("() { return this.__isNullInDB_");
759 o(colname);
760 ol("; }");
761 }
762 }
763
764 void beanWriteMiscMethods() throws SQLException
765 {
766 ol();
767 //isnew
768 ol();
769 ol("/** returns <tt>true</tt> if this object is newly created and has <b>not</b> been loaded from the database, <tt>false</tt> otherwise */");
770 ol("public boolean isNew() ");
771 ol(" {");
772 ol(" return this.__isNew;");
773 ol(" }");
774
775 ol();
776 ol("/** Specify <tt>true</tt> to set this object's status as newly created (and not read from a database) */");
777 ol("protected void setNew(boolean val) ");
778 ol(" {");
779 ol(" this.__isNew = val;");
780 ol(" }");
781
782 //modified
783 ol();
784 ol("/** returns <tt>true</tt> if this object's data (for any field) has changed since it was created/loaded, <tt>false</tt> otherwise */");
785 ol("public boolean isModified() ");
786 ol(" {");
787 ol(" return this.__isModified;");
788 ol(" }");
789
790 ol();
791 ol("/** Resets the modified status of this object to not-modified");
792 ol("this is useful when loading an object via a prepared statement");
793 ol("(by using various setXXX methods when we do so, we inadvertently");
794 ol("set the modified status of each field to true)");
795 ol("*/");
796 ol("void resetModified() ");
797 ol(" {");
798 List collist = table.getColumnList();
799 ol(" this.__isModified = false;");
800 for (int n = 0; n < collist.size(); n++)
801 {
802 ColumnData cd = (ColumnData) collist.get(n);
803 String colname = cd.getName();
804 o(" this.__isModified_");
805 o(colname);
806 ol(" = false;");
807 }
808 ol(" }");
809
810 ol();
811 ol("/**");
812 ol("Allows putting arbitrary object-specific data into this object.");
813 ol("This is useful to store additional query-specific columns when performing");
814 ol("arbitrary queries. For example: ");
815 ol("<blockquote><pre>");
816 o ("String query = \"select <font color=blue>1+1 as my_sum, \n\t\tnow() as my_time</font>, ");
817 o (table.getName());
818 o ("Mgr.columns() \n\t\tfrom ");
819 o (table.getName());
820 ol("\";");
821 ol("PreparedStatement ps = con.prepareStatment(query);");
822 ol("ResultSet rs = ps.executeQuery();");
823 ol("List list = new ArrayList();");
824 ol("while (rs.next()) {");
825 o (" <font color=green>"); o (table.getName()); o ("</font> obj = ");
826 o(table.getName());
827 ol("Mgr.getFromRS(rs);");
828 ol(" obj.<font color=blue>putExtraData(\"my_sum\"</font>, rs.getInt(\"my_sum\"));");
829 ol(" obj.<font color=blue>putExtraData(\"my_time\"</font>, rs.getDate(\"my_time\"));");
830 ol(" }");
831 o ("//use the list later on...each <font color=green>");
832 o (table.getName());
833 ol(" </font>object in the list will ");
834 ol("//have the extra data..");
835 ol("</pre></blockquote>");
836 ol("*/");
837 ol("public void putExtraData(Object key, Object value) ");
838 ol(" {");
839 ol(" synchronized (this) {");
840 ol(" if (__extra_data == null) {");
841 ol(" __extra_data = new HashMap();");
842 ol(" }");
843 ol(" }");
844 ol(" __extra_data.put(key, value);");
845 ol(" }");
846
847 ol();
848 ol("/**");
849 ol("Allows retrieving arbitrary object-specific data from this object.");
850 ol("This data should have been put via the {@link #putExtraData putExtraData} ");
851 ol("method prior to invoking this method");
852 ol("*/");
853 ol("public Object getExtraData(Object key) ");
854 ol(" {");
855 ol(" synchronized (this) {");
856 ol(" if (__extra_data == null) {");
857 ol(" return null;");
858 ol(" }");
859 ol(" }");
860 ol(" return __extra_data.get(key);");
861 ol(" }");
862
863 //toString
864 ol();
865 ol("public String toString() ");
866 ol(" {");
867 ol(" final String nl = fc.io.IOUtil.LINE_SEP;");
868 ol(" StringBuffer buf = new StringBuffer(256);");
869 o (" buf.append(\"Class Name: [");
870 o (beanClassName);
871 ol("]\");");
872 ol(" buf.append(\" [isDiscarded=\").append(this.isDiscarded()).append(\"]\");");
873 ol(" buf.append(\" [isNew=\").append(this.isNew()).append(\"]\");");
874 ol(" buf.append(\" [isModified=\").append(this.isModified()).append(\"]\");");
875 ol(" buf.append(nl);");
876 ol(" buf.append(\"Note: IsNullInDB only meaningful for existing rows (i.e., isNew=false)\");");
877 ol(" buf.append(nl);");
878 ol();
879 ol(" ByteArrayOutputStream out = new ByteArrayOutputStream(768);");
880 ol(" TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();");
881 ol(" config.setPrintBorders(false);");
882 ol(" config.setCellSpacing(1);");
883 ol(" config.setCellPadding(0);");
884 ol(" config.setAutoFit(true);");
885 ol(" TablePrinter p = new TablePrinter(7, new PrintStream(out), config);");
886 ol(" p.startTable();");
887 ol();
888 ol(" p.startRow();");
889 ol(" p.printCell(\"PK\");");
890 ol(" p.printCell(\"FK\");");
891 ol(" p.printCell(\"Field\");");
892 ol(" p.printCell(\"Value\");");
893 ol(" p.printCell(\"isModified\");");
894 ol(" p.printCell(\"isNullinDB\");");
895 ol(" p.printCell(\"isSerial/AutoInc\");");
896 ol(" p.endRow();");
897
898 for (int n = 0; n < collist.size(); n++)
899 {
900 ColumnData cd = (ColumnData) collist.get(n);
901 String isPK = cd.isPK() ? "x" : "-";
902 String isFK = "-";
903 if (cd.isFK()) {
904 ColumnData.FKData fk = cd.getFK();
905 isFK = "x [" + fk.getPKTableName() + "." +
906 fk.getPKColName() + "]";
907
908 }
909 String colname = cd.getName();
910 String value = "String.valueOf(this." + colname + ")";
911 String modified = "(this.__isModified_" + colname + ")";
912 String isnull = "(this.__isNullInDB_" + colname + ")";
913 String isAI = cd.isAutoIncrement() ? "x" : "-";
914
915 ol();
916 ol(" p.startRow();");
917 o (" p.printCell(\""); o(isPK); ol("\");");
918 o (" p.printCell(\""); o(isFK); ol("\");");
919 o (" p.printCell(\""); o(colname); ol("\");");
920 o (" p.printCell("); o(value); ol(");");
921 o (" p.printCell("); o(modified); ol(" ?\"x\":\"-\");");
922 o (" p.printCell("); o(isnull); ol(" ?\"x\":\"-\");");
923 o (" p.printCell(\""); o(isAI); ol("\");");
924 ol(" p.endRow();");
925 }
926 ol();
927 ol(" p.endTable();");
928 ol(" buf.append(out.toString());");
929 ol(" return buf.toString();");
930 ol(" }");
931
932 ol();
933 ol("/**");
934 ol("Returns a map of all fields->values (as Strings) contained in this");
935 ol("object. This is useful when sending auto converting the object to JSON, etc.");
936 ol("*/");
937 ol("public Map allFieldsMap() ");
938 ol(" {");
939 ol(" final HashMap m = new HashMap();");
940 for (int n = 0; n < collist.size(); n++)
941 {
942 ColumnData cd = (ColumnData) collist.get(n);
943
944 o (" m.put(\"");
945 o (cd.getName());
946 o ("\", ");
947
948 if (! cd.usesPrimitiveJavaType()) {
949 o ("("); o(cd.getName()); o(" == null ? null : ");
950 }
951
952 o ("String.valueOf(this."); o(cd.getName()); o(")");
953
954 if (! cd.usesPrimitiveJavaType()) {
955 o (")");
956 }
957
958 ol(");");
959 }
960 ol(" return m;");
961 ol(" }");
962 ol();
963 }
964
965 void beanWriteGetSet() throws SQLException
966 {
967 List cols = table.getColumnList();
968
969 ol("/* ============== Gets and Sets ============== */");
970 for (int n = 0; n < cols.size(); n++)
971 {
972 ColumnData cd = (ColumnData) cols.get(n);
973 ol();
974 ol(getBeanComment(cd));
975 String colname = cd.getName();
976 o("public ");
977 o(cd.getJavaTypeFromSQLType());
978 o(" ");
979 o(wrangler.getGetName(cd));
980 ol("() { ");
981 o(" return ");
982 o(colname);
983 ol(";");
984 ol(" }");
985
986 ol();
987 ol(getBeanComment(cd));
988 if (cd.isAutoIncrement() && (! modifiableAutoIncrementColumns)) {
989 //we need to generate set with package access
990 //to set the id when loading the object internally
991 //from a result set
992 o("/* Generating set for ");
993 o(wrangler.getSetName(cd));
994 ol(" with non public access since it's an auto-increment column */");
995 }
996 else {
997 o("public");
998 }
999 o(" ");
1000 o(beanClassName);
1001 o(" ");
1002 o(wrangler.getSetName(cd));
1003 o("(");
1004 o(cd.getJavaTypeFromSQLType());
1005 o(" ");
1006 o(colname);
1007 ol(") {");
1008 ol(" this." + colname + " = " + colname + ";");
1009 ol(" this.__isModified_" + colname + " = true;" );
1010 ol(" this.__isModified = true;" );
1011 ol(" return this;");
1012 ol(" }");
1013 }
1014 }
1015
1016 void mgrWriteClass() throws SQLException
1017 {
1018 //classname
1019 ol("/**");
1020 o("Manages various operations on the ");
1021 o(table.getName());
1022 ol(" table. ");
1023 ol("<p>Most methods of this class take a {@link java.sql.Connection Connection}");
1024 ol("as an argument and use that connection to run various queries. ");
1025 ol("The connection parameter is never closed by methods in this class and that connection");
1026 ol("can and should be used again. Methods of this class will also throw a <tt>IllegalArgumentException</tt>");
1027 ol("if the specified connection object is <tt>null</tt>.");
1028 ol();
1029 ol("<p>Thread Safety: Operations on this class are by and large thread safe in that");
1030 ol("multiple threads can call the methods at the same time. However, seperate threads");
1031 ol("should use seperate connection objects when invoking methods of this class.");
1032 ol("*/");
1033 o(classVis + " final class " + mgrClassName);
1034 o(" extends ");
1035 ol(DBO_MgrBaseClassName);
1036 ol("{");
1037
1038 mgrWriteFields();
1039 mgrWriteConstructor();
1040 mgrWriteMethods();
1041 ol("}");
1042 }
1043
1044 void mgrWriteConstructor()
1045 {
1046 ol();
1047 ol("/** Constructor is private since class is never instantiated */");
1048 o("private ");
1049 o(mgrClassName);
1050 ol("() {");
1051 ol("\t}");
1052 ol();
1053 }
1054
1055 void mgrWriteFields() throws SQLException
1056 {
1057 ol("/* --- Fields used for collecting usage statistics --- ");
1058 ol("Increments to these don't need to be synchronized since these are");
1059 ol("ints and not longs and memory visibility is not an issue in the");
1060 ol("toString() method (in which these are read).");
1061 ol("*/");
1062 ol("private static int __getall_called = 0;");
1063 ol("private static int __getlimited_called = 0;");
1064 ol("private static int __getbykey_called = 0;");
1065 ol("private static int __getwhere_called = 0;");
1066 ol("private static int __getusing_called = 0;");
1067 ol("private static int __getusing_ps_called = 0;");
1068 ol("private static int __getfromrs_called = 0;");
1069 ol("private static int __save_called = 0;");
1070 ol("private static int __delete_called = 0;");
1071 ol("private static int __deletebykey_called = 0;");
1072 ol("private static int __deletewhere_called = 0;");
1073 ol("private static int __deleteusing_called = 0;");
1074 ol("private static int __count_called = 0;");
1075 ol("private static int __countwhere_called = 0;");
1076 ol("private static int __countusing_called = 0;");
1077 ol("private static int __exists_called = 0;");
1078 ol("/* -------------- end statistics fields -------------- */");
1079 }
1080
1081 void mgrWriteMethods() throws SQLException
1082 {
1083 //get
1084 mgrWriteMethodGetAll();
1085 mgrWriteMethodGetAllNoClause();
1086 mgrWriteMethodGetLimited();
1087 mgrWriteMethodGetByKey();
1088 mgrWriteMethodGetWhere();
1089 mgrWriteMethodGetUsing();
1090 mgrWriteMethodGetUsingNoClause();
1091 mgrWriteMethodGetUsingPS();
1092 mgrWriteMethodGetColumnNames();
1093 mgrWriteMethodGetColumnNames2();
1094 mgrWriteMethodGetFromRS();
1095 mgrWriteMethodGetFromRS2();
1096 mgrWriteMethodGetFromRS1Table();
1097 mgrWriteMethodDecodeFromRS();
1098 //save
1099 mgrWriteMethodSave();
1100 mgrWriteMethodUpdate();
1101 //delete
1102 mgrWriteMethodDelete();
1103 mgrWriteMethodDeleteByKey();
1104 mgrWriteMethodDeleteUsing();
1105 mgrWriteMethodDeleteWhere();
1106 //count, exists, misc.
1107 mgrWriteMethodCount();
1108 mgrWriteMethodCountWhere();
1109 mgrWriteMethodCountUsing();
1110 mgrWriteMethodExists();
1111 mgrWriteMethodExistsUsing();
1112 mgrWriteMethodPrepareStatement();
1113 mgrWriteCheckDiscarded();
1114 mgrWriteMethodStats();
1115 mgrWriteMethodToString();
1116 //validation stuff
1117 mgrWriteValidators();
1118 }
1119
1120 /*
1121 We return a list for no particular reason, it may have
1122 been better to return a bean_type[] instead, which would
1123 save typing casts. Testing shows that:
1124
1125 bean_type[] arr = (bean_type[]) list.toArray(new bean_type[]);
1126
1127 takes 1-2 ms for a 1000 elements so speed is not an issue
1128 (although space might be since the list.toArray is newly
1129 created).
1130 */
1131 final void mgrWriteMethodGetAll()
1132 {
1133 ol();
1134 o("static private final String getAllStmt = \"SELECT ");
1135 o(colsCommaDelimString);
1136 o(" from ");
1137 o(table.getName());
1138 ol("\";");
1139 ol("/** ");
1140 ol("Returns all rows in the table. Use with care for large tables since");
1141 ol("this method can result in VM out of memory errors. <p>This method");
1142 ol("also takes an optional (can be null) <tt>clause</tt> parameter which");
1143 ol("is sent as is to the database. For example, a clause can be:");
1144 ol("<blockquote><pre>");
1145 ol("order by some_column_name");
1146 ol("</pre> </blockquote>");
1147 o("@return a list containing {@link ");
1148 o(beanClassName);
1149 o(" } objects <i>or an empty list</i> if there are no rows in the database");
1150 ol("*/");
1151 ol("public static List getAll(final Connection con, final String clause) throws SQLException");
1152 ol(" {");
1153 ol(" __getall_called++;");
1154 ol(" final List list = new ArrayList();");
1155 //prepared statement has no parameters to set, used for
1156 //caching advantage only, (thus, no need to clear params)
1157 ol(" final String getAllStmtClaused = (clause == null) ? ");
1158 ol(" getAllStmt : getAllStmt + \" \" + clause;");
1159 ol(" PreparedStatement ps = prepareStatement(con, getAllStmtClaused);");
1160 ol(" log.bug(\"Query to run: \", ps);");
1161 ol(" final ResultSet rs = ps.executeQuery();");
1162 ol(" while (true) {");
1163 ol(" " + beanClassName + " bean = decodeFromRS(rs);");
1164 ol(" if (bean == null) { break; } ");
1165 ol(" list.add(bean);");
1166 ol(" }");
1167 ol(" rs.close();");
1168 ol(" return list;");
1169 ol(" }");
1170 } //~write getall
1171
1172 final void mgrWriteMethodGetAllNoClause()
1173 {
1174 ol();
1175 ol("/** ");
1176 o ("Convenience method that invokes {@link getAll(Connection, ");
1177 o (beanClassName);
1178 ol(", String) getAll} with an empty additional clause.");
1179 ol("*/");
1180 o ("public static List getAll(final Connection con) ");
1181 ol("throws ValidateException, SQLException");
1182 ol(" {");
1183 ol(" return getAll(con, null);");
1184 ol(" }");
1185 } //~write getUsing
1186
1187 final void mgrWriteMethodGetLimited()
1188 {
1189 ol();
1190 o("static private final String getLimitedStmt = \"SELECT ");
1191 o(colsCommaDelimString);
1192 o(" from ");
1193 o(table.getName());
1194 ol("\";");
1195 ol("/** ");
1196 ol("Returns all rows in the table starting from some row number and limited");
1197 ol("by a certain number of rows after that starting row. ");
1198 ol("<p>");
1199 ol("This method takes a required (non-null) <code>order_clause</code>, since when using");
1200 ol("a limit clause, rows must be ordered for the limit to make sense. The");
1201 ol("clause should be of the form <font color=blue>order by ...</font>");
1202 ol("<p>");
1203 ol("The <code>limit</code> specifies the number of rows that will be returned. (those many");
1204 ol("or possibly lesser rows will be returned, if the query itself yields less");
1205 ol("rows).");
1206 ol("<p>");
1207 ol("The <code>offset</code> skips that many rows before returning rows. A zero offset is");
1208 ol("the same as a traditional query with no offset clause, where rows from");
1209 ol("the beginning are returned. If say, offset = 10, then rows starting from");
1210 ol("row 11 will be returned.");
1211 ol("<p>");
1212 ol("The sql-query generated by this method is database specific but will (typically) look like:");
1213 ol("<blockquote><pre>");
1214 ol("select <column_list> from <table> order by <clause> limit 5 offset 10");
1215 ol("</pre> </blockquote>");
1216 o("@return a list containing {@link ");
1217 o(beanClassName);
1218 o(" } objects <i>or an empty list</i> if there are no rows in the database");
1219 ol("*/");
1220 ol("public static List getLimited(final Connection con, final String order_clause, int limit, int offset) throws SQLException");
1221 ol(" {");
1222 ol(" __getlimited_called++;");
1223 ol(" final List list = new ArrayList();");
1224 //prepared statement has no parameters to set, used for
1225 //caching advantage only, (thus, no need to clear params)
1226 ol(" final String tmp = getLimitedStmt + \" \" + order_clause + \" LIMIT \" + limit + \" OFFSET \" + offset;");
1227 ol(" PreparedStatement ps = prepareStatement(con, tmp);");
1228 ol(" log.bug(\"Query to run: \", ps);");
1229 ol(" final ResultSet rs = ps.executeQuery();");
1230 ol(" while (true) {");
1231 ol(" " + beanClassName + " bean = decodeFromRS(rs);");
1232 ol(" if (bean == null) { break; } ");
1233 ol(" list.add(bean);");
1234 ol(" }");
1235 ol(" rs.close();");
1236 ol(" return list;");
1237 ol(" }");
1238 } //~write getlimited
1239
1240 final void mgrWriteMethodGetByKey() throws SQLException
1241 {
1242 ol();
1243 if (pklist.size() == 0) {
1244 ol("/* getByKey() not implemented since this table does not have any primary keys defined */");
1245 return;
1246 }
1247
1248 o("static private final String getByPKStmt = \"SELECT ");
1249 o(colsCommaDelimString);
1250 o(" from ");
1251 o(table.getName());
1252 o(" WHERE ");
1253 o(Table.getPreparedStmtPlaceholders(pklist));
1254 ol("\";");
1255 ol("/** ");
1256 ol("Returns <b>the</b> row corresponding to the specified primary key(s) of this table ");
1257 ol("or <b><tt>null</tt></b> if no row was found.");
1258 ol("<p>This method uses a prepared statement and is safe from SQL injection attacks");
1259 ol("*/");
1260 o("public static ");
1261 o(beanClassName);
1262 o(" getByKey(final Connection con, ");
1263 o(pkFormalParamString);
1264 ol(") throws SQLException");
1265 ol(" {");
1266 ol(" __getbykey_called++;");
1267 ol(" PreparedStatement ps = prepareStatement(con, getByPKStmt);");
1268 ol(" StringBuilder buf = null;");
1269 ol();
1270 for (int n = 0; n < pklist.size(); n++)
1271 {
1272 ColumnData cd = (ColumnData) pklist.get(n);
1273 if (! cd.usesPrimitiveJavaType())
1274 {
1275 o (" if ("); o(cd.getName()); ol(" == null) {");
1276 ol(" if (buf == null) { buf = new StringBuilder(); }");
1277 o (" buf.append(\"");
1278 o (cd.getName());
1279 ol(" was set to null (but is non-nullable)\").append(IOUtil.LINE_SEP);");
1280 ol(" }");
1281 ol();
1282 }
1283 String varname = cd.getName();
1284 o("\t");
1285 String pos = String.valueOf((n+1));
1286 ol(cd.getPreparedStmtSetMethod("ps.", pos, varname) );
1287 }
1288
1289 ol(" if (buf != null) {");
1290 ol(" throw new ValidateException(buf.toString());");
1291 ol(" }");
1292 ol(" final ResultSet rs = ps.executeQuery();");
1293 ol(" log.bug(\"Query to run: \", ps);");
1294 ol(" " + beanClassName + " bean = decodeFromRS(rs);");
1295 ol(" rs.close();");
1296 // ol(" ps.clearParameters();");
1297 ol(" return bean;");
1298 ol(" }");
1299 } //~write getbykey
1300
1301 final void mgrWriteMethodGetWhere() throws SQLException
1302 {
1303 ol();
1304 ol("/** ");
1305 ol("Returns the rows returned by querying the table with the specified");
1306 ol("<tt>WHERE</tt> clause or <i>an empty list</i> if no rows were found.");
1307 ol("(note: the string <tt>\"WHERE\"</tt> does <b>not</b> have to be");
1308 ol("specified in the clause. It is added automatically by this method).");
1309 ol("<p>Queries can use database functions such as: <code>lower()</code>,");
1310 ol("<code>upper()</code>, <code>LIKE</code> etc. For example:");
1311 o ("<pre><blockquote>"); o(table.getName());
1312 ol("Mgr.getWhere(\"lower(col_a) = 'foo'\")");
1313 ol("//compares the lower case value of col_a with the string 'foo'");
1314 ol("</blockquote></pre>");
1315 ol("<p><b>The \"where\" clause is sent as-is to the database</b>. SQL");
1316 ol("injection attacks are possible if it is created as-is from a <b><u>untrusted</u></b> source.");
1317 ol();
1318 ol("@throws IllegalArgumentException if the specified <tt>where</tt> parameter is null");
1319 ol("*/");
1320 ol("public static List getWhere(final Connection con, final String where) throws SQLException");
1321 ol(" {");
1322 ol(" __getwhere_called++;");
1323 ol(" Argcheck.notnull(where, \"the where parameter was null (and should not be null)\");");
1324 ol(" final String where_stmt = \"SELECT " + colsCommaDelimString + " from " + table.getName() + " WHERE \" + where ;");
1325 ol(" Statement stmt = QueryUtil.getRewindableStmt(con);");
1326 ol(" log.bug(\"Query to run: \", stmt, \" \", where_stmt);");
1327 ol(" final List list = new ArrayList();");
1328 ol(" final ResultSet rs = stmt.executeQuery(where_stmt);");
1329 ol(" while (true) {");
1330 ol(" " + beanClassName + " bean = decodeFromRS(rs);");
1331 ol(" if (bean == null) { break; } ");
1332 ol(" list.add(bean);");
1333 ol(" }");
1334 ol(" stmt.close();");
1335 ol(" return list;");
1336 ol(" }");
1337 } //~write getwhere
1338
1339 final void mgrWriteMethodGetUsing() throws SQLException
1340 {
1341 ol();
1342 ol("/** ");
1343 ol("Returns the rows returned by querying the table with the value of the");
1344 o ("specified <tt>");
1345 o (beanClassName);
1346 ol("</tt> object or <i>an empty list</i> if no rows were found. As many");
1347 ol("fields in <tt>alltypes</tt> can be set as needed and the values of");
1348 ol("all set fields (including fields explicitly set to <tt>null</tt>)");
1349 ol("are then used to perform the query.");
1350 ol("<p>");
1351 ol("This method is often convenient/safer than the {@link #getWhere");
1352 ol("getWhere} method (because the <tt>getWhere</tt> method takes an");
1353 ol("arbitrary query string which has to be properly escaped by the");
1354 ol("user).");
1355 ol("<p>Essentially, this method is a more convenient way to use a");
1356 ol("PreparedStatement. Internally, a prepared statement is created and");
1357 ol("it's parameters are set to fields that are set in this object).");
1358 ol("Using PreparedStatements directly is also perfectly fine. For");
1359 ol("example, the following are equivalent. ");
1360 ol("<p> Using a PreparedStatement:");
1361 ol("<blockquote><pre>");
1362 ol("String foo = \"select * from table_foo where x = ? and y = ?\";");
1363 ol("PreparedStatement ps = con.prepareStatement(foo);");
1364 ol("ps.setString(1, \"somevalue\");");
1365 ol("ps.setString(2, \"othervalue\");");
1366 ol("ResultSet rs = ps.executeUpdate();");
1367 ol("while (rs.next()) {");
1368 ol(" table_foo bean = table_fooMgr.getFromRS(rs);");
1369 ol(" }");
1370 ol("</pre> </blockquote>");
1371 ol("");
1372 ol("Using this method:");
1373 ol("<blockquote><pre>");
1374 ol("table_foo <font color=blue>proto</font> = new table_foo();");
1375 ol("proto.set_x(\"somevalue\"); //compile time safety");
1376 ol("proto.set_y(\"othervalue\"); //compile time safety");
1377 ol("List beans = table_fooMgr.<font color=blue>getUsing(proto)</font>;");
1378 ol("</pre> </blockquote>");
1379 ol("<p>This method also takes an <tt>clause</tt> parameter which");
1380 ol("is sent as is to the database. For example, a clause can be:");
1381 ol("<blockquote><pre>");
1382 ol("order by some_column_name");
1383 ol("</pre> </blockquote>");
1384 ol("This clause is optional. Specify <tt>null</tt> to not use it at all.");
1385 ol("<p>Note: For a <i>very</i> large number of rows, it may be more");
1386 ol("efficient to use a prepared statement directly (as opposed to using");
1387 ol("this method). In most cases, this is not something to worry about,");
1388 ol("but your mileage may vary...");
1389 ol("*/");
1390 o("public static List getUsing(final Connection con, final ");
1391 o(beanClassName);
1392 ol(" bean, final String clause) throws ValidateException, SQLException");
1393 ol(" {");
1394 ol(" __getusing_called++;");
1395 ol(" Argcheck.notnull(bean, \"the bean parameter was null (and should not be null)\");");
1396 ol(" if (! bean.isModified()) { ");
1397 ol(" throw new ValidateException(\"bean=\" + bean + \" not modified, ignoring query\");");
1398 ol(" }");
1399 ol();
1400 ol(" int count = 0;");
1401 ol(" final StringBuilder buf = new StringBuilder(512);");
1402 ol(" buf.append(\"SELECT " + colsCommaDelimString + " from " +
1403 table.getName() + " WHERE \");");
1404
1405 List cols = table.getColumnList();
1406 //we allow any col to be set, including pk or partial pk
1407 List relevant_cols = cols;
1408
1409 for (int n = 0; n < cols.size(); n++)
1410 {
1411 ColumnData cd = (ColumnData) cols.get(n);
1412
1413 o (" if (bean.");
1414 o (wrangler.getIsModifiedName(cd));
1415 ol("()) { ");
1416 if (! cd.usesPrimitiveJavaType()) {
1417 o (" if (bean."); o(wrangler.getGetName(cd)); ol("() == null) {");
1418 o (" buf.append(\""); o(cd.getName()); ol(" is NULL and \");");
1419 ol(" }");
1420 ol(" else{");
1421 o (" buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");");
1422 ol(" count++;");
1423 ol(" }");
1424 }
1425 else{
1426 o (" buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");");
1427 ol(" count++;");
1428 }
1429 ol(" }");
1430 }
1431
1432 ol();
1433 //get rid of last "and "
1434 ol(" buf.setLength(buf.length() - 4);");
1435 ol();
1436 ol(" if (clause != null) {");
1437 ol(" buf.append(\" \");");
1438 ol(" buf.append(clause);");
1439 ol(" }");
1440 ol();
1441 ol(" final String getUsingPKStmt = buf.toString();");
1442 ol(" PreparedStatement ps = prepareStatement(con, getUsingPKStmt);");
1443
1444 utilFillPStmtFromList_IfModified_Object(relevant_cols, "\t");
1445
1446 ol(" log.bug(\"Query to run: \", ps);");
1447 ol(" final List list = new ArrayList();");
1448 ol(" final ResultSet rs = ps.executeQuery();");
1449 ol(" while (true) {");
1450 ol(" " + beanClassName + " row = decodeFromRS(rs);");
1451 ol(" if (row == null) { break; } ");
1452 ol(" list.add(row);");
1453 ol(" }");
1454 ol(" rs.close();");
1455 // ol(" ps.clearParameters();");
1456 ol(" return list;");
1457 ol(" }");
1458 } //~write getusing
1459
1460 final void mgrWriteMethodGetUsingNoClause() throws SQLException
1461 {
1462 ol();
1463 ol("/** ");
1464 o ("Convenience method that invokes {@link getUsing(Connection, ");
1465 o (beanClassName);
1466 o (", String) getUsing} with an empty <i><tt>clause</tt></i> parameter.");
1467 ol("*/");
1468 o("public static List getUsing(final Connection con, final ");
1469 o(beanClassName);
1470 ol(" bean) throws ValidateException, SQLException");
1471 ol(" {");
1472 ol(" return getUsing(con, bean, null);");
1473 ol(" }");
1474 } //~write getUsingnoclause
1475
1476 //the actual method written is called getUsing(..) not getUsingPS(..)
1477 //i.e., getUsing is overloaded
1478 final void mgrWriteMethodGetUsingPS() throws SQLException
1479 {
1480 ol();
1481 ol("/**");
1482 ol("This is a <i>convenience</i> method that runs the specified ");
1483 ol("prepared statement to perform a arbitrary query. For example: ");
1484 ol("<blockquote>");
1485 ol("<pre>");
1486 ol("PreparedStatement <font color=blue>ps</font> = some_tableMgr.prepare(con, ");
1487 ol(" \"select * from some_table where some_column = ?\"");
1488 ol("ps.setString(1, \"foo\");");
1489 ol("List list = fooMgr.<font color=blue>getUsing</font>(con, <font color=blue>ps</font>);");
1490 ol("for (int n = 0; n < list.size(); n++) {");
1491 ol(" sometable t = (sometable) list.get(n);");
1492 ol(" //do something");
1493 ol(" }");
1494 ol("</pre>");
1495 ol("</blockquote>");
1496 ol("The effect of the above is <u>equivalent</u> to the following (larger) block ");
1497 ol("of code:");
1498 ol("<blockquote>");
1499 ol("<pre>");
1500 ol("PreparedStatement <font color=blue>ps</font> = con.prepareStatement(");
1501 ol(" \"select * from sometable where some_column = ?\"");
1502 ol(" );");
1503 ol("ps.setString(1, \"foo\");");
1504 ol("ResultSet rs = <font color=blue>ps.executeQuery()</font>;");
1505 ol("List list = new ArrayList();");
1506 ol("while (rs.next()) {");
1507 ol(" list.add(sometableMgr.<font color=blue>getFromRS(rs)</font>);");
1508 ol(" }");
1509 ol("");
1510 ol("for (int n = 0; n < list.size(); n++) {");
1511 ol(" sometable t = (sometable) list.get(n);");
1512 ol(" //do something");
1513 ol(" }");
1514 ol("</pre>");
1515 ol("</blockquote>");
1516 ol("");
1517 ol("Note: Just as with other get<i>XXX</i> methods, for large amounts of");
1518 ol("rows (say many thousands), it may be more efficient use and iterate");
1519 ol("through a JDBC result set directly.");
1520 ol("*/");
1521 o("public static List getUsing(final Connection con, ");
1522 ol(" final PreparedStatement ps) throws ValidateException, SQLException");
1523 ol(" {");
1524 ol(" __getusing_ps_called++;");
1525 ol(" log.bug(\"Query to run: \", ps);");
1526 ol(" final List list = new ArrayList();");
1527 ol(" final ResultSet rs = ps.executeQuery();");
1528 ol(" while (true) {");
1529 ol(" " + beanClassName + " row = decodeFromRS(rs);");
1530 ol(" if (row == null) { break; } ");
1531 ol(" list.add(row);");
1532 ol(" }");
1533 ol(" rs.close();");
1534 // ol(" ps.clearParameters();");
1535 ol(" return list;");
1536 ol(" }");
1537 } //~write getusingps
1538
1539 final void mgrWriteMethodGetColumnNames() throws SQLException
1540 {
1541 ol();
1542 ol("/** ");
1543 o("Returns a <i>comma delimited list</i> of <i>all</i> columns in <tt>");
1544 o(table.getName());
1545 ol("</tt>. These column names are fully qualified, i.e., they contain ");
1546 ol("table name as a prefix to the column name. For example:");
1547 ol("<blockquote><pre>");
1548 ol("<tt>tablename.column1 AS tablename_column1, tablename.column2 AS tablename_column2 ...</tt>");
1549 ol("</pre></blockquote>");
1550 ol("<p>This list is suitable for placing in the column(s) clause of a select query, such as: ");
1551 ol("<blockquote>");
1552 ol("<tt>Single table: select <i><font color=blue>[column_list_A]</font></i> from table_A</tt><br>");
1553 ol("<tt>Join: select <i><font color=blue>[column_list_A], [column_list_B]</font></i> from table_A, table_B</tt>");
1554 ol("</blockquote>");
1555 ol("The ResultSet returned by the query can be used directly or can be passed");
1556 o ("to the {@link #getFromRS getFromRS} method to convert it into a list of <code>");
1557 ol(table.getName());
1558 ol("</code> objects. If the query is a join across multiple tables,");
1559 ol("then the {@link #getFromRS getFromRS} method for each table manager");
1560 ol("can be called on the same ResultSet to retrieve the row object for");
1561 ol("that table.");
1562 ol("Note: the returned list of names has a trailing space, which is good when");
1563 ol("the rest of the query is appended to this list.");
1564 ol("*/");
1565 o("public static String");
1566 ol(" columns() throws SQLException");
1567 ol(" {");
1568 o (" return \"");
1569 //has a trailing space, which is good, we want to return this
1570 //with a trailing space
1571 o ( table.getFullyQualifiedColumnString());
1572 ol("\";");
1573 ol(" }");
1574 } //~write columns
1575
1576
1577 final void mgrWriteMethodGetColumnNames2() throws SQLException
1578 {
1579 ol();
1580 ol("/** ");
1581 o("Returns a <i>comma delimited list</i> of <i>all</i> columns in <tt>");
1582 o(table.getName());
1583 ol("</tt>. These column names are prefix with the specified prefix, which corresponds to the");
1584 ol("table abbreviation used in the \"AS\" clause. For example:");
1585 ol("<blockquote><pre>");
1586 ol("<tt>xyz.column1 AS xyz_column1, xyz.column2 AS xyz_column2 ...</tt>");
1587 ol("</pre></blockquote>");
1588 ol("<p>This list is suitable for placing in the column(s) clause of a select query, such as: ");
1589 ol("<blockquote>");
1590 ol("<p><b>Note:</b> the \".\" will automatically be appended between the prefix and column name");
1591 ol("so the prefix should not end with a \".\" or \"_\", etc<p>");
1592 ol("<tt>Single table: select <i><font color=blue>[column_list_A]</font></i> from table_A <b>AS</b> xyz</tt><br>");
1593 ol("<tt>Join: select <i><font color=blue>[column_list_A], [column_list_B]</font></i> from table_A <b>AS</b> xyz, table_B <b>AS</b> zzz</tt>");
1594 ol("</blockquote>");
1595 ol("The ResultSet returned by the query can be used directly or can be passed");
1596 o ("to the {@link #getFromRS getFromRS(String)} method to convert it into a list of <code>");
1597 ol(table.getName());
1598 ol("</code> objects. If the query is a join across multiple tables,");
1599 ol("then the {@link #getFromRS getFromRS(String)} method for each table manager");
1600 ol("can be called on the same ResultSet to retrieve the row object for");
1601 ol("that table.");
1602 ol("Note: the returned list of names has a trailing space, which is good when");
1603 ol("the rest of the query is appended to this list.");
1604 ol("*/");
1605 o("public static String");
1606 ol(" columns(String prefix) throws SQLException");
1607 ol(" {");
1608 //has a trailing space, which is good, we want to return this
1609 //with a trailing space
1610 ol( table.getPrefixQualifiedColumnString());
1611 ol(" }");
1612 } //~write columns
1613
1614
1615
1616 final void mgrWriteMethodGetFromRS() throws SQLException
1617 {
1618 ol();
1619 ol("/** ");
1620 o("Creates and returns a new <tt>"); o(table.getName());
1621 ol("</tt> object that represents a row from the specified ResultSet. The ResultSet is");
1622 ol("typically obtained via a handwritten query/PreparedStatement. The resulting ");
1623 ol("ResultSet should contain all of the");
1624 ol("column names of table, and this will only happen if the handwritten query had");
1625 ol("a select statement that specified all fields or used a <tt>select <b>*</b>..</tt>");
1626 ol("clause.");
1627 ol("<p>");
1628 ol("In the select clause, we could also be selecting multiple tables. To disambiguate");
1629 ol("between the same field names that may exist in multiple tables, this method ");
1630 ol("also requires that the query should use <font color=blue>fully qualified</font>");
1631 ol("(prefixed with the table name) column names, such as:");
1632 ol("<blockquote><pre>");
1633 ol("<font color=blue>tablename</font>_column1");
1634 ol("<font color=blue>tablename</font>_column2");
1635 ol("...etc.");
1636 ol("</pre></blockquote>");
1637 ol("<p>");
1638 ol("For example:");
1639 ol("<blockquote>");
1640 ol("<code>select <font color=blue>foo</font>.a <b>AS</b> <font color=blue>foo</font>_a, <font color=red>bar</font>.a <b>AS</b> <font color=red>bar</font>_a from <font color=blue>foo</font>, <font color=red>bar</font> where foo.a = bar.a;</code>");
1641 ol("</blockquote>");
1642 ol("The {@link #columns} method conveniently returns a list of column names in fully qualified format ");
1643 ol("and is useful for this purpose.");
1644 ol("<p>Note: This method will read the <i>current</i> row from the specified result set");
1645 ol("and will <b>not</b> move the result set pointer to the next row after the current");
1646 ol("row has been read. The result set should be appropriately positioned [via <tt>rs.next()</tt>]");
1647 ol("<i>before</i> calling this method.");
1648 ol();
1649 ol("@return a new {@link ");
1650 o(beanClassName);
1651 o("} object populated with the contents of the next");
1652 ol(" row from the result set or <tt> null </tt> if");
1653 ol(" the ResultSet was empty.");
1654 ol("*/");
1655 o("public static ");
1656 o(beanClassName);
1657 ol(" getFromRS(final ResultSet rs) throws SQLException");
1658 ol(" {");
1659 ol(" __getfromrs_called++;");
1660 //decode from RS
1661 ol(" Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1662 ol(" boolean hasrow = ! rs.isAfterLast();");
1663 ol(" if (! hasrow) { ");
1664 ol(" return null; ");
1665 ol(" } ");
1666 o (" ");o(beanClassName);o(" bean = new ");o(beanClassName);ol("();");
1667 ol();
1668 List col_list = table.getColumnList();
1669 for (int n = 0; n < col_list.size(); n++)
1670 {
1671 //THIS METHOD USES COLUMN NAMES NOT COLUMN POSITIONS. THIS IS
1672 //-THE- MAIN DIFF BETWEEN THIS AND THE INTERNAL DECODE_FROM_RS METHOD
1673 // If a column does not exist in the result set and we try to get
1674 // that column by name, a sql exception (per the jdbc spec,
1675 // although the spec is not totally clear on this) is allowed
1676 // to be thrown (for example, the postgres drives does this)...
1677 // so we require all columns to be present while decoding a row
1678 // typically a select * must be issued. if the same column exists with
1679 // the same name in more than 2 tables, we have a problem. We therefore
1680 // require column names to be qualified with tablenames, such as:
1681 // select foo.a as foo_a, bar.a as bar_a from foo, bar;
1682 ColumnData cd = (ColumnData) col_list.get(n);
1683 String setmethod_name = wrangler.getSetName(cd);
1684 o("\tbean.");
1685 o(setmethod_name);
1686 o("( ");
1687
1688 if (! cd.useBooleanObject()) {
1689 o("rs.");
1690 }
1691 o(cd.getResultSetMethod(table.getName() + "_"));
1692
1693 ol(" );");
1694
1695 if (cd.isPK()) {
1696 o("\tbean.__orig_");
1697 o(cd.getName());
1698 o(" = ");
1699
1700 if (! cd.useBooleanObject()){
1701 o("rs.");
1702 }
1703 o(cd.getResultSetMethod(table.getName() + "_"));
1704
1705 ol("; /* save original PK */");
1706 }
1707
1708 ol("\tif (rs.wasNull()) {");
1709 o("\t\tbean.__isNullInDB_");
1710 o(cd.getName());
1711 ol(" = true;");
1712 ol("\t\t}");
1713 }
1714 ol();
1715 ol(" /* set to true when instantiated new, false when we populate the bean from a resultset */");
1716 ol(" bean.setNew(false);") ;
1717 ol(" /* it's not modified, just loaded from the database */");
1718 ol(" bean.resetModified();");
1719 ol(" return bean;");
1720 ol(" }");
1721 } //~write getfromrs
1722
1723
1724
1725 final void mgrWriteMethodGetFromRS2() throws SQLException
1726 {
1727 ol();
1728 ol("/** ");
1729 o("Creates and returns a new <tt>"); o(table.getName());
1730 ol("</tt> object that represents a row from the specified ResultSet. The ResultSet is");
1731 ol("typically obtained via a handwritten query/PreparedStatement. The resulting ");
1732 ol("ResultSet should contain all of the");
1733 ol("column names of table, prefixed with the specified <i>prefix</i> argument.");
1734 ol("a select statement that specified all fields or used a <tt>select <b>*</b>..</tt>");
1735 ol("clause.");
1736 ol("<p>");
1737 ol("In the select clause, we could also be selecting multiple tables. To disambiguate");
1738 ol("between the same field names that may exist in multiple tables, this method ");
1739 ol("also requires that the query should use a <font color=blue>prefix</font>");
1740 ol("(some arbitrary prefix) before column names, such as:");
1741 ol("<blockquote><pre>");
1742 ol("<font color=blue>foo</font>_column1");
1743 ol("<font color=blue>foo</font>_column2");
1744 ol("...etc.");
1745 ol("</pre></blockquote>");
1746 ol("This prefix will typically be the same as the table abbreviation chosen via the <b>AS</b> clause");
1747 ol("If the AS clause is not used, then it is simpler to use the {@link getFromRS(ResultSet)} method instead");
1748 ol("<p><b>Note:</b> the \".\" will automatically be appended between the prefix and column name");
1749 ol("so the prefix should not end with a \".\" or \"_\", etc<p>");
1750 ol("<p>");
1751 ol("For example:");
1752 ol("<blockquote>");
1753 ol("<code>select <font color=blue>XXX</font>.a <b>AS</b> <font color=blue>XXX</font>_a, <font color=red>YYY</font>.a <b>AS</b> <font color=red>YYY</font>_a from <font color=blue>foo as XXX</font>, <font color=red>bar as YYY</font> where foo.a = bar.a;</code>");
1754 ol("</blockquote>");
1755 ol("The {@link #columns} method conveniently returns a list of column names in fully qualified format ");
1756 ol("and is useful for this purpose.");
1757 ol("<p>Note: This method will read the <i>current</i> row from the specified result set");
1758 ol("and will <b>not</b> move the result set pointer to the next row after the current");
1759 ol("row has been read. The result set should be appropriately positioned [via <tt>rs.next()</tt>]");
1760 ol("<i>before</i> calling this method.");
1761 ol();
1762 ol("@return a new {@link ");
1763 o(beanClassName);
1764 o("} object populated with the contents of the next");
1765 ol(" row from the result set or <tt> null </tt> if");
1766 ol(" the ResultSet was empty.");
1767 ol("*/");
1768 o("public static ");
1769 o(beanClassName);
1770 ol(" getFromRS(final ResultSet rs, String prefix) throws SQLException");
1771 ol(" {");
1772 ol(" __getfromrs_called++;");
1773 //decode from RS
1774 ol(" Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1775 ol(" boolean hasrow = ! rs.isAfterLast();");
1776 ol(" if (! hasrow) { ");
1777 ol(" return null; ");
1778 ol(" } ");
1779 o (" ");o(beanClassName);o(" bean = new ");o(beanClassName);ol("();");
1780 ol();
1781 List col_list = table.getColumnList();
1782 for (int n = 0; n < col_list.size(); n++)
1783 {
1784 //THIS METHOD USES COLUMN NAMES NOT COLUMN POSITIONS. THIS IS
1785 //-THE- MAIN DIFF BETWEEN THIS AND THE INTERNAL DECODE_FROM_RS METHOD
1786 // If a column does not exist in the result set and we try to get
1787 // that column by name, a sql exception (per the jdbc spec,
1788 // although the spec is not totally clear on this) is allowed
1789 // to be thrown (for example, the postgres drives does this)...
1790 // so we require all columns to be present while decoding a row
1791 // typically a select * must be issued. if the same column exists with
1792 // the same name in more than 2 tables, we have a problem. We therefore
1793 // require column names to be qualified with tablenames, such as:
1794 // select foo.a as foo_a, bar.a as bar_a from foo, bar;
1795 ColumnData cd = (ColumnData) col_list.get(n);
1796 String setmethod_name = wrangler.getSetName(cd);
1797 o("\tbean.");
1798 o(setmethod_name);
1799 o("( ");
1800
1801 if (! cd.useBooleanObject()) {
1802 o("rs.");
1803 }
1804 o(cd.getRuntimeResultSetMethod());
1805
1806 ol(" );");
1807
1808 if (cd.isPK()) {
1809 o("\tbean.__orig_");
1810 o(cd.getName());
1811 o(" = ");
1812
1813 if (! cd.useBooleanObject()) {
1814 o("rs.");
1815 }
1816 o(cd.getRuntimeResultSetMethod());
1817
1818 ol("; /* save original PK */");
1819 }
1820
1821 ol("\tif (rs.wasNull()) {");
1822 o("\t\tbean.__isNullInDB_");
1823 o(cd.getName());
1824 ol(" = true;");
1825 ol("\t\t}");
1826 }
1827 ol();
1828 ol(" /* set to true when instantiated new, false when we populate the bean from a resultset */");
1829 ol(" bean.setNew(false);") ;
1830 ol(" /* it's not modified, just loaded from the database */");
1831 ol(" bean.resetModified();");
1832 ol(" return bean;");
1833 ol(" }");
1834 } //~write getfromrs2
1835
1836
1837 final void mgrWriteMethodGetFromRS1Table() throws SQLException
1838 {
1839 ol();
1840 ol("/** ");
1841 o("Creates and returns a new <tt>"); o(table.getName());
1842 ol("</tt> object that represents a row from the specified ResultSet. For this method");
1843 ol("to work properly, the specified ResultSet should contain <b>all</b> (typically via <b>select *");
1844 o ("</b>) of the column names of table.");
1845 o ("<tt>");
1846 o (table.getName());
1847 ol("</tt>.");
1848 ol("<p>");
1849 ol("This method does not prepend the table name to columns when reading data from");
1850 ol("the result set. It is useful when writing a JDBC query by hand that uses a single table");
1851 ol("(no joins) and then converting the returned result set into objects of this");
1852 ol("class. For example:");
1853 ol("<p>");
1854 ol("<code>select a, b, c, c*2 from foo where a = 1;</code>");
1855 ol("<p>");
1856 ol("This method will expect columns to be called <code><i>a, b, c</i></code> (no column aliases) in the returned");
1857 ol("result set. In this example, there is only one table <code>foo</code> so qualifying the column");
1858 ol("names, like <code>foo.a as foo_a</code> is not necessary). Also note, for this method to work properly, the ");
1859 ol("column list<blockquote><code>select <i>a, b, c </i></code> ...</blockquote> should be complete, i.e., contain <i>at least</i> all the columns");
1860 ol("of this table (<i>additional</i> expressions like c*2 are fine). It is slightly less efficient to retrieve all columns");
1861 ol("especially for large tables but to construct a row into an object, we need all the fields. To be safe, use <blockquote><tt>select * ....</tt></blockquote>");
1862 ol("<p>");
1863 ol("Of course, if one needs a subset of columns, one can use the ResultSet directly and forego trying to");
1864 ol("convert a ResultSet row into an corresponding object");
1865 ol("<p> ");
1866 ol("See {@link getFromRS(ResultSet)} which is more useful when writing a JDBC");
1867 ol("query that uses multiple table joins.");
1868 ol("<p>Note: This method will read the <i>current</i> row from the specified result set");
1869 ol("and will <b>not</b> move the result set pointer to the next row after the current");
1870 ol("row has been read. The result set should be appropriately positioned [via <tt>rs.next()</tt>]");
1871 ol("<i>before</i> calling this method.");
1872 ol();
1873 o ("@return a new {@link ");
1874 o (beanClassName);
1875 ol("} object populated with the contents of the next");
1876 ol(" row from the result set or <tt> null </tt> if");
1877 ol(" the ResultSet was empty.");
1878 ol("*/");
1879 o ("public static ");
1880 o (beanClassName);
1881 ol(" getFromRS1Table(final ResultSet rs) throws SQLException");
1882 ol(" {");
1883 ol(" __getfromrs_called++;");
1884 //decode from RS
1885 ol(" Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1886 ol(" boolean hasrow = ! rs.isAfterLast();");
1887 ol(" if (! hasrow) { ");
1888 ol(" return null; ");
1889 ol(" } ");
1890 o (" ");o(beanClassName);o(" bean = new ");o(beanClassName);ol("();");
1891 ol();
1892 List col_list = table.getColumnList();
1893 for (int n = 0; n < col_list.size(); n++)
1894 {
1895 //THIS METHOD USES COLUMN NAMES NOT COLUMN POSITIONS. THIS IS
1896 //-THE- MAIN DIFF BETWEEN THIS AND THE INTERNAL DECODE_FROM_RS METHOD
1897 // If a column does not exist in the result set and we try to get
1898 // that column by name, a sql exception (per the jdbc spec,
1899 // although the spec is not totally clear on this) is allowed
1900 // to be thrown (for example, the postgres driver does this)...
1901 // so we require all columns to be present while decoding a row
1902 // typically a select * must be issued. if the same column exists with
1903 // the same name in more than 2 tables, we have a problem. We therefore
1904 // require column names to be qualified with tablenames, such as:
1905 // select foo.a as foo_a, bar.a as bar_a from foo, bar;
1906 ColumnData cd = (ColumnData) col_list.get(n);
1907 String setmethod_name = wrangler.getSetName(cd);
1908 o("\tbean.");
1909 o(setmethod_name);
1910 o("( ");
1911
1912 if (! cd.useBooleanObject()){
1913 o("rs.");
1914 }
1915 o(cd.getResultSetMethod());
1916
1917 ol(" );");
1918
1919 if (cd.isPK()) {
1920 o("\tbean.__orig_");
1921 o(cd.getName());
1922 o(" = ");
1923
1924 if (! cd.useBooleanObject()) {
1925 o("rs.");
1926 }
1927 o(cd.getResultSetMethod());
1928
1929 ol("; /* save original PK */");
1930 }
1931
1932 ol("\tif (rs.wasNull()) {");
1933 o("\t\tbean.__isNullInDB_");
1934 o(cd.getName());
1935 ol(" = true;");
1936 ol("\t\t}");
1937 }
1938 ol();
1939 ol(" /* set to true when instantiated but this should be false");
1940 ol(" whenever we populate the bean from a result set */");
1941 ol(" bean.setNew(false);") ;
1942 ol(" //it's not modified, just loaded from the database");
1943 ol(" bean.resetModified();");
1944 ol(" return bean;");
1945 ol(" }");
1946 } //~write getfromrs
1947
1948 final void mgrWriteMethodDecodeFromRS() throws SQLException
1949 {
1950 //internal method. decode from RS. moves the result set via rs.next()
1951 ol();
1952 o("private static ");
1953 o(beanClassName);
1954 ol(" decodeFromRS(final ResultSet rs) throws SQLException");
1955 ol(" {");
1956 ol(" Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1957 ol(" boolean hasrow = rs.next();");
1958 ol(" if (! hasrow) { ");
1959 ol(" return null; ");
1960 ol(" } ");
1961 ol(" " + beanClassName + " bean = new " + beanClassName + "();");
1962 ol();
1963 List col_list = table.getColumnList();
1964 for (int n = 0; n < col_list.size(); n++)
1965 {
1966 ColumnData cd = (ColumnData) col_list.get(n);
1967 String setmethod_name = wrangler.getSetName(cd);
1968 o("\tbean.");
1969 o(setmethod_name);
1970 o("( ");
1971
1972 if (! cd.useBooleanObject()){
1973 o("rs.");
1974 }
1975 o(cd.getResultSetMethod());
1976
1977 ol(" );");
1978
1979 if (cd.isPK()) {
1980 o("\tbean.__orig_");
1981 o(cd.getName());
1982 o(" = ");
1983
1984 if (! cd.useBooleanObject()){
1985 o("rs.");
1986 }
1987 o(cd.getResultSetMethod());
1988
1989 ol("; /* save original PK */");
1990 }
1991
1992 ol("\tif (rs.wasNull()) {");
1993 o("\t\tbean.__isNullInDB_");
1994 o(cd.getName());
1995 ol(" = true;");
1996 ol("\t\t}");
1997 ol();
1998 }
1999
2000 ol();
2001 ol(" /* set to true when newly instantiated but this should be false");
2002 ol(" whenever we populate the bean from a result set */");
2003 ol(" bean.setNew(false);") ;
2004 ol(" //it's not modified, just loaded from the database");
2005 ol(" bean.resetModified();");
2006 ol(" return bean;");
2007 ol(" }");
2008 } //~write decodefromrs
2009
2010 final void mgrWriteMethodSave() throws SQLException
2011 {
2012 ColumnData cd = null;
2013 List cols = table.getColumnList();
2014 //we don't insert or update auto-increment columns
2015 //filter those out and put the rest in filtered_cols
2016 List filtered_cols = new ArrayList();
2017 List autoinc_cols = new ArrayList();
2018
2019 for (int n = 0; n < cols.size(); n++)
2020 {
2021 cd = (ColumnData) cols.get(n);
2022 if (cd.isAutoIncrement()) {
2023 autoinc_cols.add(cd);
2024 if (! modifiableAutoIncrementColumns) {
2025 continue; //don't add it to the filtered list
2026 }
2027 }
2028 filtered_cols.add(cd);
2029 }
2030
2031 final int filtered_count = filtered_cols.size();
2032 ol();
2033 ol("/**");
2034 ol("Saves the specified object into the database. If the specified");
2035 ol("object was newly created, then it is <span style=\"font-variant:");
2036 ol("small-caps\">insert</span>'ed into the database, else it is <span ");
2037 ol("style=\"font-variant: small-caps\">update</span>'ed. (this can be");
2038 ol("overriden by the {@link #update update} method). If the object is");
2039 ol("inserted as a new row, then after insertion, the values of");
2040 ol("serial/auto-incremented columns will be automatically available via the");
2041 ol("appropriate getXXX() methods on that object.");
2042 ol("<p>");
2043 ol("<b>NOTE 1:</b> When saving an object, only modified fields are");
2044 ol("saved. Do not rely on default field values (such as null) of newly");
2045 ol("created objects; instead explicitly set the value (including to null");
2046 ol("if needed) of any field that should be saved to the database.");
2047 ol("<p>");
2048 ol("<b>NOTE 2:</b> Once an object is successfully saved, it is discarded");
2049 ol("and cannot be saved again and any attempt to save it again will");
2050 ol("result in a runtime exception. Objects that need to be modified");
2051 ol("again must be re-instantiated or re-populated from the database");
2052 ol("before they can be saved again. (the serial/auto-increment data will still be");
2053 ol("available, discarding only affects the ability to save the object");
2054 ol("again).");
2055 ol("<b>Note 3:</b> <font color='red'>For various reasons/flexiblity, default database values");
2056 ol("for columns <i>other</i> than serial/non auto-increment columns are <b>not</b> available");
2057 ol("in the saved object. To get these values, retrieve the saved object again. (this is what");
2058 ol("we would have to do internally anyway). This is relevant, for example, when a column has");
2059 ol("a default value of a now() timestamp, and we need to get that timestamp after the object");
2060 ol("has been saved</font>");
2061 ol();
2062 ol("@return the number of rows inserted or updated (typically useful ");
2063 ol(" to see if an update succeeded)");
2064 ol("@throws ValidateException on a validation error");
2065 ol("@throws SQLException on some SQL/Database error");
2066 ol("@throws IOException by the available() method if/when");
2067 ol(" setting a stream for longvar/text types");
2068 ol("*/");
2069 o("public static int save(final Connection con, final ");
2070 o(beanClassName);
2071 //we need IOException for the available() method when
2072 //setting a stream for longvar/text types in our statement
2073 ol(" bean) throws ValidateException, SQLException, IOException");
2074 ol(" {");
2075 ol(" __save_called++;");
2076 ol(" Argcheck.notnull(bean, \"the specified bean parameter was null\");");
2077 ol(" checkDiscarded(bean);");
2078 ol(" if (! bean.isModified()) { ");
2079 ol(" log.warn(\"bean=\" + bean + \" not modified, IGNORING SAVE\");");
2080 ol(" return 0;");
2081 ol(" }");
2082 ol(" PreparedStatement ps = null;");
2083 ol();
2084 //.... insert into table_foo (col_x, col_y) values (?, ?).....
2085 //.... update table_foo set (col_x=? col_y=?) where ....
2086 ol(" boolean inserting_a_row = false;");
2087 ol(" if (bean.isNew() && ! bean.__force_update) ");
2088 //----------------- INSERT ---------------------
2089 ol(" { //insert new row");
2090 ol(" validateBeforeSaveNew(bean);");
2091 ol(" int count = 0;");
2092 ol(" inserting_a_row = true;");
2093 ol(" final StringBuilder buf = new StringBuilder(512);");
2094 o (" buf.append(\"INSERT into ");
2095 o(table.getName());
2096 ol(" (\");");
2097
2098 for (int n = 0; n < filtered_count; n++)
2099 {
2100 cd = (ColumnData) filtered_cols.get(n);
2101 o (" if (bean.");
2102 o(wrangler.getIsModifiedName(cd));
2103 ol("()) { ");
2104 o(" buf.append(\"");
2105 o(cd.getName());
2106 ol("\").append(\", \");");
2107 ol(" count++;");
2108 ol(" }");
2109 }
2110 ol();
2111
2112 ol(" if (count == 0) {");
2113 ol(" throw new ValidateException(\"Cannot save this bean because no column has been modified. Use JDBC directly as needed.\\n\");");
2114 ol(" }");
2115
2116 //get rid of last ", " [ we need to do it this way
2117 //since we don't know when the last ',' will come since
2118 //any number of columns could have been modified (or not)
2119 ol(" buf.setLength(buf.length() - 2);");
2120 ol(" buf.append(\") values (\");");
2121 ol(" for (int n = 0; n < count; n++) { ");
2122 ol(" buf.append(\"?\");");
2123 ol(" if ((n+1) < count)");
2124 ol(" buf.append(\", \");");
2125 ol(" }");
2126 ol(" buf.append(\")\");");
2127 ol();
2128 ol(" final String insertByPKStmt = buf.toString();");
2129 ol(" ps = prepareStatement(con, insertByPKStmt);");
2130
2131 ol(" /* Insert any changed values into our prepared statement */");
2132 utilFillPStmtFromList_IfModified(filtered_cols, "\t\t");
2133 ol(" }");
2134
2135 //-------------------- UPDATE -----------------------
2136 ol(" else //update existing row ");
2137 ol(" {");
2138 if (pklist.size() == 0) {
2139 //we write pknum to shut up compiler about unreachable statements
2140 ol(" int pknum = 0;");
2141 ol(" if (pknum == 0) {");
2142 ol(" throw new ValidateException(");
2143 ol(" \"Cannot update this bean because it has no primary keys.\"");
2144 ol(" + \"Use JDBC directly to update values to this table.\");");
2145 ol(" }");
2146 }
2147 ol(" validateBeforeSaveUpdate(bean);");
2148 ol(" int count = 0;");
2149 ol(" final StringBuilder buf = new StringBuilder(512);");
2150 ol(" buf.append(\"UPDATE \");");
2151 o (" buf.append(\"");
2152 o(table.getName());
2153 ol("\");");
2154 ol(" buf.append(\" SET \");");
2155 for (int n = 0; n < filtered_count; n++)
2156 {
2157 cd = (ColumnData) filtered_cols.get(n);
2158 o(" if (bean.");
2159 o(wrangler.getIsModifiedName(cd));
2160 ol("()) { ");
2161 o(" buf.append(\"");
2162 o(cd.getName());
2163 o("=?, ");
2164 ol("\");");
2165 ol(" count++;");
2166 ol(" }");
2167 }
2168 ol();
2169
2170 ol(" if (count == 0) {");
2171 ol(" throw new ValidateException(\"Cannot save this bean because no column has been modified. Use JDBC directly as needed.\\n\");");
2172 ol(" }");
2173
2174 //get rid of last ", " [ we need to do it this way
2175 //since we don't know when the last ',' will come since
2176 //any number of columns could have been modified (or not)
2177 ol(" buf.setLength(buf.length() - 2);");
2178
2179 ol(" buf.append(\" WHERE \");");
2180 o (" buf.append(\"");
2181 o(Table.getPreparedStmtPlaceholders(pklist));
2182 ol("\");");
2183
2184 ol(" ps = con.prepareStatement(buf.toString());");
2185 //ol(" log.bug(ps);"); //we write a debug below anyway
2186 ol();
2187
2188 ol(" /* Insert any changed values into our prepared statement */");
2189 //note we don't have to worry about columns that have
2190 //not modified (including primitive columns for which
2191 //isNullInDB is true)
2192 utilFillPStmtFromList_IfModified(filtered_cols, "\t\t");
2193
2194 ol();
2195 ol(" /* Set primary keys for the WHERE part of our prepared statement */");
2196 for (int n = 0; n < pklist.size(); n++)
2197 {
2198 cd = (ColumnData) pklist.get(n);
2199 String getmethod_name = wrangler.getGetName(cd);
2200 String varname = cd.getName();
2201
2202 o("\t\t");
2203 o(cd.getJavaTypeFromSQLType());
2204 o(" ");
2205 o(varname);
2206 o(" = ");
2207
2208 //If it is a forced update, then the primary key(s) are always
2209 //provided as a param (so use that to find the row to update, not
2210 //the cached pk, since that will be null/empty in a new object anyway)
2211 //If it's not a forced update, use cached orig pk value (in case pk
2212 //itself was changed)
2213
2214 o("(bean.__force_update) ? ");
2215 o("bean.");
2216 o(getmethod_name);
2217 o("()");
2218 o(" : ");
2219 o("bean.__orig_");
2220 o(cd.getName());
2221 ol(";");
2222
2223 o("\t\t");
2224 //skip over the if_modified '?' (which are created
2225 //at runtime by the generated code using the 'pos')
2226 //integer
2227 ol(cd.getPreparedStmtSetMethod("ps.", "++pos", varname));
2228 }
2229 ol("\t\t} //~else update;");
2230 ol();
2231 ol(" log.bug(\"Query to run: \", ps);");
2232 ol(" int result = ps.executeUpdate();");
2233
2234 ol(" if (inserting_a_row) { //get auto increment info");
2235 int autoinc_size = autoinc_cols.size();
2236 if (autoinc_size > 0)
2237 {
2238 ol(" /* Retrieve values from auto-increment columns */");
2239 ol(" ResultSet rs = null; Statement stmt = null;");
2240 ol(" String query = null;");
2241 ol(" boolean found = false;");
2242 ol();
2243 for (int n = 0; n < autoinc_size; n++)
2244 {
2245
2246 //if a auto increment column is modifiable AND was modified,
2247 //then we cannot get a auto increment value for it (since
2248 //we are specifying our own value in that case, the sequence
2249 //on the database is not used)
2250
2251 o (" if (bean.");
2252 o(wrangler.getIsModifiedName(cd));
2253 ol("()) { ");
2254 o (" //column: ");
2255 ol(cd.getName());
2256 ol(" //not getting auto increment value for this column");
2257 ol(" //since not using auto increment, a value was specified manually");
2258 ol(" }");
2259 ol(" else{");
2260
2261 cd = (ColumnData) autoinc_cols.get(n);
2262 String query = dbspecific.getAutoIncrementQuery(cd);
2263 String setmethod_name = wrangler.getSetName(cd);
2264 ol(" stmt = con.createStatement();");
2265 o (" query = \""); o(query); ol("\";");
2266 ol(" rs = stmt.executeQuery(query);");
2267 ol(" found = rs.next();");
2268 ol(" if (! found) throw new SQLException(\"No last inserted id returned\");");
2269 o (" bean."); o(setmethod_name); o("( ");
2270
2271 if (! cd.useBooleanObject()){
2272 o("rs.");
2273 }
2274 o(cd.getResultSetMethod());
2275
2276 ol(");");
2277
2278 ol(" if (rs.wasNull()) {");
2279 o (" bean.__isNullInDB_");
2280 o (cd.getName());
2281 ol(" = true;");
2282 ol(" }");
2283 ol(" rs.close();");
2284 ol(" }");
2285 } //~for
2286 ol(" }");
2287 } //~if auto-inc size > 0
2288 else {
2289 ol(" //No auto inc columns in this table");
2290 ol(" }");
2291 }
2292 ol();
2293 ol(" //discard after saving/updating for safety");
2294 ol(" bean.discard();");
2295 ol(" return result;");
2296 ol(" }");
2297 }
2298
2299 final void mgrWriteMethodUpdate() throws SQLException
2300 {
2301 ol();
2302 if (pklist.size() == 0) {
2303 ol("/* update() method not implemented since this bean has no primary keys. Use JDBC directly.");
2304 return;
2305 }
2306
2307 ol("/**");
2308 ol("Uses the specified object to update existing data in the database.");
2309 ol("<p>");
2310 ol("Note, the {@link #save save} method automatically saves newly created objects");
2311 ol("as <i>inserts</i> in the database. (and <i>retrieved</i> objects, when");
2312 ol("subsequently modified, are saved as <i>updates</i>).");
2313 ol("<p>");
2314 ol("However, sometimes it is useful to create a <i>new</i> object and then");
2315 ol("use it's data to <i>update</i> an existing row in the database.");
2316 ol("This method need <b>only</b> be called to save a <u>newly</u>");
2317 ol("created object as an <u>update</u> into the database (overriding the");
2318 ol("default action of saving new objects as inserts in the database).");
2319 ol("<p>");
2320 ol("Note, also, a bean can only be updated if the corresponding table it has");
2321 ol("at least one primary key defined. To update tables with no primary keys,");
2322 ol("use JDBC directly.");
2323 ol("<p>");
2324 o("This method takes primary key(s) of {@link ");
2325 o(beanClassName);
2326 ol("} as additional arguments and sets those in the");
2327 ol("specified bean before updating the database (this way the row to update");
2328 ol("can be uniquely identified).");
2329 ol();
2330 ol("@see #save");
2331 ol();
2332 ol("@return the number of rows that were updated (typically useful ");
2333 ol(" to see if an update succeeded)");
2334 ol("@throws ValidateException on a validation error");
2335 ol("@throws SQLException on some SQL/Database error");
2336 ol("*/");
2337 o("public static int update(final Connection con, final ");
2338 o(beanClassName);
2339 o(" bean, ");
2340 o(pkFormalParamString);
2341 ol(") throws ValidateException, SQLException, IOException");
2342 ol(" {");
2343
2344 for (int n = 0; n < pklist.size(); n++)
2345 {
2346 ColumnData cd = (ColumnData) pklist.get(n);
2347 String varname = cd.getName(); /* used in pkFormalParamString too*/
2348 o("\tbean.");
2349 o( wrangler.getSetName(cd));
2350 o("(");
2351 o(varname);
2352 o(");");
2353 ol();
2354 }
2355 ol();
2356 ol(" if (bean.isNew()) { /* force update (and not insert) for new bean */");
2357 ol(" bean.__force_update = true;");
2358 ol(" }");
2359 ol(" return save(con, bean);");
2360 ol(" }");
2361 }
2362
2363 final void mgrWriteMethodDelete() throws SQLException
2364 {
2365 ol();
2366 if (pklist.size() == 0) {
2367 ol("/* delete() not implemented since this table does not have any primary keys defined. */");
2368 return;
2369 }
2370 o("static private final String deleteStmt = \"DELETE ");
2371 o(" from ");
2372 o(table.getName());
2373 o(" WHERE ");
2374 o(Table.getPreparedStmtPlaceholders(pklist));
2375 ol("\";");
2376 ol("/** ");
2377 o("Deletes this object from the database. ");
2378 ol("<p>");
2379 ol("<b>NOTE 1:</b> Only objects that were retrieved from the database can be deleted. Newly");
2380 ol("created objects cannot be deleted since they do not yet exist in the database.");
2381 ol("Use {@link #deleteByKey deleteByKey} or {@link #deleteWhere deleteWhere} instead");
2382 ol("for arbitrary deletions. <p><b>NOTE 2:</b> Once an object is successfully");
2383 ol("deleted, it is discarded and cannot be deleted again and any attempt to delete");
2384 ol("it again will result in a runtime Exception.");
2385 ol("*/");
2386 o("public static void delete(final Connection con, ");
2387 o(beanClassName);
2388 ol(" bean) throws SQLException");
2389 ol(" {");
2390 ol(" __delete_called++;");
2391 ol(" if (bean.isNew()) {");
2392 ol(" throw new DBOException(\"Cannot delete new objects using this method. Use deleteByKey() or deleteWhere() instead\");");
2393 ol(" }");
2394 ol(" checkDiscarded(bean);");
2395 ol(" final PreparedStatement ps = prepareStatement(con, deleteStmt);");
2396 //list of pk's for this table
2397 for (int n = 0; n < pklist.size(); n++)
2398 {
2399 ColumnData cd = (ColumnData) pklist.get(n);
2400 String getmethod_name = wrangler.getGetName(cd);
2401 o("\t");
2402 o(cd.getJavaTypeFromSQLType());
2403 o(" ");
2404 String varname = cd.getName();
2405 o(varname);
2406 o(" = bean.");
2407 o(getmethod_name);
2408 ol("();");
2409 o("\t");
2410 String pos = String.valueOf((n+1));
2411 ol(cd.getPreparedStmtSetMethod("ps.", pos, varname));
2412 }
2413 ol(" log.bug(\"Query to run: \", ps);");
2414 ol(" final int result = ps.executeUpdate();");
2415 // ol(" ps.clearParameters();");
2416 ol(" if (result != 1) { ");
2417 ol(" throw new DBOException(\"The number of deleted rows was: \" + result + \"; [Should have been 1 row exactly] \");");
2418 ol(" }");
2419 ol(" }");
2420 }
2421
2422 final void mgrWriteMethodDeleteByKey() throws SQLException
2423 {
2424 ol();
2425 if (pklist.size() == 0) {
2426 ol("/* deleteByKey() not implemented since this table does not have any primary keys defined */");
2427 return;
2428 }
2429
2430 o("static private final String deleteByPKStmt = \"DELETE ");
2431 o(" from ");
2432 o(table.getName());
2433 o(" WHERE ");
2434 o(Table.getPreparedStmtPlaceholders(pklist));
2435 ol("\";");
2436 ol("/** ");
2437 o("Deletes the rows with the specified primary key(s) from the database. ");
2438 ol("<p>This method uses a prepared statement and is safe from SQL injection attacks");
2439 ol("*/");
2440 o("public static void deleteByKey(final Connection con, ");
2441 o(pkFormalParamString);
2442 ol(") throws SQLException");
2443 ol(" {");
2444 ol(" __deletebykey_called++;");
2445 ol(" PreparedStatement ps = prepareStatement(con, deleteByPKStmt);");
2446
2447 for (int n = 0; n < pklist.size(); n++)
2448 {
2449 ColumnData cd = (ColumnData) pklist.get(n);
2450 String varname = cd.getName();
2451 o("\t");
2452 String pos = String.valueOf((n+1));
2453 ol( cd.getPreparedStmtSetMethod("ps.", pos, varname) );
2454 }
2455
2456 ol(" log.bug(\"Query to run: \", ps);");
2457 ol(" final int result = ps.executeUpdate();");
2458 // ol(" ps.clearParameters();");
2459 ol(" if (result != 1) { ");
2460 ol(" throw new DBOException(\"The number of deleted rows was: \" + result + \"; [Should have been 1 row exactly] \");");
2461 ol(" }");
2462 ol(" }");
2463 } //~write delete by key
2464
2465 final void mgrWriteMethodDeleteWhere()
2466 {
2467 ol();
2468 ol("/** ");
2469 ol("Deletes the rows with the specified where clause. <p><b>The");
2470 ol("where clause is sent as-is to the database and SQL injection");
2471 ol("attacks are possible if it is created as-is from a untrusted");
2472 ol("source.</b>");
2473 ol("(note: the string <tt>\"WHERE\"</tt> does <b>not</b> have to be");
2474 ol("specified in the clause. It is added automatically by this method).");
2475 ol();
2476 ol("@return the number of rows deleted by the database");
2477 ol("*/");
2478 o("public static int deleteWhere(final Connection con, final String where) throws SQLException");
2479 ol(" {");
2480 ol(" __deletewhere_called++;");
2481 ol(" Argcheck.notnull(where, \"the where parameter was null (and should not be null)\");");
2482 ol(" final String stmt_string = \"DELETE from " + table.getName() + " WHERE \" + where ;");
2483 ol(" Statement stmt = con.createStatement();");
2484 ol(" log.bug(\"Query to run: \", stmt_string);");
2485 ol(" final int result = stmt.executeUpdate(stmt_string);");
2486 ol(" return result;");
2487 ol("}");
2488 }
2489
2490 final void mgrWriteMethodDeleteUsing() throws SQLException
2491 {
2492 ol();
2493 ol("/** ");
2494 ol("Returns the rows returned by querying the table with the contents of");
2495 ol("the specified instance of <tt>alltypes</tt> or <tt>null</tt> if no");
2496 ol("rows were found. As many fields in <tt>alltypes</tt> can be set as");
2497 ol("needed and the values of all set fields (including fields explicitly");
2498 ol("set to <tt>null</tt>) are then used to perform the query. <p>Note,");
2499 ol("however that this method does use any primary key(s). If the ");
2500 ol("primary keys are known then one should use the {@link");
2501 ol("#deleteByKey deleteByKey} method to delete the data instead.");
2502 ol("<p>");
2503 ol("This method is often convenient/safer than the {@link #deleteWhere");
2504 ol("deleteWhere} method (because the <tt>deleteWhere</tt> method takes");
2505 ol("an arbitrary query string which has to be properly escaped by the user).");
2506 ol();
2507 ol("<p>Essentially, this method is a more convenient way to use a");
2508 ol("PreparedStatement. Internally, a prepared statement is created and");
2509 ol("it's parameters are set to fields that are set in this object).");
2510 ol("Using PreparedStatements directly is also perfectly fine. For");
2511 ol("example, the following are equivalent. ");
2512 ol("<p> Using a PreparedStatement:");
2513 ol("<blockquote><pre>");
2514 ol("String foo = \"delete from table_foo where x = ? and y = ?\";");
2515 ol("PreparedStatement ps = con.prepareStatement(foo);");
2516 ol("ps.setString(1, \"somevalue\");");
2517 ol("ps.setString(2, \"othervalue\");");
2518 ol("int rows_deleted = ps.executeUpdate();");
2519 ol("</pre> </blockquote> ");
2520 ol("");
2521 ol("Using this method:");
2522 ol("<blockquote><pre>");
2523 ol("table_foo proto = new table_foo();");
2524 ol("proto.set_x(\"somevalue\"); //compile time safety");
2525 ol("proto.set_y(\"othervalue\"); //compile time safety");
2526 ol("int rows_deleted = table_fooMgr.<font color=blue>deleteUsing</font>(proto);");
2527 ol("</pre></blockquote>");
2528 ol("@return the number of rows deleted");
2529 ol("*/");
2530 o("public static int deleteUsing(final Connection con, final ");
2531 o(beanClassName);
2532 ol(" bean) throws ValidateException, SQLException");
2533 ol(" {");
2534 ol(" __deleteusing_called++;");
2535 ol();
2536 ol(" Argcheck.notnull(bean, \"the bean parameter was null (and should not be null)\");");
2537 ol(" if (! bean.isModified()) { ");
2538 ol(" throw new ValidateException(\"bean=\" + bean + \" not modified, ignoring query\");");
2539 ol(" }");
2540 ol();
2541 ol(" final StringBuilder buf = new StringBuilder(512);");
2542 o (" buf.append(\"DELETE from ");
2543 o (table.getName());
2544 ol(" WHERE \");");
2545 ol();
2546 ol(" int count = 0;");
2547 List cols = table.getColumnList();
2548 List relevant_cols = new ArrayList();
2549 for (int n = 0; n < cols.size(); n++)
2550 {
2551 ColumnData cd = (ColumnData) cols.get(n);
2552 //columns that are not PK only
2553 if (cd.isPK())
2554 continue;
2555
2556 o (" if (bean.");
2557 o (wrangler.getIsModifiedName(cd));
2558 ol("()) { ");
2559
2560 //for the later call to utilFillPStmtFromList_IfModified
2561 relevant_cols.add(cd);
2562
2563 if (! cd.usesPrimitiveJavaType()) {
2564 o (" if (bean."); o(wrangler.getGetName(cd)); ol("() == null) {");
2565 o (" buf.append(\""); o(cd.getName()); ol(" is NULL and \");");
2566 ol(" }");
2567 ol(" else{");
2568 o (" buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");");
2569 ol(" count++;");
2570 ol(" }");
2571 }
2572 else{
2573 o (" buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");");
2574 ol(" count++;");
2575 }
2576 ol(" }");
2577 } //for
2578
2579 if (relevant_cols.size() == 0) {
2580 ol(" throw new RuntimeException(\"This table contains to non-primary keys. Use the deleteByKey method instead.\");");
2581 ol(" }");
2582 return;
2583 }
2584
2585 //get rid of last "and "
2586 ol(" buf.setLength(buf.length() - 4);");
2587 ol(" if (count == 0) {");
2588 ol(" throw new ValidateException(\"No non-PrimaryKey column was modified/set in this bean. You must set at least one such column. To delete by the Primary key, use the deleteByKey method instead.\");");
2589 ol(" }");
2590 ol(" final String getUsingPKStmt = buf.toString();");
2591 ol(" PreparedStatement ps = prepareStatement(con, getUsingPKStmt);");
2592
2593 utilFillPStmtFromList_IfModified_Object(relevant_cols, "\t");
2594
2595 ol(" log.bug(\"Query to run: \", ps);");
2596 ol(" List list = new ArrayList();");
2597 ol(" int result = ps.executeUpdate();");
2598 // ol(" ps.clearParameters();");
2599 ol(" return result;");
2600 ol(" }");
2601 } //~write deleteusing
2602
2603
2604 final void mgrWriteMethodCount()
2605 {
2606 ol();
2607 ol("private final static String countStmt = \"SELECT count(*) from " + table.getName() + "\";");
2608 ol("/**");
2609 ol("Returns the count of all rows in the table. <p><b>Note</b>: This may");
2610 ol("be an expensive operation in MVCC databases like PostgresQL, Oracle and");
2611 ol("others, where an entire non-optimized table scan <i>may</i> be");
2612 ol("required -- hence speed will typically be O(n). However, on Postgres (for");
2613 ol("example), this is still very fast for small values of n (on a");
2614 ol("mid-level test machine) as of 2004, counting 4k records was about");
2615 ol("15 milli-seconds(ms); this scaled almost linearly, so count(*) for 16k records was");
2616 ol("about 70 ms, 65k records was about 370 ms, 524k records was about");
2617 ol("2000 ms and 1 million records was about 4000 ms. Results will vary");
2618 ol("on your machine and database but the general O(n) principle will");
2619 ol("remain the same.");
2620 ol("*/");
2621 ol("public static int count(final Connection con) throws SQLException");
2622 ol(" {");
2623 ol(" __count_called++;");
2624 ol(" int count = -1;");
2625 ol(" final Statement stmt = con.createStatement();");
2626 ol(" final ResultSet rs = stmt.executeQuery(countStmt);");
2627 ol(" if (rs.next())");
2628 ol(" {");
2629 ol(" count = rs.getInt(1);");
2630 ol(" }");
2631 ol(" else { //rs returned no count, which should never happen");
2632 ol(" throw new DBOException(\"The COUNT query [\" + countStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2633 ol(" }");
2634 ol(" stmt.close();");
2635 ol(" return count;");
2636 ol(" }");
2637 } //~write count
2638
2639 final void mgrWriteMethodCountWhere()
2640 {
2641 ol();
2642 ol("/**");
2643 ol("Returns the count of rows in the table using the specified <tt>where</tt> clause.");
2644 ol("(note: the string <tt>\"WHERE\"</tt> does <b>not</b> have to be");
2645 ol("specified in the clause. It is added automatically by this method).");
2646 ol();
2647 ol("@throws IllegalArgumentException if the where paramater was null");
2648 ol("*/");
2649 ol("public static int countWhere(final Connection con, final String where) throws SQLException");
2650 ol(" {");
2651 ol(" __countwhere_called++;");
2652 ol(" Argcheck.notnull(where, \"the where parameter was null\");");
2653 ol(" int count = -1;");
2654 ol(" final String countWhereStmt = \"SELECT count(*) from " + table.getName() + " WHERE \" + where;");
2655 ol(" Statement stmt = con.createStatement();");
2656 //the mysql driver does not print stmt.toString() properly so we need
2657 //to also log the statement string
2658 ol(" log.bug(\"Query to run: \", stmt, \" \", countWhereStmt);");
2659 ol(" ResultSet rs = stmt.executeQuery(countWhereStmt);");
2660 ol(" if (rs.next())");
2661 ol(" {");
2662 ol(" count = rs.getInt(1);");
2663 ol(" }");
2664 ol(" else { //rs returned no count, which should never happen");
2665 ol(" throw new DBOException(\"The COUNT query [\" + countWhereStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2666 ol(" }");
2667 ol(" stmt.close();");
2668 ol(" return count;");
2669 ol(" }");
2670 } //~write countwhere
2671
2672 final void mgrWriteMethodCountUsing() throws SQLException
2673 {
2674 ol();
2675 ol("/** ");
2676 ol("Returns the rows count by querying the table with the contents of the");
2677 o ("specified instance of <tt>");
2678 o (beanClassName);
2679 ol("</tt> As many fields in <tt>alltypes</tt> can be set as needed and the");
2680 ol("values of all set fields (including fields explicitly set to");
2681 ol("<tt>null</tt>) are then used to perform the query. If the primary");
2682 ol("key(s) are known then one can also use the {@link #exists} method to");
2683 ol("see if that row exists in the database.");
2684 ol("<p>");
2685 ol("This method is often convenient/safer than the {@link #countWhere");
2686 ol("countWhere} method (because the <tt>countWhere</tt> method takes an");
2687 ol("arbitrary query string which has to be properly escaped by the");
2688 ol("user). ");
2689 ol("<p>Essentially, this method is a more convenient way to use a");
2690 ol("PreparedStatement (with parameters set to fields that are set in");
2691 ol("this object). Using PreparedStatements directly is also perfectly");
2692 ol("fine. For example, the following two are equivalent. <p>");
2693 ol("Using a PreparedStatement:");
2694 ol("<blockquote><pre>");
2695 ol("String foo = \"select <i>count(*)</i> from table_foo where x = ? and y = ?\";");
2696 ol("PreparedStatement ps = con.prepareStatement(foo);");
2697 ol("ps.setString(1, \"somevalue\");");
2698 ol("ps.setString(2, \"othervalue\");");
2699 ol("ResultSet rs = ps.executeUpdate();");
2700 ol("rs.next();");
2701 ol("int count = rs.getInt(1);");
2702 ol("</pre> </blockquote>");
2703 ol("");
2704 ol("Using this method:");
2705 ol("<blockquote><pre>");
2706 ol("table_foo proto = new table_foo();");
2707 ol("proto.set_x(\"somevalue\"); //compile time safety");
2708 ol("proto.set_y(\"othervalue\"); //compile time safety");
2709 ol("int count = table_fooMgr.<font color=blue>countUsing</font>(proto);");
2710 ol("</pre> </blockquote>");
2711 ol("*/");
2712 o("public static int countUsing(final Connection con, final ");
2713 o(beanClassName);
2714 ol(" bean) throws ValidateException, SQLException");
2715 ol(" {");
2716 ol(" __countusing_called++;");
2717 o (" Argcheck.notnull(bean, \"the bean parameter was null (and should not be null)\");");
2718 ol(" if (! bean.isModified()) { ");
2719 ol(" throw new ValidateException(\"bean=\" + bean + \" not modified, ignoring query\");");
2720 ol(" }");
2721 ol();
2722 ol(" int count = 0;");
2723 ol(" final StringBuilder buf = new StringBuilder(512);");
2724 o (" buf.append(\"SELECT count(*) from " );
2725 o (table.getName());
2726 ol(" WHERE \");");
2727
2728 List cols = table.getColumnList();
2729 List relevant_cols = new ArrayList();
2730 for (int n = 0; n < cols.size(); n++)
2731 {
2732 ColumnData cd = (ColumnData) cols.get(n);
2733
2734 //for the later call to utilFillPStmtFromList_IfModified
2735 relevant_cols.add(cd);
2736
2737 o (" if (bean.");
2738 o (wrangler.getIsModifiedName(cd));
2739 ol("()) { ");
2740 if (! cd.usesPrimitiveJavaType()) {
2741 o (" if (bean."); o(wrangler.getGetName(cd)); ol("() == null) {");
2742 o (" buf.append(\""); o(cd.getName()); ol(" is NULL and \");");
2743 ol(" }");
2744 ol(" else{");
2745 o (" buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");");
2746 ol(" count++;");
2747 ol(" }");
2748 }
2749 else{
2750 o (" buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");");
2751 ol(" count++;");
2752 }
2753 ol(" }");
2754 }
2755 ol();
2756
2757 //get rid of last "and "
2758 ol(" buf.setLength(buf.length() - 4);");
2759 ol();
2760 ol(" final String countUsingStmt = buf.toString();");
2761 ol(" PreparedStatement ps = prepareStatement(con, countUsingStmt);");
2762
2763 utilFillPStmtFromList_IfModified_Object(relevant_cols, "\t");
2764
2765 ol(" log.bug(\"Query to run: \", ps);");
2766 ol(" ResultSet rs = ps.executeQuery();");
2767 ol(" if (! rs.next()) {");
2768 ol(" throw new DBOException(\"The COUNT query [\" + countUsingStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2769 ol(" }");
2770 ol(" int rows = rs.getInt(1);");
2771 ol(" rs.close();");
2772 // ol(" ps.clearParameters();");
2773 ol(" return rows;");
2774 ol(" }");
2775 } //~write getusing
2776
2777
2778 final void mgrWriteMethodExists() throws SQLException
2779 {
2780 if (pklist.size() == 0) {
2781 ol("/* exists() not implemented since this table does not have any primary keys defined */");
2782 return;
2783 }
2784
2785 ol();
2786 o("static private final String existsStmt = \"SELECT count(*) from ");
2787 o(table.getName());
2788 o(" WHERE ");
2789 o(Table.getPreparedStmtPlaceholders(pklist));
2790 ol("\";");
2791
2792 ol("/**");
2793 ol("Returns <tt>true</tt> if a row with the specified primary keys exists, <tt>false</tt> otherwise.");
2794 ol("<p>This method uses a prepared statement and is safe from SQL injection attacks");
2795 ol("*/");
2796 o("public static boolean exists(final Connection con, ");
2797 //example: int col_a, String col_b ....
2798 o(pkFormalParamString);
2799 ol(") throws SQLException");
2800 ol(" {");
2801 ol(" __exists_called++;");
2802 ol(" PreparedStatement ps = prepareStatement(con, existsStmt);");
2803 for (int n = 0; n < pklist.size(); n++)
2804 {
2805 ColumnData cd = (ColumnData) pklist.get(n);
2806 String varname = cd.getName(); //example: col_a
2807 o("\t");
2808 String pos = String.valueOf((n+1));
2809 ol(cd.getPreparedStmtSetMethod("ps.", pos, varname));
2810 }
2811 ol(" log.bug(\"Query to run: \", ps);");
2812 ol(" ResultSet rs = ps.executeQuery();");
2813 ol(" int count = -1;");
2814 ol(" if (rs.next())");
2815 ol(" {");
2816 ol(" count = rs.getInt(1);");
2817 ol(" }");
2818 ol(" else { //rs returned no count, which should never happen");
2819 ol(" throw new DBOException(\"The COUNT query [\" + existsStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2820 ol(" }");
2821 ol(" rs.close();");
2822 // ol(" ps.clearParameters();");
2823 ol(" return (count > 0); //exists if count > 0");
2824 ol(" }");
2825 }
2826
2827 final void mgrWriteMethodExistsUsing() throws SQLException
2828 {
2829 ol("/**");
2830 o ("A thin wrapper around {@link getUsing(Connection,"); o(beanClassName);
2831 ol(") getUsing}");
2832 ol("that returns <tt>false</tt> if no rows are returned, <tt>true</tt> otherwise.");
2833 ol("*/");
2834 o("public static boolean existsUsing(final Connection con, final ");
2835 o(beanClassName);
2836 ol(" bean) throws ValidateException, SQLException");
2837 ol(" {");
2838 ol(" final List list = getUsing(con, bean, null);");
2839 ol(" return (list.size() > 0);");
2840 ol(" }");
2841 }
2842
2843 final void mgrWriteMethodPrepareStatement()
2844 {
2845 ol();
2846 ol("/**");
2847 ol("Returns a prepared statement given it's variable name.");
2848 ol();
2849 ol("Essentially speeds up the creation of prepared statements perhaps");
2850 ol("using a per connection cache -- which makes sense for pooled");
2851 ol("connections since they are not closed but returned to the pool");
2852 ol("and hence don't need to \"prepare\" statements every time (the");
2853 ol("prepareStatement call is seperate from actually filling in the");
2854 ol("placeholders in a already created prepared statement -- which");
2855 ol("does need to be done every time). <p>");
2856 ol("Prepared statements are unique per connection, so multiple threads");
2857 ol("using different connections won't stomp over each other's prepared");
2858 ol("statements. Multiple threads using the SAME connection will cause");
2859 ol("bizarre errors but multiple threads won't get the same connection");
2860 ol("from the connection manager -- ever :-), so that should never happen.");
2861 ol("*/");
2862 o("private static final PreparedStatement prepareStatement (");
2863 ol(" final Connection con, final String sql) throws SQLException");
2864 ol(" {");
2865 ol(" if (! (con instanceof fc.jdbc.PooledConnection) ) { ");
2866 ol(" return con.prepareStatement(sql);");
2867 ol(" }");
2868 ol(" final PooledConnection pc = (PooledConnection) con;");
2869 ol(" return pc.getCachedPreparedStatement(sql);");
2870 ol(" }");
2871 }
2872
2873 final void mgrWriteCheckDiscarded()
2874 {
2875 ol();
2876 ol("private static final void checkDiscarded(final DBO bean) throws DBOException");
2877 ol(" {");
2878 ol(" if (bean.isDiscarded()) {");
2879 ol(" throw new DBOException(\"===== Attempt to save a discarded object === \" + bean);");
2880 ol(" }");
2881 ol(" }");
2882 }
2883
2884 final void mgrWriteMethodStats()
2885 {
2886 ol();
2887 ol("private static final java.util.Date __loadDate = new java.util.Date();");
2888 ol("/** Returns usage statistics for this class */");
2889 ol("public static String stats() ");
2890 ol(" {");
2891 ol(" //locally created _numberFormat for thread safety");
2892 ol(" final java.text.NumberFormat _numberFormat = java.text.NumberFormat.getInstance();");
2893 ol(" final String nl = fc.io.IOUtil.LINE_SEP;");
2894 ol(" StringBuffer buf = new StringBuffer(256);");
2895 o (" buf.append(\"Class Name: [");
2896 o (mgrClassName);
2897 ol("]; Class loaded on: \");");
2898 ol(" buf.append(__loadDate);");
2899 ol(" buf.append(nl);");
2900 o (" buf.append(\"---- Start Usage Statistics ----\")"); ol(".append(nl);");
2901 ol();
2902 ol(" ByteArrayOutputStream out = new ByteArrayOutputStream(512);");
2903 ol(" TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();");
2904 ol(" config.setPrintBorders(false);");
2905 ol(" config.setCellSpacing(1);");
2906 ol(" config.setCellPadding(0);");
2907 ol(" config.setAutoFit(true);");
2908 ol(" TablePrinter p = new TablePrinter(2, new PrintStream(out), config);");
2909 ol(" p.startTable();");
2910 ol();
2911 ol(" p.startRow();");
2912 ol(" p.printCell(\"Method\");");
2913 ol(" p.printCell(\"# times called\");");
2914 ol(" p.endRow();");
2915 ol();
2916 ol(" p.startRow();");
2917 ol(" p.printCell(\"getAll()\");");
2918 ol(" p.printCell(_numberFormat.format(__getall_called));");
2919 ol(" p.endRow();");
2920 ol();
2921 ol(" p.startRow();");
2922 ol(" p.printCell(\"getLimited()\");");
2923 ol(" p.printCell(_numberFormat.format(__getlimited_called));");
2924 ol(" p.endRow();");
2925 ol();
2926 ol(" p.startRow();");
2927 ol(" p.printCell(\"getByKey()\");");
2928 ol(" p.printCell(_numberFormat.format(__getbykey_called));");
2929 ol(" p.endRow();");
2930 ol();
2931 ol(" p.startRow();");
2932 ol(" p.printCell(\"getWhere()\");");
2933 ol(" p.printCell(_numberFormat.format(__getwhere_called));");
2934 ol(" p.endRow();");
2935 ol();
2936 ol(" p.startRow();");
2937 ol(" p.printCell(\"getUsing()\");");
2938 ol(" p.printCell(_numberFormat.format(__getusing_called));");
2939 ol(" p.endRow();");
2940 ol();
2941 ol(" p.startRow();");
2942 ol(" p.printCell(\"getUsing(prepared_stmt)\");");
2943 ol(" p.printCell(_numberFormat.format(__getusing_ps_called));");
2944 ol(" p.endRow();");
2945 ol();
2946 ol(" p.startRow();");
2947 ol(" p.printCell(\"getFromRS()\");");
2948 ol(" p.printCell(_numberFormat.format(__getfromrs_called));");
2949 ol(" p.endRow();");
2950 ol();
2951 ol(" p.startRow();");
2952 ol(" p.printCell(\"save()\");");
2953 ol(" p.printCell(_numberFormat.format(__save_called));");
2954 ol(" p.endRow();");
2955 ol();
2956 ol(" p.startRow();");
2957 ol(" p.printCell(\"delete()\");");
2958 ol(" p.printCell(_numberFormat.format(__delete_called));");
2959 ol(" p.endRow();");
2960 ol();
2961 ol(" p.startRow();");
2962 ol(" p.printCell(\"deleteByKey()\");");
2963 ol(" p.printCell(_numberFormat.format(__deletebykey_called));");
2964 ol(" p.endRow();");
2965 ol();
2966 ol(" p.startRow();");
2967 ol(" p.printCell(\"deleteWhere()\");");
2968 ol(" p.printCell(_numberFormat.format(__deletewhere_called));");
2969 ol(" p.endRow();");
2970 ol();
2971 ol(" p.startRow();");
2972 ol(" p.printCell(\"deleteUsing()\");");
2973 ol(" p.printCell(_numberFormat.format(__deleteusing_called));");
2974 ol(" p.endRow();");
2975 ol();
2976 ol(" p.startRow();");
2977 ol(" p.printCell(\"count()\");");
2978 ol(" p.printCell(_numberFormat.format(__count_called));");
2979 ol(" p.endRow();");
2980 ol();
2981 ol(" p.startRow();");
2982 ol(" p.printCell(\"countWhere()\");");
2983 ol(" p.printCell(_numberFormat.format(__countwhere_called));");
2984 ol(" p.endRow();");
2985 ol();
2986 ol(" p.startRow();");
2987 ol(" p.printCell(\"countUsing()\");");
2988 ol(" p.printCell(_numberFormat.format(__countusing_called));");
2989 ol(" p.endRow();");
2990 ol();
2991 ol(" p.startRow();");
2992 ol(" p.printCell(\"exists()\");");
2993 ol(" p.printCell(_numberFormat.format(__exists_called));");
2994 ol(" p.endRow();");
2995 ol();
2996 ol(" p.endTable();");
2997 ol(" buf.append(out.toString());");
2998 ol(" return buf.toString();");
2999 ol(" }");
3000 }
3001
3002 final void mgrWriteMethodToString()
3003 {
3004 ol();
3005 ol("public String toString() ");
3006 ol(" {");
3007 ol(" return getClass().getName() + \" [call stats() for more info]\";");
3008 ol(" }");
3009 }
3010
3011 void mgrWriteValidators() throws SQLException
3012 {
3013 ol();
3014 ol("// ================ Validation ==================== ");
3015 ol();
3016 ol("/** ");
3017 ol("Creates and attaches validators for all the fields in the");
3018 ol("specified {@link fc.web.forms.Form}. These fields should");
3019 o("<i>have the same name</i> in the form as in {@link "); o(beanClassName);
3020 ol("}. If this is not the case, then the then the differences can be specifed");
3021 ol("as follows. <p>");
3022 ol("<dl>");
3023 ol("<dt>with a prefix</dt>");
3024 o (" <dd><tt>(prefix + ");o(beanClassName);ol(" column)</tt> should equal <tt>form fieldname</tt></dd>");
3025 ol("<dt>with a suffix</dt> ");
3026 o (" <dd><tt>(");o(beanClassName);ol(" column + suffix)</tt> should equal <tt>form fieldname</tt></dd>");
3027 ol("<dt>with both a prefix/suffix</dt> ");
3028 o (" <dd><tt>(prefix + ");o(beanClassName);ol(" + suffix)</tt> should equal <tt>form fieldname</tt></dd>");
3029 ol("<dt>with a arbitrary map</dt> ");
3030 o (" <dd>[key] <tt>");o(beanClassName);ol(" column</tt> -> [value] <tt>form fieldname</tt>");
3031 ol(" <u>If a map is specified, then the prefix/suffix are not used.</u>");
3032 ol(" </dd>");
3033 ol("</dl>");
3034 ol("<p>These validators are for database constraints such as <i>nullability</i> & <i>column length</i>.");
3035 ol("These validators save a lot of grunt-work in adding such schema");
3036 ol("constraints to the front-end {@link fc.web.forms.Form}. <p><b>However, <i>business and");
3037 ol("other validation constraints</i> still need to be manually added to");
3038 ol("the application code/front-end forms as/when needed</b>.");
3039 ol("<p>");
3040 ol("");
3041 ol("The following table shows the kind of validators added by this method");
3042 ol("<table border=1 width=90%>");
3043 ol("<tr bgcolor='#CCCCCC'>");
3044 ol(" <td>Database SQL Type</td>");
3045 ol(" <td><b>Nullable</b>validator</td>");
3046 ol(" <td><b>Length</b> validator</td>");
3047 ol(" <td><b>Digits only</b> input validator ({@link VText#allowIntegersOnly})</td>");
3048 ol("</tr>");
3049 ol(" <tr>");
3050 ol(" <td><tt>CHAR</tt>, <tt>VARCHAR</tt></td>");
3051 ol(" <td>Yes (maximum length constraint).<br><font size='-1' color=red>This");
3052 ol(" only applies to form fields that are subclasses of {@link ");
3053 ol(" fc.web.forms.MaxSizable} </font></td>");
3054 ol(" <td>-NO-</td>");
3055 ol(" </tr>");
3056 ol(" <tr>");
3057 ol(" <td><tt>TINYINT, MEDIUMINT, INT, BIGINT (integral types)</tt></td>");
3058 ol(" <td>Yes</td>");
3059 ol(" <td>-NO-</td>");
3060 ol(" <td>Yes to integer columns displayed using form fields that are subclasses of {@link fc.web.forms.AbstractText}<br> Note: <b>not</b> added non-<i>integral</i> number types such as <tt>FLOAT, REAL, DOUBLE, NUMERIC/DECIMAL</tt></td>");
3061 ol(" </tr>");
3062 ol(" <tr>");
3063 ol(" <td>All other SQL types</td>");
3064 ol(" <td>Yes</td>");
3065 ol(" <td>-NO-</td>");
3066 ol(" </tr>");
3067 ol("</table>");
3068 ol("<p>Automatic validators are very useful but can be very tricky to understand. It is");
3069 ol("suggested to invoke this method, print the form using it's <tt>toString</tt>");
3070 ol("method and then examine the output to see what validators were added If those");
3071 ol("automatic validators are too little, too many or too hard to understand, <u>then");
3072 ol("simply enoough, do NOT invoke this method and simply add validators by");
3073 ol("hand</u>. In particular, do <i>not</i> add automatic validators for");
3074 ol("<b>tables</b> in which a row is optional but <i>if</i> some column is filled in");
3075 ol("the front end form, <i>then</i> all columns must be filled.");
3076 ol();
3077 ol("@param form the form containing fields (some or all) representing");
3078 ol(" this and possible other tables. These field");
3079 ol(" objects must have been added to the form prior");
3080 ol(" to calling this method");
3081 ol("@param prefix an optional (null allowed) prefix to this table's column name with which the");
3082 ol(" corresponding column was added to the form.");
3083 ol(" A <tt>*</tt> specifies all possible prefixes");
3084 ol("@param suffix an optional suffix (null allowed) to this table's column name with which the ");
3085 ol(" corresponding column was added to the form.");
3086 ol(" A <tt>*</tt> specifies all possible suffixes");
3087 ol("@param map an optional map (null allowed) that maps this table's column name with which the ");
3088 ol(" corresponding column was added to the form. ");
3089 ol(" [key] <tt>table's column_name</tt> -> [value] <tt>form's fieldname</tt>");
3090 ol("*/");
3091 ol("public static void addValidators(final fc.web.forms.Form form, final String prefix, final String suffix, final Map map) ");
3092 ol(" {");
3093 ol(" addValidators(form, prefix, suffix, map, false);");
3094 ol(" }");
3095 ol();
3096
3097 ol("private static void addValidators(fc.web.forms.Form form, String prefix, String suffix, final Map map, final boolean onlyOnFilled) ");
3098 ol(" {");
3099 ol(" List list = null;");
3100 ol(" Argcheck.notnull(form, \"How can I add validators to the form when the form parameter was null ?\");");
3101 ol(" Field field = null;");
3102 ol(" FieldValidator fv = null;");
3103 ol(" String colname_in_form = null;");
3104 ol(" //fields can be null if they are not being used in the html form");
3105 List cols = table.getColumnList();
3106 for (int n = 0; n < cols.size(); n++)
3107 {
3108 ColumnData cd = (ColumnData) cols.get(n);
3109 String colname = cd.getName();
3110 String sqltypename = cd.getSQLTypeName().intern();
3111 ol();
3112 o ("\t");ol(getBeanComment(cd));
3113 o (" list = getFieldFromForm(form, \"");
3114 o (colname);
3115 o ("\", prefix, suffix, map, ");
3116
3117 //false=no warn message if this field not in form
3118 if (cd.isAutoIncrement())
3119 o("false");
3120 else
3121 o("true");
3122
3123 ol(");");
3124 ol(" if (list.size() > 0) ");
3125 ol(" { //add applicable automatic validators, empty if n/a");
3126 ol(" for (int n = 0; n < list.size(); n++)");
3127 ol(" {");
3128 ol(" field = (Field) list.get(n);");
3129
3130 //nullable
3131 if (! cd.isNullable())
3132 {
3133 if (cd.hasDefaultValue()) {
3134 o (" /* field is non-nullable but has a default value [");
3135 o (cd.getDefaultValue());
3136 ol("], skipping non-nullability validation */");
3137 }
3138 else{
3139 ol(" if (field instanceof Choice) {");
3140 ol(" //choice fields are ignored because they can");
3141 ol(" //mean false even when NOT selected/filled");
3142 ol(" continue;");
3143 ol(" }");
3144 ol(" /* field is non-nullable */");
3145 o (" fv = new VFilled(field, \"");
3146 o(validateNull_ErrorMsg);
3147 ol("\");");
3148 }
3149 }
3150 else{
3151 ol(" /* field is nullable, skipping non-nullability validation */");
3152 }
3153
3154 //numbers only
3155 if (sqltypename.toLowerCase().indexOf("int") >= 0)
3156 {
3157 ol();
3158 ol(" /* database type for this field is integral */");
3159 ol(" if (field instanceof AbstractText) {");
3160 o (" fv = new VText((AbstractText)field, \"");
3161 o(validateIntegerOnly_ErrorMsg);
3162 ol("\")");
3163 ol(" .allowIntegersOnly();");
3164 ol(" }");
3165 }
3166
3167 //length
3168 if ((sqltypename == "CHAR" || sqltypename == "VARCHAR"))
3169 /*
3170 colsize IS NOT accurate/reliable for non text types.
3171 (therefore we only set maxsize for char/varchar)
3172
3173 text(longvarchar) size is not knowable with postgres and postgres
3174 returns -1 for LONGVARCHAR Except now we have another bug, the
3175 current driver returns VARCHAR even for text columns (instead of
3176 LONGVARCHAR). What the fuck ? They are fucking up the best free
3177 database on the planet with a shitty jdbc driver. god, it's almost
3178 5am and i'm tired. p.s: postgres jdbc driver + support is still
3179 waaay better than all other competing db's though.
3180 */
3181 {
3182 //bug workaround
3183 int colsize = cd.getSize();
3184 if (colsize < 0)
3185 colsize = Integer.MAX_VALUE;
3186 //end workaround
3187
3188 ol(" if (! (field instanceof MaxSizable)) {");
3189 o (" log.warn(\"Skipping maximum length validator for field '\" + field.getName() + \"'; [database type='");o(sqltypename);ol("', field.type='\" + field.getType() + \"' is not MaxSizable]\"); ");
3190 ol(" }");
3191 ol(" else{");
3192 o (" VText vt = new VText((MaxSizable) field, \"");
3193 o(validateText_ErrorMsg_MaxSize);
3194 ol("\");");
3195 o (" vt.setMaxSize(");
3196 o (String.valueOf(colsize));
3197 ol(");");
3198 ol(" }");
3199 }
3200 ol(" }"); //generated for
3201 ol(" }");
3202 }
3203 ol(" }");
3204 ol();
3205
3206 ol("/** ");
3207 ol("Convenience method that calls {@link #addValidators(Form, String, String, Map)} with a ");
3208 ol("<tt>null</tt> prefix/suffix and the specified map");
3209 ol("*/");
3210 ol("public static void addValidators(fc.web.forms.Form form, Map map) ");
3211 ol(" {");
3212 ol(" addValidators(form, null, null, map);");
3213 ol(" }");
3214 ol();
3215
3216 ol("/** ");
3217 ol("Convenience method that calls {@link #addValidators(Form, String, String, Map)} with a ");
3218 ol("<tt>null</tt> prefix/suffix/map");
3219 ol("*/");
3220 ol("public static void addValidators(fc.web.forms.Form form) ");
3221 ol(" {");
3222 ol(" addValidators(form, null, null, null);");
3223 ol(" }");
3224 ol();
3225
3226 ol("/** ");
3227 ol("Convenience method that calls {@link #addValidators(Form, String, String, map)} with the ");
3228 ol("specified prefix and a <tt>null</tt> suffix/map");
3229 ol("*/");
3230 ol("public static void addValidators(fc.web.forms.Form form, String prefix) ");
3231 ol(" {");
3232 ol(" addValidators(form, prefix, null, null);");
3233 ol(" }");
3234 ol();
3235
3236 ol("/** ");
3237 ol("Validates a form field <i>if</i> it is filled by the user. Leaves empty fields alone.");
3238 ol("This is very useful for fields that are optional but must have the correct value when");
3239 ol("filled by the user");
3240 ol("*/");
3241 ol("public static void addIfFilledValidators(Form form, String prefix) ");
3242 ol(" {");
3243 ol(" addValidators(form, prefix, null, null, true);");
3244 ol(" }");
3245 ol();
3246
3247 ol("/** implementation helper method -- not for public use */");
3248 ol("static List getFieldFromForm(Form form, String colname, String prefix, String suffix, Map map, boolean warn)");
3249 ol(" {");
3250 ol(" Field field = null;");
3251 ol(" List list = Form.empty_list;");
3252 ol(" boolean getwhere = false;");
3253 ol(" getwhere = false;");
3254 ol(" String colname_in_form = colname;");
3255 ol();
3256 ol(" if (map != null) {");
3257 ol(" String str = (String) map.get(colname);");
3258 ol(" if (str != null) {");
3259 ol(" prefix = null; /*ignored when there is a mapping*/");
3260 ol(" suffix = null; /*ignored when there is a mapping*/");
3261 ol(" colname_in_form = str; /* else if not in map, colname remains as-is*/");
3262 ol(" }");
3263 ol(" }");
3264 ol();
3265 ol(" if (prefix != null) ");
3266 ol(" { ");
3267 ol(" if (prefix.equals(\"*\")) { ");
3268 ol(" getwhere = true;");
3269 ol(" }");
3270 ol(" else{");
3271 ol(" colname_in_form = prefix + colname_in_form;");
3272 ol(" }");
3273 ol(" }");
3274 ol();
3275 ol(" if (suffix != null) ");
3276 ol(" {");
3277 ol(" if (suffix.equals(\"*\")) { ");
3278 ol(" getwhere = true;");
3279 ol(" }");
3280 ol(" else{");
3281 ol(" colname_in_form = colname_in_form + suffix;");
3282 ol(" }");
3283 ol(" }");
3284 ol();
3285 ol(" if (getwhere) { ");
3286 ol(" list = form.getContaining(colname_in_form);");
3287 ol(" if (list.size() == 0 && warn) warn(form, colname_in_form, suffix, prefix, map);");
3288 ol(" return list;");
3289 ol(" }");
3290 ol(" else{");
3291 ol(" //containsField() check prevents an un-necessary warning with form.get()");
3292 ol(" if (! form.containsField(colname_in_form)) {");
3293 ol(" if (warn) warn(form, colname_in_form, suffix, prefix, map);");
3294 ol(" return list;");
3295 ol(" }");
3296 ol(" field = form.get(colname_in_form);" );
3297 ol(" list = new ArrayList();");
3298 ol(" list.add(field);");
3299 ol(" }");
3300 ol(" return list;");
3301 ol(" }");
3302
3303 ol();
3304 ol("private static final void warn(Form form, String name, String suffix, String prefix, Map map) {");
3305 ol(" log.warn(form.getName(),\": No automatic validators will be added for Field [\",name,\"]. This field does not exist in the front-end form. (this could be normal). suffix=[\"+suffix+\"] prefix=[\"+prefix+\"] map=\", map);");
3306 ol(" }");
3307
3308 //ValidateBeforeSaveNew
3309 ol();
3310 ol("/** ");
3311 ol("Validates the bean before saving it to the database. This");
3312 ol("method is called internally by the {@link save()} method");
3313 ol("before saving a new bean (i.e., inserting a new row) to the");
3314 ol("database.");
3315 ol("<p>");
3316 ol("The validation is somewhat basic and there can exist many");
3317 ol("constraints and conditions on the database that might results in a");
3318 ol("insert/update error anyway. But by doing some basic validation");
3319 ol("against some known constraints, we save a needless trip to the");
3320 ol("database.");
3321 ol("We check to see that: <ol>");
3322 ol("<li><i>non-nullable and non auto-increment</i> columns [and with no default");
3323 ol("column value in the database] are modified (via a set method) since these");
3324 ol("columns must have a explicitly set value.</li>");
3325 ol("<li>for non-nullable columns that hold non-primitive (i.e., Object)");
3326 ol("java types, the modified value for non-nullable columns is a");
3327 ol("non-null object. </li>");
3328 ol("</ol>");
3329 ol("*/");
3330 o ("protected static void validateBeforeSaveNew(");
3331 o (beanClassName);
3332 ol(" bean) throws ValidateException");
3333 ol(" {");
3334 ol(" boolean error = false;");
3335 ol(" final StringBuffer buf = new StringBuffer(\"The following validation errors were found\").append(IOUtil.LINE_SEP);");
3336 for (int n = 0; n < cols.size(); n++)
3337 {
3338 ColumnData cd = (ColumnData) cols.get(n);
3339 String colname = cd.getName();
3340 ol();
3341 if (cd.isNullable()) {
3342 o (" //["); o (colname);
3343 ol("]=>nullable, no modification check necessary, skipping...");
3344 }
3345 else if ( ! cd.isNullable() && cd.hasDefaultValue()) {
3346 o (" //["); o (colname);
3347 o ("]=>is not nullable but has a default column value [");
3348 o (cd.getDefaultValue());
3349 ol("], no modification check necessary, skipping...");
3350 }
3351 else if (cd.isAutoIncrement()) {
3352 o (" //["); o (colname);
3353 ol("]=>auto-increment, no modification check necessary, skipping...");
3354 }
3355 else { //not nullable and value required
3356 o(" if (! bean.");
3357 o(wrangler.getIsModifiedName(cd));
3358 ol("()) {");
3359 ol(" error = true;");
3360 o (" buf.append(\"");
3361 o (colname);
3362 o (" was not set (this field is required in the database)\").append(\";current value=\"");
3363 o (").append(bean.");
3364 o (wrangler.getGetName(cd));
3365 ol("()).append(IOUtil.LINE_SEP);");
3366 ol(" }");
3367
3368 if (! cd.usesPrimitiveJavaType())
3369 {
3370 ol(" else { //was modified but to null");
3371 o (" if (bean.");
3372 o (wrangler.getGetName(cd));
3373 ol("() == null) {");
3374 ol(" error = true;");
3375 o (" buf.append(\"");
3376 o (colname);
3377 o (" was set to null (but is non-nullable)\").append(\";current value=\"");
3378 o (").append(bean.");
3379 o (wrangler.getGetName(cd));
3380 ol("()).append(IOUtil.LINE_SEP);");
3381 ol(" }");
3382 ol(" }");
3383 }
3384 else{
3385 o (" //");
3386 o (cd.getJavaTypeFromSQLType());
3387 ol(" is primitive, skipping null test");
3388 }
3389 }
3390 } //for
3391
3392 ol(" if (error) { ");
3393 ol(" throw new ValidateException(buf.toString());");
3394 ol(" }");
3395 ol(" }");
3396
3397 //ValidateBeforeSaveUpdate
3398 ol();
3399 ol("/** ");
3400 ol("Validates the bean before saving it to the database. This method is");
3401 ol("called internally by the {@link save()} method before updating an");
3402 ol("existing bean (i.e., updating a row) in the database.");
3403 ol("<p>");
3404 ol("For <i>each modified column</i>, if that column is non-nullable in");
3405 ol("the database, then it must have a non-null value in the bean before");
3406 ol("it is saved. This check is only done for fields of");
3407 ol("<i>non</i>-primitive (Object) java types. [There is no way to ensure");
3408 ol("a non-null value for <i>primitive</i> types since all values");
3409 ol("[including 0] are non-null for those types].");
3410 ol("*/");
3411 o ("protected static void validateBeforeSaveUpdate(");
3412 o (beanClassName);
3413 ol(" bean) throws ValidateException");
3414 ol(" {");
3415 ol(" boolean error = false;");
3416 ol(" final StringBuffer buf = new StringBuffer(\"The following validation errors were found\").append(IOUtil.LINE_SEP);");
3417 ol();
3418
3419 for (int n = 0; n < cols.size(); n++)
3420 {
3421 ColumnData cd = (ColumnData) cols.get(n);
3422 String colname = cd.getName();
3423
3424 if (cd.isNullable()) {
3425 o (" //["); o (colname);
3426 ol("]=>nullable, no modification check necessary, skipping...");
3427 }
3428 /*
3429 dont check for existence of default column value here, if it's an
3430 update, prolly a good idea for the bean to have a updated value and not
3431 rely on column defaults
3432 */
3433 else if (cd.isAutoIncrement()) {
3434 o (" //["); o (colname);
3435 ol("]=>auto-increment, no modification check necessary, skipping...");
3436 }
3437 /*
3438 else if (cd.isPK()) { //must be able to find the row to update
3439 o(" if (! bean.");
3440 o(wrangler.getIsModifiedName(cd));
3441 ol("()) {");
3442 ol(" error = true;");
3443 o (" buf.append(\"");
3444 o (colname);
3445 ol(" was not set (this field is a primary key and is needed to find the row to update.\");");
3446 ol(" }");
3447 }
3448 */
3449 /* unlike validatebeforesave, we don't check to see if every
3450 required column has been modified (an update allows updates
3451 whatever is modified, and leaves the rest (required or non required)
3452 alone
3453 */
3454 else{
3455 o(" if (bean.");
3456 o(wrangler.getIsModifiedName(cd));
3457 ol("()) {");
3458 if (! cd.usesPrimitiveJavaType())
3459 {
3460 o (" if (bean.");
3461 o (wrangler.getGetName(cd));
3462 ol("() == null) {");
3463 ol(" error = true;");
3464 o (" buf.append(\"");
3465 o (colname);
3466 o (" was set to null (but is non-nullable)\").append(\";current value=\"");
3467 o (").append(bean.");
3468 o (wrangler.getGetName(cd));
3469 ol("()).append(IOUtil.LINE_SEP);");
3470 ol(" }");
3471 }
3472 else{
3473 o (" //");
3474 o (cd.getJavaTypeFromSQLType());
3475 ol(" is primitive, skipping null test");
3476 }
3477 ol(" }");
3478 }
3479 } //for
3480
3481 ol(" if (error) { ");
3482 ol(" throw new ValidateException(buf.toString());");
3483 ol(" }");
3484 ol(" }");
3485
3486 }//~mgrWriteValidators()
3487
3488 void o(String str) {
3489 out.print(str);
3490 }
3491
3492 void o(String str, String str2) {
3493 out.print(str);
3494 out.print(str2);
3495 }
3496
3497 void ol(String str) {
3498 out.println(str);
3499 }
3500
3501 void ol() {
3502 out.println();
3503 }
3504
3505 void utilFillPStmtFromList_IfModified (
3506 final List list, final String tabprefix)
3507 throws SQLException
3508 {
3509 final String tabprefix2 = tabprefix + "\t";
3510 //final String tabprefix3 = tabprefix + "\t\t";
3511
3512 o(tabprefix);
3513 ol("int pos = 0;");
3514
3515 /* Example: for all columns, output:
3516 if (bean.isModified_col_a())
3517 {
3518 pos++;
3519 int col_a = bean.get_col_a();
3520 ps.set(pos, col_a);
3521 }
3522 */
3523 for (int n = 0; n < list.size(); n++)
3524 {
3525 ColumnData cd = (ColumnData) list.get(n);
3526 String colname = cd.getName();
3527
3528 o(tabprefix);
3529 o("if (bean.");
3530 o(wrangler.getIsModifiedName(cd));
3531 ol("()) {");
3532
3533 o(tabprefix2);
3534 ol("pos++;");
3535
3536 o(tabprefix2);
3537 o(cd.getJavaTypeFromSQLType());
3538 o(" ");
3539 o(colname);
3540 o(" = bean.");
3541 o(wrangler.getGetName(cd));
3542 ol("();");
3543
3544 /* we don't need this, if it's modified, then we
3545 save it, whether it was originally
3546 null in the db is irrelevant
3547
3548 if (bean.isNullInDB(col_a))
3549 o(tabprefix2);
3550 o("if (bean.");
3551 o(wrangler.getIsNullInDBName(cd));
3552 ol("()) {");
3553 o(tabprefix3);
3554 o("ps.");
3555 o(cd.getPreparedStmtSetNullMethod("pos", colname));
3556 ol(";");
3557 o(tabprefix3);
3558 ol("}");
3559 o(tabprefix2);
3560 ol("else{");
3561 o(tabprefix3);
3562 */
3563 o(tabprefix2);
3564 ol(cd.getPreparedStmtSetMethod("ps.", "pos", colname));
3565 //ol("log.bug(ps);");
3566 o(tabprefix2);
3567 ol("}");
3568 } //~for
3569 }
3570
3571
3572 void utilFillPStmtFromList_IfModified_Object
3573 (final List list, final String tabprefix)
3574 throws SQLException
3575 {
3576 final String tabprefix2 = tabprefix + "\t";
3577 final String tabprefix3 = tabprefix + "\t\t";
3578
3579 o(tabprefix);
3580 ol("int pos = 0;");
3581
3582 for (int n = 0; n < list.size(); n++)
3583 {
3584 ColumnData cd = (ColumnData) list.get(n);
3585 String colname = cd.getName();
3586
3587 o(tabprefix);
3588 o("if (bean.");
3589 o(wrangler.getIsModifiedName(cd));
3590 ol("()) {");
3591
3592 if (! cd.usesPrimitiveJavaType())
3593 {
3594 o(tabprefix2);
3595 o ("if (bean.");
3596 o (wrangler.getGetName(cd));
3597 ol("() == null) { ");
3598 o (tabprefix3);
3599 ol("/* no value to set here, uses [xxx IS NULL] syntax*/");
3600 o (tabprefix3);
3601 ol("}");
3602 o(tabprefix2);
3603 ol("else{");
3604 }
3605
3606 o(tabprefix3);
3607 ol("pos++;");
3608 o(tabprefix3);
3609 o(cd.getJavaTypeFromSQLType());
3610 o(" ");
3611 o(colname);
3612 o(" = bean.");
3613 o(wrangler.getGetName(cd));
3614 ol("();");
3615 o(tabprefix3);
3616 ol(cd.getPreparedStmtSetMethod("ps.", "pos", colname));
3617 o(tabprefix3);
3618 ol("}");
3619
3620 if (! cd.usesPrimitiveJavaType()) {
3621 o(tabprefix2);
3622 ol("}");
3623 }
3624 } //~for
3625 }
3626
3627
3628 /**
3629 Usage:
3630 java fc.jdbc.dbobjects.Generate -conf
3631 <path-to-configuration-file> No flags will produce a list of
3632 options and usage information.
3633 **/
3634 public static void main(String[] args) throws Exception
3635 {
3636 Generate gen = new Generate(args);
3637 }
3638
3639 }