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    }