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 }