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