KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*****************************************************************
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  ****************************************************************/

19
20 package org.apache.cayenne.access;
21
22 import java.sql.Connection JavaDoc;
23 import java.sql.Driver JavaDoc;
24 import java.sql.SQLException JavaDoc;
25 import java.sql.Statement JavaDoc;
26 import java.util.ArrayList JavaDoc;
27 import java.util.Collection JavaDoc;
28 import java.util.Collections JavaDoc;
29 import java.util.HashMap JavaDoc;
30 import java.util.Iterator JavaDoc;
31 import java.util.List JavaDoc;
32 import java.util.ListIterator JavaDoc;
33 import java.util.Map JavaDoc;
34
35 import javax.sql.DataSource JavaDoc;
36
37 import org.apache.cayenne.conn.DataSourceInfo;
38 import org.apache.cayenne.conn.DriverDataSource;
39 import org.apache.cayenne.dba.DbAdapter;
40 import org.apache.cayenne.dba.PkGenerator;
41 import org.apache.cayenne.dba.TypesMapping;
42 import org.apache.cayenne.map.DataMap;
43 import org.apache.cayenne.map.DbAttribute;
44 import org.apache.cayenne.map.DbEntity;
45 import org.apache.cayenne.map.DbJoin;
46 import org.apache.cayenne.map.DbRelationship;
47 import org.apache.cayenne.map.DerivedDbEntity;
48 import org.apache.cayenne.validation.SimpleValidationFailure;
49 import org.apache.cayenne.validation.ValidationResult;
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52
53 /**
54  * Utility class that generates database schema based on Cayenne mapping. It is a logical
55  * counterpart of DbLoader class.
56  *
57  * @author Andrus Adamchik
58  */

59 public class DbGenerator {
60
61     private Log logObj = LogFactory.getLog(DbGenerator.class);
62
63     protected DbAdapter adapter;
64     protected DataMap map;
65
66     // optional DataDomain needed for correct FK generation in cross-db situations
67
protected DataDomain domain;
68
69     // stores generated SQL statements
70
protected Map JavaDoc dropTables;
71     protected Map JavaDoc createTables;
72     protected Map JavaDoc createConstraints;
73     protected List JavaDoc createPK;
74     protected List JavaDoc dropPK;
75
76     /**
77      * Contains all DbEntities ordered considering their interdependencies.
78      * DerivedDbEntities are filtered out of this list.
79      */

80     protected List JavaDoc dbEntitiesInInsertOrder;
81     protected List JavaDoc dbEntitiesRequiringAutoPK;
82
83     protected boolean shouldDropTables;
84     protected boolean shouldCreateTables;
85     protected boolean shouldDropPKSupport;
86     protected boolean shouldCreatePKSupport;
87     protected boolean shouldCreateFKConstraints;
88
89     protected ValidationResult failures;
90
91     /**
92      * Creates and initializes new DbGenerator.
93      */

94     public DbGenerator(DbAdapter adapter, DataMap map) {
95         this(adapter, map, Collections.EMPTY_LIST);
96     }
97
98     /**
99      * Creates and initializes new DbGenerator instance.
100      *
101      * @param adapter DbAdapter corresponding to the database
102      * @param map DataMap whose entities will be used in schema generation
103      * @param excludedEntities entities that should be ignored during schema generation
104      */

105     public DbGenerator(DbAdapter adapter, DataMap map, Collection JavaDoc excludedEntities) {
106         this(adapter, map, excludedEntities, null);
107     }
108
109     /**
110      * Creates and initializes new DbGenerator instance.
111      *
112      * @param adapter DbAdapter corresponding to the database
113      * @param map DataMap whose entities will be used in schema generation
114      * @param excludedEntities entities that should be ignored during schema generation
115      * @param domain optional DataDomain used to detect cross-database relationships.
116      * @since 1.2
117      */

118     public DbGenerator(DbAdapter adapter, DataMap map, Collection JavaDoc excludedEntities,
119             DataDomain domain) {
120         // sanity check
121
if (adapter == null) {
122             throw new IllegalArgumentException JavaDoc("Adapter must not be null.");
123         }
124
125         if (map == null) {
126             throw new IllegalArgumentException JavaDoc("DataMap must not be null.");
127         }
128
129         this.domain = domain;
130         this.map = map;
131         this.adapter = adapter;
132
133         prepareDbEntities(excludedEntities);
134         resetToDefaults();
135         buildStatements();
136     }
137
138     protected void resetToDefaults() {
139         this.shouldDropTables = false;
140         this.shouldDropPKSupport = false;
141         this.shouldCreatePKSupport = true;
142         this.shouldCreateTables = true;
143         this.shouldCreateFKConstraints = true;
144     }
145
146     /**
147      * Creates and stores internally a set of statements for database schema creation,
148      * ignoring configured schema creation preferences. Statements are NOT executed in
149      * this method.
150      */

151     protected void buildStatements() {
152         dropTables = new HashMap JavaDoc();
153         createTables = new HashMap JavaDoc();
154         createConstraints = new HashMap JavaDoc();
155
156         DbAdapter adapter = getAdapter();
157         Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
158         while (it.hasNext()) {
159             DbEntity dbe = (DbEntity) it.next();
160
161             String JavaDoc name = dbe.getName();
162
163             // build "DROP TABLE"
164
dropTables.put(name, adapter.dropTable(dbe));
165
166             // build "CREATE TABLE"
167
createTables.put(name, adapter.createTable(dbe));
168
169             // build constraints
170
createConstraints.put(name, createConstraintsQueries(dbe));
171         }
172
173         PkGenerator pkGenerator = adapter.getPkGenerator();
174         dropPK = pkGenerator.dropAutoPkStatements(dbEntitiesRequiringAutoPK);
175         createPK = pkGenerator.createAutoPkStatements(dbEntitiesRequiringAutoPK);
176     }
177
178     /**
179      * Returns <code>true</code> if there is nothing to be done by this generator. If
180      * <code>respectConfiguredSettings</code> is <code>true</code>, checks are done
181      * applying currently configured settings, otherwise check is done, assuming that all
182      * possible generated objects.
183      */

184     public boolean isEmpty(boolean respectConfiguredSettings) {
185         if (dbEntitiesInInsertOrder.isEmpty() && dbEntitiesRequiringAutoPK.isEmpty()) {
186             return true;
187         }
188
189         if (!respectConfiguredSettings) {
190             return false;
191         }
192
193         return !(shouldDropTables
194                 || shouldCreateTables
195                 || shouldCreateFKConstraints
196                 || shouldCreatePKSupport || shouldDropPKSupport);
197     }
198
199     /** Returns DbAdapter associated with this DbGenerator. */
200     public DbAdapter getAdapter() {
201         return adapter;
202     }
203
204     /**
205      * Returns a list of all schema statements that should be executed with the current
206      * configuration.
207      */

208     public List JavaDoc configuredStatements() {
209         List JavaDoc list = new ArrayList JavaDoc();
210
211         if (shouldDropTables) {
212             ListIterator JavaDoc it = dbEntitiesInInsertOrder
213                     .listIterator(dbEntitiesInInsertOrder.size());
214             while (it.hasPrevious()) {
215                 DbEntity ent = (DbEntity) it.previous();
216                 list.add(dropTables.get(ent.getName()));
217             }
218         }
219
220         if (shouldCreateTables) {
221             Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
222             while (it.hasNext()) {
223                 DbEntity ent = (DbEntity) it.next();
224                 list.add(createTables.get(ent.getName()));
225             }
226         }
227
228         if (shouldCreateFKConstraints) {
229             Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
230             while (it.hasNext()) {
231                 DbEntity ent = (DbEntity) it.next();
232                 List JavaDoc fks = (List JavaDoc) createConstraints.get(ent.getName());
233                 list.addAll(fks);
234             }
235         }
236
237         if (shouldDropPKSupport) {
238             list.addAll(dropPK);
239         }
240
241         if (shouldCreatePKSupport) {
242             list.addAll(createPK);
243         }
244
245         return list;
246     }
247
248     /**
249      * Creates a temporary DataSource out of DataSourceInfo and invokes
250      * <code>public void runGenerator(DataSource ds)</code>.
251      */

252     public void runGenerator(DataSourceInfo dsi) throws Exception JavaDoc {
253         this.failures = null;
254
255         // do a pre-check. Maybe there is no need to run anything
256
// and therefore no need to create a connection
257
if (isEmpty(true)) {
258             return;
259         }
260
261         Driver JavaDoc driver = (Driver JavaDoc) Class.forName(dsi.getJdbcDriver()).newInstance();
262         DataSource JavaDoc dataSource = new DriverDataSource(driver, dsi.getDataSourceUrl(), dsi
263                 .getUserName(), dsi.getPassword());
264
265         runGenerator(dataSource);
266     }
267
268     /**
269      * Executes a set of commands to drop/create database objects. This is the main worker
270      * method of DbGenerator. Command set is built based on pre-configured generator
271      * settings.
272      */

273     public void runGenerator(DataSource JavaDoc ds) throws Exception JavaDoc {
274         this.failures = null;
275
276         Connection JavaDoc connection = ds.getConnection();
277
278         try {
279
280             // drop tables
281
if (shouldDropTables) {
282                 ListIterator JavaDoc it = dbEntitiesInInsertOrder
283                         .listIterator(dbEntitiesInInsertOrder.size());
284                 while (it.hasPrevious()) {
285                     DbEntity ent = (DbEntity) it.previous();
286                     safeExecute(connection, (String JavaDoc) dropTables.get(ent.getName()));
287                 }
288             }
289
290             // create tables
291
List JavaDoc createdTables = new ArrayList JavaDoc();
292             if (shouldCreateTables) {
293                 Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
294                 while (it.hasNext()) {
295                     DbEntity ent = (DbEntity) it.next();
296
297                     // only create missing tables
298

299                     safeExecute(connection, (String JavaDoc) createTables.get(ent.getName()));
300                     createdTables.add(ent.getName());
301                 }
302             }
303
304             // create FK
305
if (shouldCreateTables && shouldCreateFKConstraints) {
306                 Iterator JavaDoc it = dbEntitiesInInsertOrder.iterator();
307                 while (it.hasNext()) {
308                     DbEntity ent = (DbEntity) it.next();
309
310                     if (createdTables.contains(ent.getName())) {
311                         List JavaDoc fks = (List JavaDoc) createConstraints.get(ent.getName());
312                         Iterator JavaDoc fkIt = fks.iterator();
313                         while (fkIt.hasNext()) {
314                             safeExecute(connection, (String JavaDoc) fkIt.next());
315                         }
316                     }
317                 }
318             }
319
320             // drop PK
321
if (shouldDropPKSupport) {
322                 List JavaDoc dropAutoPKSQL = getAdapter().getPkGenerator().dropAutoPkStatements(
323                         dbEntitiesRequiringAutoPK);
324                 Iterator JavaDoc it = dropAutoPKSQL.iterator();
325                 while (it.hasNext()) {
326                     safeExecute(connection, (String JavaDoc) it.next());
327                 }
328             }
329
330             // create pk
331
if (shouldCreatePKSupport) {
332                 List JavaDoc createAutoPKSQL = getAdapter()
333                         .getPkGenerator()
334                         .createAutoPkStatements(dbEntitiesRequiringAutoPK);
335                 Iterator JavaDoc it = createAutoPKSQL.iterator();
336                 while (it.hasNext()) {
337                     safeExecute(connection, (String JavaDoc) it.next());
338                 }
339             }
340
341             new DbGeneratorPostprocessor().execute(connection);
342         }
343         finally {
344             connection.close();
345         }
346     }
347
348     /**
349      * Builds and executes a SQL statement, catching and storing SQL exceptions resulting
350      * from invalid SQL. Only non-recoverable exceptions are rethrown.
351      *
352      * @since 1.1
353      */

354     protected boolean safeExecute(Connection JavaDoc connection, String JavaDoc sql) throws SQLException JavaDoc {
355         Statement JavaDoc statement = connection.createStatement();
356
357         try {
358             QueryLogger.logQuery(sql, null);
359             statement.execute(sql);
360             return true;
361         }
362         catch (SQLException JavaDoc ex) {
363             if (this.failures == null) {
364                 this.failures = new ValidationResult();
365             }
366
367             failures.addFailure(new SimpleValidationFailure(sql, ex.getMessage()));
368             QueryLogger.logQueryError(ex);
369             return false;
370         }
371         finally {
372             statement.close();
373         }
374     }
375
376     /**
377      * Returns an array of queries to create foreign key constraints for a particular
378      * DbEntity.
379      *
380      * @deprecated since 3.0 as this method is used to generate both FK and UNIQUE
381      * constraints, use 'createConstraintsQueries' instead.
382      */

383     public List JavaDoc createFkConstraintsQueries(DbEntity table) {
384         return createConstraintsQueries(table);
385     }
386
387     /**
388      * Creates FK and UNIQUE constraint statements for a given table.
389      *
390      * @since 3.0
391      */

392     public List JavaDoc createConstraintsQueries(DbEntity table) {
393         List JavaDoc list = new ArrayList JavaDoc();
394         Iterator JavaDoc it = table.getRelationships().iterator();
395         while (it.hasNext()) {
396             DbRelationship rel = (DbRelationship) it.next();
397
398             if (rel.isToMany()) {
399                 continue;
400             }
401
402             // skip FK to a different DB
403
if (domain != null) {
404                 DataMap srcMap = rel.getSourceEntity().getDataMap();
405                 DataMap targetMap = rel.getTargetEntity().getDataMap();
406
407                 if (srcMap != null && targetMap != null && srcMap != targetMap) {
408                     if (domain.lookupDataNode(srcMap) != domain.lookupDataNode(targetMap)) {
409                         continue;
410                     }
411                 }
412             }
413
414             // create an FK CONSTRAINT only if the relationship is to PK
415
// and if this is not a dependent PK
416

417             // create UNIQUE CONSTRAINT on FK if reverse relationship is to-one
418

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

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

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

463     public boolean shouldCreateTables() {
464         return shouldCreateTables;
465     }
466
467     public boolean shouldDropPKSupport() {
468         return shouldDropPKSupport;
469     }
470
471     public boolean shouldDropTables() {
472         return shouldDropTables;
473     }
474
475     public boolean shouldCreateFKConstraints() {
476         return shouldCreateFKConstraints;
477     }
478
479     public void setShouldCreatePKSupport(boolean shouldCreatePKSupport) {
480         this.shouldCreatePKSupport = shouldCreatePKSupport;
481     }
482
483     public void setShouldCreateTables(boolean shouldCreateTables) {
484         this.shouldCreateTables = shouldCreateTables;
485     }
486
487     public void setShouldDropPKSupport(boolean shouldDropPKSupport) {
488         this.shouldDropPKSupport = shouldDropPKSupport;
489     }
490
491     public void setShouldDropTables(boolean shouldDropTables) {
492         this.shouldDropTables = shouldDropTables;
493     }
494
495     public void setShouldCreateFKConstraints(boolean shouldCreateFKConstraints) {
496         this.shouldCreateFKConstraints = shouldCreateFKConstraints;
497     }
498
499     /**
500      * Returns a DataDomain used by the DbGenerator to detect cross-database
501      * relationships. By default DataDomain is null.
502      *
503      * @since 1.2
504      */

505     public DataDomain getDomain() {
506         return domain;
507     }
508
509     /**
510      * Helper method that orders DbEntities to satisfy referential constraints and returns
511      * an ordered list. It also filters out DerivedDbEntities.
512      */

513     private void prepareDbEntities(Collection JavaDoc excludedEntities) {
514         if (excludedEntities == null) {
515             excludedEntities = Collections.EMPTY_LIST;
516         }
517
518         // remove derived db entities
519
List JavaDoc tables = new ArrayList JavaDoc();
520         List JavaDoc tablesWithAutoPk = new ArrayList JavaDoc();
521         Iterator JavaDoc it = map.getDbEntities().iterator();
522         while (it.hasNext()) {
523             DbEntity nextEntity = (DbEntity) it.next();
524
525             // do sanity checks...
526

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