View Javadoc

1   package org.apache.torque.engine.database.model;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Hashtable;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.collections.map.ListOrderedMap;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.torque.engine.EngineException;
34  import org.apache.torque.engine.database.transform.DTDResolver;
35  import org.apache.torque.engine.platform.Platform;
36  import org.apache.torque.engine.platform.PlatformFactory;
37  import org.xml.sax.Attributes;
38  
39  
40  /***
41   * A class for holding application data structures.
42   *
43   * @author <a href="mailto:leon@opticode.co.za>Leon Messerschmidt</a>
44   * @author <a href="mailto:jmcnally@collab.net>John McNally</a>
45   * @author <a href="mailto:mpoeschl@marmot.at>Martin Poeschl</a>
46   * @author <a href="mailto:dlr@collab.net>Daniel Rall</a>
47   * @author <a href="mailto:byron_foster@byron_foster@yahoo.com>Byron Foster</a>
48   * @author <a href="mailto:monroe@dukece.com>Greg Monroe</a>
49   * @version $Id: Database.java 473814 2006-11-11 22:30:30Z tv $
50   */
51  public class Database
52  {
53      /*** Logging class from commons.logging */
54      private static Log log = LogFactory.getLog(Database.class);
55  
56      private String databaseType = null;
57      private List tableList = new ArrayList(100);
58      private Map domainMap = new HashMap();
59      private String name;
60      private String javaName;
61      private String pkg;
62      private String baseClass;
63      private String basePeer;
64      private String defaultIdMethod;
65      private String defaultJavaType;
66      private String defaultJavaNamingMethod;
67      private Hashtable tablesByName = new Hashtable();
68      private Hashtable tablesByJavaName = new Hashtable();
69      private boolean heavyIndexing;
70      /*** the name of the definition file */
71      private String fileName;
72      private Map options = Collections.synchronizedMap(new ListOrderedMap());
73  
74  
75      /***
76       * Creates a new instance for the specified database type.
77       *
78       * @param databaseType The default type for this database.
79       */
80      public Database(String databaseType)
81      {
82          this.databaseType = databaseType;
83      }
84  
85      /***
86       * Load the database object from an xml tag.
87       *
88       * @param attrib the xml attributes
89       */
90      public void loadFromXML(Attributes attrib)
91      {
92          setName(attrib.getValue("name"));
93          pkg = attrib.getValue("package");
94          baseClass = attrib.getValue("baseClass");
95          basePeer = attrib.getValue("basePeer");
96          defaultJavaType = attrib.getValue("defaultJavaType");
97          defaultIdMethod = attrib.getValue("defaultIdMethod");
98          defaultJavaNamingMethod = attrib.getValue("defaultJavaNamingMethod");
99          if (defaultJavaNamingMethod == null)
100         {
101             defaultJavaNamingMethod = NameGenerator.CONV_METHOD_UNDERSCORE;
102         }
103         heavyIndexing = "true".equals(attrib.getValue("heavyIndexing"));
104     }
105 
106     /***
107      * Get the name of the Database
108      *
109      * @return name of the Database
110      */
111     public String getName()
112     {
113         return name;
114     }
115 
116     /***
117      * Set the name of the Database
118      *
119      * @param name name of the Database
120      */
121     public void setName(String name)
122     {
123         /*** @task check this */
124 //        this.name = (name == null ? Torque.getDefaultDB() : name);
125         this.name = (name == null ? "default" : name);
126     }
127 
128     public String getFileName()
129     {
130         return fileName;
131     }
132 
133     public void setFileName(String name)
134     {
135         this.fileName = name;
136     }
137 
138     /***
139      * Get the value of package.
140      * @return value of package.
141      */
142     public String getPackage()
143     {
144         return pkg;
145     }
146 
147     /***
148      * Set the value of package.
149      * @param v  Value to assign to package.
150      */
151     public void setPackage(String v)
152     {
153         this.pkg = v;
154     }
155 
156     /***
157      * Get the value of baseClass.
158      * @return value of baseClass.
159      */
160     public String getBaseClass()
161     {
162         if (baseClass == null)
163         {
164             return "BaseObject";
165         }
166         return baseClass;
167     }
168 
169     /***
170      * Set the value of baseClass.
171      * @param v  Value to assign to baseClass.
172      */
173     public void setBaseClass(String v)
174     {
175         this.baseClass = v;
176     }
177 
178     /***
179      * Get the value of basePeer.
180      * @return value of basePeer.
181      */
182     public String getBasePeer()
183     {
184         if (basePeer == null)
185         {
186             return "BasePeer";
187         }
188         return basePeer;
189     }
190 
191     /***
192      * Set the value of basePeer.
193      * @param v Value to assign to basePeer.
194      */
195     public void setBasePeer(String v)
196     {
197         this.basePeer = v;
198     }
199 
200     /***
201      * Get the value of defaultIdMethod.
202      * @return value of defaultIdMethod.
203      */
204     public String getDefaultIdMethod()
205     {
206         return defaultIdMethod;
207     }
208 
209     /***
210      * Set the value of defaultIdMethod.
211      * @param v Value to assign to defaultIdMethod.
212      */
213     public void setDefaultIdMethod(String v)
214     {
215         this.defaultIdMethod = v;
216     }
217 
218     /***
219      * Get type to use in Java sources (primitive || object)
220      *
221      * @return the type to use
222      */
223     public String getDefaultJavaType()
224     {
225         return defaultJavaType;
226     }
227 
228     /***
229      * Get the value of defaultJavaNamingMethod which specifies the
230      * method for converting schema names for table and column to Java names.
231      *
232      * @return The default naming conversion used by this database.
233      */
234     public String getDefaultJavaNamingMethod()
235     {
236         return defaultJavaNamingMethod;
237     }
238 
239     /***
240      * Set the value of defaultJavaNamingMethod.
241      * @param v The default naming conversion for this database to use.
242      */
243     public void setDefaultJavaNamingMethod(String v)
244     {
245         this.defaultJavaNamingMethod = v;
246     }
247 
248     /***
249      * Get the value of heavyIndexing.
250      * @return value of heavyIndexing.
251      */
252     public boolean isHeavyIndexing()
253     {
254         return heavyIndexing;
255     }
256 
257     /***
258      * Set the value of heavyIndexing.
259      * @param v  Value to assign to heavyIndexing.
260      */
261     public void setHeavyIndexing(boolean v)
262     {
263         this.heavyIndexing = v;
264     }
265 
266     /***
267      * Return an List of all tables
268      *
269      * @return List of all tables
270      */
271     public List getTables()
272     {
273         return tableList;
274     }
275 
276     /***
277      * Return the table with the specified name.
278      *
279      * @param name table name
280      * @return A Table object.  If it does not exist it returns null
281      */
282     public Table getTable(String name)
283     {
284         return (Table) tablesByName.get(name);
285     }
286 
287     /***
288      * Return the table with the specified javaName.
289      *
290      * @param javaName name of the java object representing the table
291      * @return A Table object.  If it does not exist it returns null
292      */
293     public Table getTableByJavaName(String javaName)
294     {
295         return (Table) tablesByJavaName.get(javaName);
296     }
297 
298     /***
299      * An utility method to add a new table from an xml attribute.
300      *
301      * @param attrib the xml attributes
302      * @return the created Table
303      */
304     public Table addTable(Attributes attrib)
305     {
306         Table tbl = new Table();
307         tbl.setDatabase(this);
308         tbl.loadFromXML(attrib, this.getDefaultIdMethod());
309         addTable(tbl);
310         return tbl;
311     }
312 
313     /***
314      * Add a table to the list and sets the Database property to this Database
315      *
316      * @param tbl the table to add
317      */
318     public void addTable(Table tbl)
319     {
320         tbl.setDatabase(this);
321         tableList.add(tbl);
322         tablesByName.put(tbl.getName(), tbl);
323         tablesByJavaName.put(tbl.getJavaName(), tbl);
324         tbl.setPackage(getPackage());
325     }
326 
327     public void addDomain(Domain domain)
328     {
329         domainMap.put(domain.getName(), domain);
330     }
331 
332     public Domain getDomain(String domainName)
333     {
334         return (Domain) domainMap.get(domainName);
335     }
336 
337     protected String getDatabaseType()
338     {
339         return databaseType;
340     }
341 
342     public void setDatabaseType(String databaseType)
343     {
344         this.databaseType = databaseType;
345     }
346 
347     /***
348      * Returns the Platform implementation for this database.
349      *
350      * @return a Platform implementation
351      */
352     public Platform getPlatform()
353     {
354         return PlatformFactory.getPlatformFor(databaseType);
355     }
356 
357     /***
358      * Determines if this database will be using the
359      * <code>IDMethod.ID_BROKER</code> to create ids for torque OM
360      * objects.
361      * @return true if there is at least one table in this database that
362      * uses the <code>IDMethod.ID_BROKER</code> method of generating
363      * ids. returns false otherwise.
364      */
365     public boolean requiresIdTable()
366     {
367         Iterator iter = getTables().iterator();
368         while (iter.hasNext())
369         {
370             Table table = (Table) iter.next();
371             if (table.getIdMethod().equals(IDMethod.ID_BROKER))
372             {
373                 return true;
374             }
375         }
376         return false;
377     }
378 
379     /***
380      * Initializes the model.
381      *
382      * @throws EngineException
383      */
384     public void doFinalInitialization() throws EngineException
385     {
386         Iterator iter = getTables().iterator();
387         while (iter.hasNext())
388         {
389             Table currTable = (Table) iter.next();
390 
391             // check schema integrity
392             // if idMethod="autoincrement", make sure a column is
393             // specified as autoIncrement="true"
394             // FIXME: Handle idMethod="native" via DB adapter.
395             // TODO autoincrement is no longer supported!!!
396             if (currTable.getIdMethod().equals("autoincrement"))
397             {
398                 boolean foundOne = false;
399                 Iterator colIter = currTable.getColumns().iterator();
400                 while (colIter.hasNext() && !foundOne)
401                 {
402                     foundOne = ((Column) colIter.next()).isAutoIncrement();
403                 }
404 
405                 if (!foundOne)
406                 {
407                     String errorMessage = "Table '" + currTable.getName()
408                             + "' is marked as autoincrement, but it does not "
409                             + "have a column which declared as the one to "
410                             + "auto increment (i.e. autoIncrement=\"true\")\n";
411                     throw new EngineException("Error in XML schema: " + errorMessage);
412                 }
413             }
414 
415             currTable.doFinalInitialization();
416 
417             // setup reverse fk relations
418             Iterator fks = currTable.getForeignKeys().iterator();
419             while (fks.hasNext())
420             {
421                 ForeignKey currFK = (ForeignKey) fks.next();
422                 Table foreignTable = getTable(currFK.getForeignTableName());
423                 if (foreignTable == null)
424                 {
425                     throw new EngineException("Attempt to set foreign"
426                             + " key to nonexistent table, "
427                             + currFK.getForeignTableName());
428                 }
429                 else
430                 {
431                     // TODO check type and size
432                     List referrers = foreignTable.getReferrers();
433                     if ((referrers == null || !referrers.contains(currFK)))
434                     {
435                         foreignTable.addReferrer(currFK);
436                     }
437 
438                     // local column references
439                     Iterator localColumnNames = currFK.getLocalColumns().iterator();
440                     while (localColumnNames.hasNext())
441                     {
442                         Column local = currTable
443                                 .getColumn((String) localColumnNames.next());
444                         // give notice of a schema inconsistency.
445                         // note we do not prevent the npe as there is nothing
446                         // that we can do, if it is to occur.
447                         if (local == null)
448                         {
449                             throw new EngineException("Attempt to define foreign"
450                                     + " key with nonexistent column in table, "
451                                     + currTable.getName());
452                         }
453                         else
454                         {
455                             //check for foreign pk's
456                             if (local.isPrimaryKey())
457                             {
458                                 currTable.setContainsForeignPK(true);
459                             }
460                         }
461                     }
462 
463                     // foreign column references
464                     Iterator foreignColumnNames
465                             = currFK.getForeignColumns().iterator();
466                     while (foreignColumnNames.hasNext())
467                     {
468                         String foreignColumnName = (String) foreignColumnNames.next();
469                         Column foreign = foreignTable.getColumn(foreignColumnName);
470                         // if the foreign column does not exist, we may have an
471                         // external reference or a misspelling
472                         if (foreign == null)
473                         {
474                             throw new EngineException("Attempt to set foreign"
475                                     + " key to nonexistent column: table="
476                                     +  currTable.getName() + ", foreign column="
477                                     +  foreignColumnName);
478                         }
479                         else
480                         {
481                             foreign.addReferrer(currFK);
482                         }
483                     }
484                 }
485             }
486         }
487     }
488 
489     /***
490      * Get the base name to use when creating related Java Classes.
491      *
492      * @return A Java syntax capatible version of the dbName using the method
493      *         defined by the defaultJavaNamingMethod XML value.
494      */
495     public String getJavaName()
496     {
497         if (javaName == null)
498         {
499             List inputs = new ArrayList(2);
500             inputs.add(name);
501             inputs.add(defaultJavaNamingMethod);
502             try
503             {
504                 javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR,
505                                                     inputs);
506             }
507             catch (EngineException e)
508             {
509                 log.error(e, e);
510             }
511         }
512         return javaName;
513     }
514 
515     /***
516      * Convert dbName to a Java compatible name by the JavaName method only
517      * (ignores the defaultJavaNamingMethod).
518      *
519      * @return The current dbName converted to a standard format that can
520      *          be used as part of a Java Object name.
521      */
522     public String getStandardJavaName()
523     {
524         if (javaName == null)
525         {
526             List inputs = new ArrayList(2);
527             inputs.add(name);
528             inputs.add(NameGenerator.CONV_METHOD_JAVANAME);
529             try
530             {
531                 javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR,
532                                                     inputs);
533             }
534             catch (EngineException e)
535             {
536                 log.error(e, e);
537             }
538         }
539         return javaName;
540     }
541 
542     /***
543      * Creats a string representation of this Database.
544      * The representation is given in xml format.
545      *
546      * @return string representation in xml
547      */
548     public String toString()
549     {
550         StringBuffer result = new StringBuffer();
551 
552         result.append ("<?xml version=\"1.0\"?>\n");
553         result.append ("<!DOCTYPE database SYSTEM \""
554                 + DTDResolver.WEB_SITE_DTD + "\">\n");
555         result.append("<!-- Autogenerated by SQLToXMLSchema! -->\n");
556         result.append("<database name=\"").append(getName()).append('"')
557             .append(" package=\"").append(getPackage()).append('"')
558             .append(" defaultIdMethod=\"").append(getDefaultIdMethod())
559             .append('"')
560             .append(" baseClass=\"").append(getBaseClass()).append('"')
561             .append(" basePeer=\"").append(getBasePeer()).append('"')
562             .append(">\n");
563 
564         for (Iterator i = tableList.iterator(); i.hasNext();)
565         {
566             result.append(i.next());
567         }
568 
569         result.append("</database>");
570         return result.toString();
571     }
572 
573     /***
574      * Add an XML Specified option key/value pair to this element's option set.
575      *
576      * @param key the key of the option.
577      * @param value the value of the option.
578      */
579     public void addOption(String key, String value)
580     {
581         options.put(key, value);
582     }
583 
584     /***
585      * Get the value that was associated with this key in an XML option
586      * element.
587      *
588      * @param key the key of the option.
589      * @return The value for the key or a null.
590      */
591     public String getOption(String key)
592     {
593         return (String) options.get(key);
594     }
595 
596     /***
597      * Gets the full ordered hashtable array of items specified by XML option
598      * statements under this element.<p>
599      *
600      * Note, this is not thread save but since it's only used for
601      * generation which is single threaded, there should be minimum
602      * danger using this in Velocity.
603      *
604      * @return An Map of all options. Will not be null but may be empty.
605      */
606     public Map getOptions()
607     {
608         return options;
609     }
610 }