001 package org.apache.torque.engine.database.model; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE 005 * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 007 * License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 012 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 013 * specific language governing permissions and limitations under the License. 014 */ 015 016 import java.util.ArrayList; 017 import java.util.Collections; 018 import java.util.HashMap; 019 import java.util.Hashtable; 020 import java.util.Iterator; 021 import java.util.List; 022 import java.util.Map; 023 024 import org.apache.commons.collections.map.ListOrderedMap; 025 import org.apache.commons.logging.Log; 026 import org.apache.commons.logging.LogFactory; 027 import org.apache.torque.engine.EngineException; 028 import org.apache.torque.engine.database.transform.DTDResolver; 029 import org.apache.torque.engine.platform.Platform; 030 import org.apache.torque.engine.platform.PlatformFactory; 031 import org.xml.sax.Attributes; 032 033 /** 034 * A class for holding application data structures. 035 * 036 * @author <a href="mailto:leon@opticode.co.za>Leon Messerschmidt</a> 037 * @author <a href="mailto:jmcnally@collab.net>John McNally</a> 038 * @author <a href="mailto:mpoeschl@marmot.at>Martin Poeschl</a> 039 * @author <a href="mailto:dlr@collab.net>Daniel Rall</a> 040 * @author <a href="mailto:byron_foster@byron_foster@yahoo.com>Byron Foster</a> 041 * @author <a href="mailto:monroe@dukece.com>Greg Monroe</a> 042 * @version $Id: Database.java,v 1.1.6.1 2008-04-01 04:07:48 jkeller Exp $ 043 */ 044 public class Database { 045 /** Logging class from commons.logging */ 046 private static Log log = LogFactory.getLog(Database.class); 047 048 private String databaseType = null; 049 private List tableList = new ArrayList(100); 050 private Map domainMap = new HashMap(); 051 private String name; 052 private String javaName; 053 private String pkg; 054 private String baseClass; 055 private String basePeer; 056 private String defaultIdMethod; 057 private String defaultJavaType; 058 private String defaultJavaNamingMethod; 059 private Hashtable tablesByName = new Hashtable(); 060 private Hashtable tablesByJavaName = new Hashtable(); 061 private boolean heavyIndexing; 062 /** the name of the definition file */ 063 private String fileName; 064 private Map options = Collections.synchronizedMap(new ListOrderedMap()); 065 066 /** 067 * Creates a new instance for the specified database type. 068 * 069 * @param databaseType 070 * The default type for this database. 071 */ 072 public Database(String databaseType) { 073 this.databaseType = databaseType; 074 } 075 076 /** 077 * Load the database object from an xml tag. 078 * 079 * @param attrib 080 * the xml attributes 081 */ 082 public void loadFromXML(Attributes attrib) { 083 setName(attrib.getValue("name")); 084 pkg = attrib.getValue("package"); 085 baseClass = attrib.getValue("baseClass"); 086 basePeer = attrib.getValue("basePeer"); 087 defaultJavaType = attrib.getValue("defaultJavaType"); 088 defaultIdMethod = attrib.getValue("defaultIdMethod"); 089 defaultJavaNamingMethod = attrib.getValue("defaultJavaNamingMethod"); 090 if (defaultJavaNamingMethod == null) { 091 defaultJavaNamingMethod = NameGenerator.CONV_METHOD_UNDERSCORE; 092 } 093 heavyIndexing = "true".equals(attrib.getValue("heavyIndexing")); 094 } 095 096 /** 097 * Get the name of the Database 098 * 099 * @return name of the Database 100 */ 101 public String getName() { 102 return name; 103 } 104 105 /** 106 * Set the name of the Database 107 * 108 * @param name 109 * name of the Database 110 */ 111 public void setName(String name) { 112 /** @task check this */ 113 // this.name = (name == null ? Torque.getDefaultDB() : name); 114 this.name = (name == null ? "default" : name); 115 } 116 117 public String getFileName() { 118 return fileName; 119 } 120 121 public void setFileName(String name) { 122 this.fileName = name; 123 } 124 125 /** 126 * Get the value of package. 127 * 128 * @return value of package. 129 */ 130 public String getPackage() { 131 return pkg; 132 } 133 134 /** 135 * Set the value of package. 136 * 137 * @param v 138 * Value to assign to package. 139 */ 140 public void setPackage(String v) { 141 this.pkg = v; 142 } 143 144 /** 145 * Get the value of baseClass. 146 * 147 * @return value of baseClass. 148 */ 149 public String getBaseClass() { 150 if (baseClass == null) { 151 return "BaseObject"; 152 } 153 return baseClass; 154 } 155 156 /** 157 * Set the value of baseClass. 158 * 159 * @param v 160 * Value to assign to baseClass. 161 */ 162 public void setBaseClass(String v) { 163 this.baseClass = v; 164 } 165 166 /** 167 * Get the value of basePeer. 168 * 169 * @return value of basePeer. 170 */ 171 public String getBasePeer() { 172 if (basePeer == null) { 173 return "BasePeer"; 174 } 175 return basePeer; 176 } 177 178 /** 179 * Set the value of basePeer. 180 * 181 * @param v 182 * Value to assign to basePeer. 183 */ 184 public void setBasePeer(String v) { 185 this.basePeer = v; 186 } 187 188 /** 189 * Get the value of defaultIdMethod. 190 * 191 * @return value of defaultIdMethod. 192 */ 193 public String getDefaultIdMethod() { 194 return defaultIdMethod; 195 } 196 197 /** 198 * Set the value of defaultIdMethod. 199 * 200 * @param v 201 * Value to assign to defaultIdMethod. 202 */ 203 public void setDefaultIdMethod(String v) { 204 this.defaultIdMethod = v; 205 } 206 207 /** 208 * Get type to use in Java sources (primitive || object) 209 * 210 * @return the type to use 211 */ 212 public String getDefaultJavaType() { 213 return defaultJavaType; 214 } 215 216 /** 217 * Get the value of defaultJavaNamingMethod which specifies the method for converting schema names for table and 218 * column to Java names. 219 * 220 * @return The default naming conversion used by this database. 221 */ 222 public String getDefaultJavaNamingMethod() { 223 return defaultJavaNamingMethod; 224 } 225 226 /** 227 * Set the value of defaultJavaNamingMethod. 228 * 229 * @param v 230 * The default naming conversion for this database to use. 231 */ 232 public void setDefaultJavaNamingMethod(String v) { 233 this.defaultJavaNamingMethod = v; 234 } 235 236 /** 237 * Get the value of heavyIndexing. 238 * 239 * @return value of heavyIndexing. 240 */ 241 public boolean isHeavyIndexing() { 242 return heavyIndexing; 243 } 244 245 /** 246 * Set the value of heavyIndexing. 247 * 248 * @param v 249 * Value to assign to heavyIndexing. 250 */ 251 public void setHeavyIndexing(boolean v) { 252 this.heavyIndexing = v; 253 } 254 255 /** 256 * Return an List of all tables 257 * 258 * @return List of all tables 259 */ 260 public List getTables() { 261 return tableList; 262 } 263 264 /** 265 * Return the table with the specified name. 266 * 267 * @param name 268 * table name 269 * @return A Table object. If it does not exist it returns null 270 */ 271 public Table getTable(String name) { 272 return (Table) tablesByName.get(name); 273 } 274 275 /** 276 * Return the table with the specified javaName. 277 * 278 * @param javaName 279 * name of the java object representing the table 280 * @return A Table object. If it does not exist it returns null 281 */ 282 public Table getTableByJavaName(String javaName) { 283 return (Table) tablesByJavaName.get(javaName); 284 } 285 286 /** 287 * An utility method to add a new table from an xml attribute. 288 * 289 * @param attrib 290 * the xml attributes 291 * @return the created Table 292 */ 293 public Table addTable(Attributes attrib) { 294 Table tbl = new Table(); 295 tbl.setDatabase(this); 296 tbl.loadFromXML(attrib, this.getDefaultIdMethod()); 297 addTable(tbl); 298 return tbl; 299 } 300 301 /** 302 * Add a table to the list and sets the Database property to this Database 303 * 304 * @param tbl 305 * the table to add 306 */ 307 public void addTable(Table tbl) { 308 tbl.setDatabase(this); 309 tbl.setPackage(getPackage()); 310 tableList.add(tbl); 311 List<String> tableNames = getTableNames(tbl); 312 for (String tableName : tableNames) { 313 tablesByName.put(tableName, tbl); 314 } 315 tablesByJavaName.put(tbl.getJavaName(), tbl); 316 } 317 318 /** 319 * Remove a table from this database 320 */ 321 public void removeTable(Table tbl) { 322 removeFromList(tbl); 323 List<String> tableNames = getTableNames(tbl); 324 for (String tableName : tableNames) { 325 tablesByName.remove(tableName); 326 } 327 tablesByJavaName.remove(tbl.getJavaName()); 328 } 329 330 protected List<String> getTableNames(Table table) { 331 List<String> tableNames = new ArrayList<String>(); 332 tableNames.add(table.getName()); 333 tableNames.add(table.getName().toLowerCase()); 334 tableNames.add(table.getName().toUpperCase()); 335 return tableNames; 336 } 337 338 protected void removeFromList(Table targetTable) { 339 for (int i = 0; i < tableList.size(); i++) { 340 Table table = (Table) tableList.get(i); 341 String name1 = table.getName(); 342 String name2 = targetTable.getName(); 343 if (name1.equalsIgnoreCase(name2)) { 344 tableList.remove(i); 345 return; 346 } 347 } 348 349 } 350 351 public void addDomain(Domain domain) { 352 domainMap.put(domain.getName(), domain); 353 } 354 355 public Domain getDomain(String domainName) { 356 return (Domain) domainMap.get(domainName); 357 } 358 359 protected String getDatabaseType() { 360 return databaseType; 361 } 362 363 public void setDatabaseType(String databaseType) { 364 this.databaseType = databaseType; 365 } 366 367 /** 368 * Returns the Platform implementation for this database. 369 * 370 * @return a Platform implementation 371 */ 372 public Platform getPlatform() { 373 return PlatformFactory.getPlatformFor(databaseType); 374 } 375 376 /** 377 * Determines if this database will be using the <code>IDMethod.ID_BROKER</code> to create ids for torque OM 378 * objects. 379 * 380 * @return true if there is at least one table in this database that uses the <code>IDMethod.ID_BROKER</code> method 381 * of generating ids. returns false otherwise. 382 */ 383 public boolean requiresIdTable() { 384 Iterator iter = getTables().iterator(); 385 while (iter.hasNext()) { 386 Table table = (Table) iter.next(); 387 if (table.getIdMethod().equals(IDMethod.ID_BROKER)) { 388 return true; 389 } 390 } 391 return false; 392 } 393 394 /** 395 * Initializes the model. 396 * 397 * @throws EngineException 398 */ 399 public void doFinalInitialization() throws EngineException { 400 Iterator iter = getTables().iterator(); 401 while (iter.hasNext()) { 402 Table currTable = (Table) iter.next(); 403 404 // check schema integrity 405 // if idMethod="autoincrement", make sure a column is 406 // specified as autoIncrement="true" 407 // FIXME: Handle idMethod="native" via DB adapter. 408 // TODO autoincrement is no longer supported!!! 409 if (currTable.getIdMethod().equals("autoincrement")) { 410 boolean foundOne = false; 411 Iterator colIter = currTable.getColumns().iterator(); 412 while (colIter.hasNext() && !foundOne) { 413 foundOne = ((Column) colIter.next()).isAutoIncrement(); 414 } 415 416 if (!foundOne) { 417 String errorMessage = "Table '" + currTable.getName() 418 + "' is marked as autoincrement, but it does not " 419 + "have a column which declared as the one to " 420 + "auto increment (i.e. autoIncrement=\"true\")\n"; 421 throw new EngineException("Error in XML schema: " + errorMessage); 422 } 423 } 424 425 currTable.doFinalInitialization(); 426 427 // setup reverse fk relations 428 Iterator fks = currTable.getForeignKeys().iterator(); 429 while (fks.hasNext()) { 430 ForeignKey currFK = (ForeignKey) fks.next(); 431 Table foreignTable = getTable(currFK.getForeignTableName()); 432 if (foreignTable == null) { 433 throw new EngineException("Attempt to set foreign" + " key to nonexistent table, " 434 + currFK.getForeignTableName()); 435 } else { 436 // TODO check type and size 437 List referrers = foreignTable.getReferrers(); 438 if ((referrers == null || !referrers.contains(currFK))) { 439 foreignTable.addReferrer(currFK); 440 } 441 442 // local column references 443 Iterator localColumnNames = currFK.getLocalColumns().iterator(); 444 while (localColumnNames.hasNext()) { 445 Column local = currTable.getColumn((String) localColumnNames.next()); 446 // give notice of a schema inconsistency. 447 // note we do not prevent the npe as there is nothing 448 // that we can do, if it is to occur. 449 if (local == null) { 450 throw new EngineException("Attempt to define foreign" 451 + " key with nonexistent column in table, " + currTable.getName()); 452 } else { 453 // check for foreign pk's 454 if (local.isPrimaryKey()) { 455 currTable.setContainsForeignPK(true); 456 } 457 } 458 } 459 460 // foreign column references 461 Iterator foreignColumnNames = currFK.getForeignColumns().iterator(); 462 while (foreignColumnNames.hasNext()) { 463 String foreignColumnName = (String) foreignColumnNames.next(); 464 Column foreign = foreignTable.getColumn(foreignColumnName); 465 // if the foreign column does not exist, we may have an 466 // external reference or a misspelling 467 if (foreign == null) { 468 throw new EngineException("Attempt to set foreign" + " key to nonexistent column: table=" 469 + currTable.getName() + ", foreign column=" + foreignColumnName); 470 } else { 471 foreign.addReferrer(currFK); 472 } 473 } 474 } 475 } 476 } 477 } 478 479 /** 480 * Get the base name to use when creating related Java Classes. 481 * 482 * @return A Java syntax capatible version of the dbName using the method defined by the defaultJavaNamingMethod XML 483 * value. 484 */ 485 public String getJavaName() { 486 if (javaName == null) { 487 List inputs = new ArrayList(2); 488 inputs.add(name); 489 inputs.add(defaultJavaNamingMethod); 490 try { 491 javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR, inputs); 492 } catch (EngineException e) { 493 log.error(e, e); 494 } 495 } 496 return javaName; 497 } 498 499 /** 500 * Convert dbName to a Java compatible name by the JavaName method only (ignores the defaultJavaNamingMethod). 501 * 502 * @return The current dbName converted to a standard format that can be used as part of a Java Object name. 503 */ 504 public String getStandardJavaName() { 505 if (javaName == null) { 506 List inputs = new ArrayList(2); 507 inputs.add(name); 508 inputs.add(NameGenerator.CONV_METHOD_JAVANAME); 509 try { 510 javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR, inputs); 511 } catch (EngineException e) { 512 log.error(e, e); 513 } 514 } 515 return javaName; 516 } 517 518 /** 519 * Creats a string representation of this Database. The representation is given in xml format. 520 * 521 * @return string representation in xml 522 */ 523 @Override 524 public String toString() { 525 StringBuffer result = new StringBuffer(); 526 527 result.append("<?xml version=\"1.0\"?>\n"); 528 result.append("<!DOCTYPE database SYSTEM \"" + DTDResolver.WEB_SITE_DTD + "\">\n"); 529 result.append("<!-- Autogenerated by SQLToXMLSchema! -->\n"); 530 result.append("<database name=\"").append(getName()).append('"').append(" package=\"").append(getPackage()) 531 .append('"').append(" defaultIdMethod=\"").append(getDefaultIdMethod()).append('"') 532 .append(" baseClass=\"").append(getBaseClass()).append('"').append(" basePeer=\"") 533 .append(getBasePeer()).append('"').append(">\n"); 534 535 for (Iterator i = tableList.iterator(); i.hasNext();) { 536 result.append(i.next()); 537 } 538 539 result.append("</database>"); 540 return result.toString(); 541 } 542 543 /** 544 * Add an XML Specified option key/value pair to this element's option set. 545 * 546 * @param key 547 * the key of the option. 548 * @param value 549 * the value of the option. 550 */ 551 public void addOption(String key, String value) { 552 options.put(key, value); 553 } 554 555 /** 556 * Get the value that was associated with this key in an XML option element. 557 * 558 * @param key 559 * the key of the option. 560 * @return The value for the key or a null. 561 */ 562 public String getOption(String key) { 563 return (String) options.get(key); 564 } 565 566 /** 567 * Gets the full ordered hashtable array of items specified by XML option statements under this element. 568 * <p> 569 * 570 * Note, this is not thread save but since it's only used for generation which is single threaded, there should be 571 * minimum danger using this in Velocity. 572 * 573 * @return An Map of all options. Will not be null but may be empty. 574 */ 575 public Map getOptions() { 576 return options; 577 } 578 }