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