KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > access > DbGenerator


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56
57 package org.objectstyle.cayenne.access;
58
59 import java.sql.Connection JavaDoc;
60 import java.sql.Driver JavaDoc;
61 import java.sql.SQLException JavaDoc;
62 import java.sql.Statement JavaDoc;
63 import java.util.ArrayList JavaDoc;
64 import java.util.Collection JavaDoc;
65 import java.util.Collections JavaDoc;
66 import java.util.HashMap JavaDoc;
67 import java.util.Iterator JavaDoc;
68 import java.util.List JavaDoc;
69 import java.util.ListIterator JavaDoc;
70 import java.util.Map JavaDoc;
71
72 import javax.sql.DataSource JavaDoc;
73
74 import org.apache.log4j.Level;
75 import org.apache.log4j.Logger;
76 import org.objectstyle.cayenne.CayenneRuntimeException;
77 import org.objectstyle.cayenne.conn.DataSourceInfo;
78 import org.objectstyle.cayenne.conn.DriverDataSource;
79 import org.objectstyle.cayenne.dba.DbAdapter;
80 import org.objectstyle.cayenne.dba.PkGenerator;
81 import org.objectstyle.cayenne.dba.TypesMapping;
82 import org.objectstyle.cayenne.map.DataMap;
83 import org.objectstyle.cayenne.map.DbAttribute;
84 import org.objectstyle.cayenne.map.DbEntity;
85 import org.objectstyle.cayenne.map.DbJoin;
86 import org.objectstyle.cayenne.map.DbRelationship;
87 import org.objectstyle.cayenne.map.DerivedDbEntity;
88 import org.objectstyle.cayenne.validation.SimpleValidationFailure;
89 import org.objectstyle.cayenne.validation.ValidationResult;
90
91 /**
92  * Utility class that generates database schema based on Cayenne mapping. It is a logical
93  * counterpart of DbLoader class.
94  *
95  * @author Andrei Adamchik
96  */

97 public class DbGenerator {
98
99     private Logger logObj = Logger.getLogger(DbGenerator.class);
100     
101     protected DbAdapter adapter;
102     protected DataMap map;
103
104     // stores generated SQL statements
105
protected Map JavaDoc dropTables;
106     protected Map JavaDoc createTables;
107     protected Map JavaDoc createFK;
108     protected List JavaDoc createPK;
109     protected List JavaDoc dropPK;
110
111     /**
112      * Contains all DbEntities ordered considering their interdependencies.
113      * DerivedDbEntities are filtered out of this list.
114      */

115     protected List JavaDoc dbEntitiesInInsertOrder;
116     protected List JavaDoc dbEntitiesRequiringAutoPK;
117
118     protected boolean shouldDropTables;
119     protected boolean shouldCreateTables;
120     protected boolean shouldDropPKSupport;
121     protected boolean shouldCreatePKSupport;
122     protected boolean shouldCreateFKConstraints;
123
124     protected ValidationResult failures;
125
126     /**
127      * Creates and initializes new DbGenerator.
128      */

129     public DbGenerator(DbAdapter adapter, DataMap map) {
130         this(adapter, map, Collections.EMPTY_LIST);
131     }
132
133     /**
134      * Creates and initializes new DbGenerator. <code>excludedEntities</code> parameter
135      * specifies which entities should be ignored during class generation.
136      */

137     public DbGenerator(DbAdapter adapter, DataMap map, Collection JavaDoc excludedEntities) {
138         // sanity check
139
if (adapter == null) {
140             throw new IllegalArgumentException JavaDoc("Adapter must not be null.");
141         }
142
143         if (map == null) {
144             throw new IllegalArgumentException JavaDoc("DataMap must not be null.");
145         }
146
147         this.map = map;
148         this.adapter = adapter;
149
150         prepareDbEntities(excludedEntities);
151         resetToDefaults();
152         buildStatements();
153     }
154
155     protected void resetToDefaults() {
156         this.shouldDropTables = false;
157         this.shouldDropPKSupport = false;
158         this.shouldCreatePKSupport = true;
159         this.shouldCreateTables = true;
160         this.shouldCreateFKConstraints = true;
161     }
162
163     /**
164      * Creates and stores internally a set of statements for database schema creation,
165      * ignoring configured schema creation preferences. Statements are NOT executed in
166      * this method.
167      */

168     protected void buildStatements() {
169         dropTables = new HashMap JavaDoc();
170         createTables = new HashMap JavaDoc();
171         createFK = new HashMap JavaDoc();
172
173         DbAdapter adapter = getAdapter();
174         Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
175         boolean supportsFK = adapter.supportsFkConstraints();
176         while (it.hasNext()) {
177             DbEntity dbe = (DbEntity) it.next();
178
179             String JavaDoc name = dbe.getName();
180
181             // build "DROP TABLE"
182
dropTables.put(name, adapter.dropTable(dbe));
183
184             // build "CREATE TABLE"
185
createTables.put(name, adapter.createTable(dbe));
186
187             // build "FK"
188
if (supportsFK) {
189                 createFK.put(name, createFkConstraintsQueries(dbe));
190             }
191         }
192
193         PkGenerator pkGenerator = adapter.getPkGenerator();
194         dropPK = pkGenerator.dropAutoPkStatements(dbEntitiesRequiringAutoPK);
195         createPK = pkGenerator.createAutoPkStatements(dbEntitiesRequiringAutoPK);
196     }
197
198     /**
199      * Returns <code>true</code> if there is nothing to be done by this generator. If
200      * <code>respectConfiguredSettings</code> is <code>true</code>, checks are done
201      * applying currently configured settings, otherwise check is done, assuming that all
202      * possible generated objects.
203      */

204     public boolean isEmpty(boolean respectConfiguredSettings) {
205         if (dbEntitiesInInsertOrder.isEmpty() && dbEntitiesRequiringAutoPK.isEmpty()) {
206             return true;
207         }
208
209         if (!respectConfiguredSettings) {
210             return false;
211         }
212
213         return !(shouldDropTables
214                 || shouldCreateTables
215                 || shouldCreateFKConstraints
216                 || shouldCreatePKSupport || shouldDropPKSupport);
217     }
218
219     /** Returns DbAdapter associated with this DbGenerator. */
220     public DbAdapter getAdapter() {
221         return adapter;
222     }
223
224     /**
225      * Returns a list of all schema statements that should be executed with the current
226      * configuration.
227      */

228     public List JavaDoc configuredStatements() {
229         List JavaDoc list = new ArrayList JavaDoc();
230
231         if (shouldDropTables) {
232             ListIterator JavaDoc it = dbEntitiesInInsertOrder
233                     .listIterator(dbEntitiesInInsertOrder.size());
234             while (it.hasPrevious()) {
235                 DbEntity ent = (DbEntity) it.previous();
236                 list.add(dropTables.get(ent.getName()));
237             }
238         }
239
240         if (shouldCreateTables) {
241             Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
242             while (it.hasNext()) {
243                 DbEntity ent = (DbEntity) it.next();
244                 list.add(createTables.get(ent.getName()));
245             }
246         }
247
248         if (shouldCreateFKConstraints && getAdapter().supportsFkConstraints()) {
249             Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
250             while (it.hasNext()) {
251                 DbEntity ent = (DbEntity) it.next();
252                 List JavaDoc fks = (List JavaDoc) createFK.get(ent.getName());
253                 list.addAll(fks);
254             }
255         }
256
257         if (shouldDropPKSupport) {
258             list.addAll(dropPK);
259         }
260
261         if (shouldCreatePKSupport) {
262             list.addAll(createPK);
263         }
264
265         return list;
266     }
267
268     /**
269      * Creates a temporary DataSource out of DataSourceInfo and invokes
270      * <code>public void runGenerator(DataSource ds)</code>.
271      */

272     public void runGenerator(DataSourceInfo dsi) throws Exception JavaDoc {
273         this.failures = null;
274
275         // do a pre-check. Maybe there is no need to run anything
276
// and therefore no need to create a connection
277
if (isEmpty(true)) {
278             return;
279         }
280
281         Driver JavaDoc driver = (Driver JavaDoc) Class.forName(dsi.getJdbcDriver()).newInstance();
282         DataSource JavaDoc dataSource = new DriverDataSource(driver, dsi.getDataSourceUrl(), dsi
283                 .getUserName(), dsi.getPassword());
284
285         runGenerator(dataSource);
286     }
287
288     /**
289      * Executes a set of commands to drop/create database objects. This is the main worker
290      * method of DbGenerator. Command set is built based on pre-configured generator
291      * settings.
292      */

293     public void runGenerator(DataSource JavaDoc ds) throws Exception JavaDoc {
294         this.failures = null;
295
296         Connection JavaDoc connection = ds.getConnection();
297
298         try {
299
300             // drop tables
301
if (shouldDropTables) {
302                 ListIterator JavaDoc it = dbEntitiesInInsertOrder
303                         .listIterator(dbEntitiesInInsertOrder.size());
304                 while (it.hasPrevious()) {
305                     DbEntity ent = (DbEntity) it.previous();
306                     safeExecute(connection, (String JavaDoc) dropTables.get(ent.getName()));
307                 }
308             }
309
310             // create tables
311
List JavaDoc createdTables = new ArrayList JavaDoc();
312             if (shouldCreateTables) {
313                 Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
314                 while (it.hasNext()) {
315                     DbEntity ent = (DbEntity) it.next();
316
317                     // only create missing tables
318

319                     safeExecute(connection, (String JavaDoc) createTables.get(ent.getName()));
320                     createdTables.add(ent.getName());
321                 }
322             }
323
324             // create FK
325
if (shouldCreateTables
326                     && shouldCreateFKConstraints
327                     && getAdapter().supportsFkConstraints()) {
328                 Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
329                 while (it.hasNext()) {
330                     DbEntity ent = (DbEntity) it.next();
331
332                     if (createdTables.contains(ent.getName())) {
333                         List JavaDoc fks = (List JavaDoc) createFK.get(ent.getName());
334                         Iterator JavaDoc fkIt = fks.iterator();
335                         while (fkIt.hasNext()) {
336                             safeExecute(connection, (String JavaDoc) fkIt.next());
337                         }
338                     }
339                 }
340             }
341             
342             // drop PK
343
if (shouldDropPKSupport) {
344                 List JavaDoc dropAutoPKSQL = getAdapter().getPkGenerator().dropAutoPkStatements(
345                         dbEntitiesRequiringAutoPK);
346                 Iterator JavaDoc it = dropAutoPKSQL.iterator();
347                 while (it.hasNext()) {
348                     safeExecute(connection, (String JavaDoc) it.next());
349                 }
350             }
351
352             // create pk
353
if (shouldCreatePKSupport) {
354                 List JavaDoc createAutoPKSQL = getAdapter()
355                         .getPkGenerator()
356                         .createAutoPkStatements(dbEntitiesRequiringAutoPK);
357                 Iterator JavaDoc it = createAutoPKSQL.iterator();
358                 while (it.hasNext()) {
359                     safeExecute(connection, (String JavaDoc) it.next());
360                 }
361             }
362         }
363         finally {
364             connection.close();
365         }
366     }
367
368     /**
369      * Builds and executes a SQL statement, catching and storing SQL exceptions resulting
370      * from invalid SQL. Only non-recoverable exceptions are rethrown.
371      *
372      * @since 1.1
373      */

374     protected boolean safeExecute(Connection JavaDoc connection, String JavaDoc sql) throws SQLException JavaDoc {
375         Statement JavaDoc statement = connection.createStatement();
376
377         try {
378             QueryLogger.logQuery(Level.INFO, sql, null);
379             statement.execute(sql);
380             return true;
381         }
382         catch (SQLException JavaDoc ex) {
383             if (this.failures == null) {
384                 this.failures = new ValidationResult();
385             }
386
387             failures.addFailure(new SimpleValidationFailure(sql, ex.getMessage()));
388             QueryLogger.logQueryError(Level.INFO, ex);
389             return false;
390         }
391         finally {
392             statement.close();
393         }
394     }
395
396     /**
397      * Returns an array of queries to create foreign key constraints for a particular
398      * DbEntity. Throws CayenneRuntimeException, if called for adapter that does not
399      * support FK constraints.
400      */

401     public List JavaDoc createFkConstraintsQueries(DbEntity dbEnt) {
402         if (!getAdapter().supportsFkConstraints()) {
403             throw new CayenneRuntimeException(
404                     "FK constraints are not supported by adapter.");
405         }
406
407         List JavaDoc list = new ArrayList JavaDoc();
408         Iterator JavaDoc it = dbEnt.getRelationships().iterator();
409         while (it.hasNext()) {
410             DbRelationship rel = (DbRelationship) it.next();
411
412             if (rel.isToMany()) {
413                 continue;
414             }
415
416             // create an FK CONSTRAINT only if the relationship is to PK
417
// and if this is not a dependent PK
418

419             // create UNIQUE CONSTRAINT on FK if reverse relationship is to-one
420

421             if (rel.isToPK() && !rel.isToDependentPK()) {
422
423                 if (getAdapter().supportsUniqueConstraints()) {
424
425                     DbRelationship reverse = rel.getReverseRelationship();
426                     if (reverse != null && !reverse.isToMany() && !reverse.isToPK()) {
427                         list.add(getAdapter().createUniqueConstraint(
428                                 (DbEntity) rel.getSourceEntity(),
429                                 rel.getSourceAttributes()));
430                     }
431                 }
432
433                 list.add(getAdapter().createFkConstraint(rel));
434             }
435         }
436         return list;
437     }
438
439     /**
440      * Returns an object representing a collection of failures that occurred on the last
441      * "runGenerator" invocation, or null if there were no failures. Failures usually
442      * indicate problems with generated DDL (such as "create...", "drop...", etc.) and
443      * usually happen due to the DataMap being out of sync with the database.
444      *
445      * @since 1.1
446      */

447     public ValidationResult getFailures() {
448         return failures;
449     }
450
451     /**
452      * Returns whether DbGenerator is configured to create primary key support for DataMap
453      * entities.
454      */

455     public boolean shouldCreatePKSupport() {
456         return shouldCreatePKSupport;
457     }
458
459     /**
460      * Returns whether DbGenerator is configured to create tables for DataMap entities.
461      */

462     public boolean shouldCreateTables() {
463         return shouldCreateTables;
464     }
465
466     public boolean shouldDropPKSupport() {
467         return shouldDropPKSupport;
468     }
469
470     public boolean shouldDropTables() {
471         return shouldDropTables;
472     }
473
474     public boolean shouldCreateFKConstraints() {
475         return shouldCreateFKConstraints;
476     }
477
478     public void setShouldCreatePKSupport(boolean shouldCreatePKSupport) {
479         this.shouldCreatePKSupport = shouldCreatePKSupport;
480     }
481
482     public void setShouldCreateTables(boolean shouldCreateTables) {
483         this.shouldCreateTables = shouldCreateTables;
484     }
485
486     public void setShouldDropPKSupport(boolean shouldDropPKSupport) {
487         this.shouldDropPKSupport = shouldDropPKSupport;
488     }
489
490     public void setShouldDropTables(boolean shouldDropTables) {
491         this.shouldDropTables = shouldDropTables;
492     }
493
494     public void setShouldCreateFKConstraints(boolean shouldCreateFKConstraints) {
495         this.shouldCreateFKConstraints = shouldCreateFKConstraints;
496     }
497
498     /**
499      * Helper method that orders DbEntities to satisfy referential constraints and returns
500      * an ordered list. It also filters out DerivedDbEntities.
501      */

502     private void prepareDbEntities(Collection JavaDoc excludedEntities) {
503         if (excludedEntities == null) {
504             excludedEntities = Collections.EMPTY_LIST;
505         }
506
507         // remove derived db entities
508
List JavaDoc tables = new ArrayList JavaDoc();
509         List JavaDoc tablesWithAutoPk = new ArrayList JavaDoc();
510         Iterator JavaDoc it = map.getDbEntities().iterator();
511         while (it.hasNext()) {
512             DbEntity nextEntity = (DbEntity) it.next();
513
514             // do sanity checks...
515

516             // derived DbEntities are not included in generated SQL
517
if (nextEntity instanceof DerivedDbEntity) {
518                 continue;
519             }
520
521             // tables with no columns are not included
522
if (nextEntity.getAttributes().size() == 0) {
523                 logObj
524                         .info("Skipping entity with no attributes: "
525                                 + nextEntity.getName());
526                 continue;
527             }
528
529             // check if this entity is explicitly excluded
530
if (excludedEntities.contains(nextEntity)) {
531                 continue;
532             }
533
534             // tables with invalid DbAttributes are not included
535
boolean invalidAttributes = false;
536             Iterator JavaDoc nextDbAtributes = nextEntity.getAttributes().iterator();
537             while (nextDbAtributes.hasNext()) {
538                 DbAttribute attr = (DbAttribute) nextDbAtributes.next();
539                 if (attr.getType() == TypesMapping.NOT_DEFINED) {
540                     logObj.info("Skipping entity, attribute type is undefined: "
541                             + nextEntity.getName()
542                             + "."
543                             + attr.getName());
544                     invalidAttributes = true;
545                     break;
546                 }
547             }
548             if (invalidAttributes) {
549                 continue;
550             }
551
552             tables.add(nextEntity);
553
554             // check if an automatic PK generation can be potentailly supported
555
// in this entity. For now simply check that the key is not propagated
556
Iterator JavaDoc relationships = nextEntity.getRelationships().iterator();
557
558             // create a copy of the original PK list,
559
// since the list will be modified locally
560
List JavaDoc pkAttributes = new ArrayList JavaDoc(nextEntity.getPrimaryKey());
561             while (pkAttributes.size() > 0 && relationships.hasNext()) {
562                 DbRelationship nextRelationship = (DbRelationship) relationships.next();
563                 if (!nextRelationship.isToMasterPK()) {
564                     continue;
565                 }
566
567                 // supposedly all source attributes of the relationship
568
// to master entity must be a part of primary key,
569
// so
570
Iterator JavaDoc joins = nextRelationship.getJoins().iterator();
571                 while (joins.hasNext()) {
572                     DbJoin join = (DbJoin) joins.next();
573                     pkAttributes.remove(join.getSource());
574                 }
575             }
576
577             // primary key is needed only if at least one of the primary key attributes
578
// is not propagated via releationship
579
if (pkAttributes.size() > 0) {
580                 tablesWithAutoPk.add(nextEntity);
581             }
582         }
583
584         // sort table list
585
if (tables.size() > 1) {
586             DataNode node = new DataNode("temp");
587             node.addDataMap(map);
588             node.getEntitySorter().sortDbEntities(tables, false);
589         }
590
591         this.dbEntitiesInInsertOrder = tables;
592         this.dbEntitiesRequiringAutoPK = tablesWithAutoPk;
593     }
594 }
Popular Tags