org.objectstyle.cayenne.access.DbGenerator Maven / Gradle / Ivy
/* ====================================================================
*
* The ObjectStyle Group Software License, version 1.1
* ObjectStyle Group - http://objectstyle.org/
*
* Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
* of the software. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if any,
* must include the following acknowlegement:
* "This product includes software developed by independent contributors
* and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
* or promote products derived from this software without prior written
* permission. For written permission, email
* "andrus at objectstyle dot org".
*
* 5. Products derived from this software may not be called "ObjectStyle"
* or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
* names without prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals and hosted on ObjectStyle Group web site. For more
* information on the ObjectStyle Group, please see
* .
*/
package org.objectstyle.cayenne.access;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.objectstyle.cayenne.CayenneRuntimeException;
import org.objectstyle.cayenne.conn.DataSourceInfo;
import org.objectstyle.cayenne.conn.DriverDataSource;
import org.objectstyle.cayenne.dba.DbAdapter;
import org.objectstyle.cayenne.dba.PkGenerator;
import org.objectstyle.cayenne.dba.TypesMapping;
import org.objectstyle.cayenne.map.DataMap;
import org.objectstyle.cayenne.map.DbAttribute;
import org.objectstyle.cayenne.map.DbEntity;
import org.objectstyle.cayenne.map.DbJoin;
import org.objectstyle.cayenne.map.DbRelationship;
import org.objectstyle.cayenne.map.DerivedDbEntity;
import org.objectstyle.cayenne.validation.SimpleValidationFailure;
import org.objectstyle.cayenne.validation.ValidationResult;
/**
* Utility class that generates database schema based on Cayenne mapping. It is a logical
* counterpart of DbLoader class.
*
* @author Andrus Adamchik
*/
public class DbGenerator {
private Logger logObj = Logger.getLogger(DbGenerator.class);
protected DbAdapter adapter;
protected DataMap map;
// optional DataDomain needed for correct FK generation in cross-db situations
protected DataDomain domain;
// stores generated SQL statements
protected Map dropTables;
protected Map createTables;
protected Map createFK;
protected List createPK;
protected List dropPK;
/**
* Contains all DbEntities ordered considering their interdependencies.
* DerivedDbEntities are filtered out of this list.
*/
protected List dbEntitiesInInsertOrder;
protected List dbEntitiesRequiringAutoPK;
protected boolean shouldDropTables;
protected boolean shouldCreateTables;
protected boolean shouldDropPKSupport;
protected boolean shouldCreatePKSupport;
protected boolean shouldCreateFKConstraints;
protected ValidationResult failures;
/**
* Creates and initializes new DbGenerator.
*/
public DbGenerator(DbAdapter adapter, DataMap map) {
this(adapter, map, Collections.EMPTY_LIST);
}
/**
* Creates and initializes new DbGenerator instance.
*
* @param adapter DbAdapter corresponding to the database
* @param map DataMap whose entities will be used in schema generation
* @param excludedEntities entities that should be ignored during schema generation
*/
public DbGenerator(DbAdapter adapter, DataMap map, Collection excludedEntities) {
this(adapter, map, excludedEntities, null);
}
/**
* Creates and initializes new DbGenerator instance.
*
* @param adapter DbAdapter corresponding to the database
* @param map DataMap whose entities will be used in schema generation
* @param excludedEntities entities that should be ignored during schema generation
* @param domain optional DataDomain used to detect cross-database relationships.
* @since 1.2
*/
public DbGenerator(DbAdapter adapter, DataMap map, Collection excludedEntities,
DataDomain domain) {
// sanity check
if (adapter == null) {
throw new IllegalArgumentException("Adapter must not be null.");
}
if (map == null) {
throw new IllegalArgumentException("DataMap must not be null.");
}
this.domain = domain;
this.map = map;
this.adapter = adapter;
prepareDbEntities(excludedEntities);
resetToDefaults();
buildStatements();
}
protected void resetToDefaults() {
this.shouldDropTables = false;
this.shouldDropPKSupport = false;
this.shouldCreatePKSupport = true;
this.shouldCreateTables = true;
this.shouldCreateFKConstraints = true;
}
/**
* Creates and stores internally a set of statements for database schema creation,
* ignoring configured schema creation preferences. Statements are NOT executed in
* this method.
*/
protected void buildStatements() {
dropTables = new HashMap();
createTables = new HashMap();
createFK = new HashMap();
DbAdapter adapter = getAdapter();
Iterator it = dbEntitiesInInsertOrder.iterator();
boolean supportsFK = adapter.supportsFkConstraints();
while (it.hasNext()) {
DbEntity dbe = (DbEntity) it.next();
String name = dbe.getName();
// build "DROP TABLE"
dropTables.put(name, adapter.dropTable(dbe));
// build "CREATE TABLE"
createTables.put(name, adapter.createTable(dbe));
// build "FK"
if (supportsFK) {
createFK.put(name, createFkConstraintsQueries(dbe));
}
}
PkGenerator pkGenerator = adapter.getPkGenerator();
dropPK = pkGenerator.dropAutoPkStatements(dbEntitiesRequiringAutoPK);
createPK = pkGenerator.createAutoPkStatements(dbEntitiesRequiringAutoPK);
}
/**
* Returns true
if there is nothing to be done by this generator. If
* respectConfiguredSettings
is true
, checks are done
* applying currently configured settings, otherwise check is done, assuming that all
* possible generated objects.
*/
public boolean isEmpty(boolean respectConfiguredSettings) {
if (dbEntitiesInInsertOrder.isEmpty() && dbEntitiesRequiringAutoPK.isEmpty()) {
return true;
}
if (!respectConfiguredSettings) {
return false;
}
return !(shouldDropTables
|| shouldCreateTables
|| shouldCreateFKConstraints
|| shouldCreatePKSupport || shouldDropPKSupport);
}
/** Returns DbAdapter associated with this DbGenerator. */
public DbAdapter getAdapter() {
return adapter;
}
/**
* Returns a list of all schema statements that should be executed with the current
* configuration.
*/
public List configuredStatements() {
List list = new ArrayList();
if (shouldDropTables) {
ListIterator it = dbEntitiesInInsertOrder
.listIterator(dbEntitiesInInsertOrder.size());
while (it.hasPrevious()) {
DbEntity ent = (DbEntity) it.previous();
list.add(dropTables.get(ent.getName()));
}
}
if (shouldCreateTables) {
Iterator it = dbEntitiesInInsertOrder.iterator();
while (it.hasNext()) {
DbEntity ent = (DbEntity) it.next();
list.add(createTables.get(ent.getName()));
}
}
if (shouldCreateFKConstraints && getAdapter().supportsFkConstraints()) {
Iterator it = dbEntitiesInInsertOrder.iterator();
while (it.hasNext()) {
DbEntity ent = (DbEntity) it.next();
List fks = (List) createFK.get(ent.getName());
list.addAll(fks);
}
}
if (shouldDropPKSupport) {
list.addAll(dropPK);
}
if (shouldCreatePKSupport) {
list.addAll(createPK);
}
return list;
}
/**
* Creates a temporary DataSource out of DataSourceInfo and invokes
* public void runGenerator(DataSource ds)
.
*/
public void runGenerator(DataSourceInfo dsi) throws Exception {
this.failures = null;
// do a pre-check. Maybe there is no need to run anything
// and therefore no need to create a connection
if (isEmpty(true)) {
return;
}
Driver driver = (Driver) Class.forName(dsi.getJdbcDriver()).newInstance();
DataSource dataSource = new DriverDataSource(driver, dsi.getDataSourceUrl(), dsi
.getUserName(), dsi.getPassword());
runGenerator(dataSource);
}
/**
* Executes a set of commands to drop/create database objects. This is the main worker
* method of DbGenerator. Command set is built based on pre-configured generator
* settings.
*/
public void runGenerator(DataSource ds) throws Exception {
this.failures = null;
Connection connection = ds.getConnection();
try {
// drop tables
if (shouldDropTables) {
ListIterator it = dbEntitiesInInsertOrder
.listIterator(dbEntitiesInInsertOrder.size());
while (it.hasPrevious()) {
DbEntity ent = (DbEntity) it.previous();
safeExecute(connection, (String) dropTables.get(ent.getName()));
}
}
// create tables
List createdTables = new ArrayList();
if (shouldCreateTables) {
Iterator it = dbEntitiesInInsertOrder.iterator();
while (it.hasNext()) {
DbEntity ent = (DbEntity) it.next();
// only create missing tables
safeExecute(connection, (String) createTables.get(ent.getName()));
createdTables.add(ent.getName());
}
}
// create FK
if (shouldCreateTables
&& shouldCreateFKConstraints
&& getAdapter().supportsFkConstraints()) {
Iterator it = dbEntitiesInInsertOrder.iterator();
while (it.hasNext()) {
DbEntity ent = (DbEntity) it.next();
if (createdTables.contains(ent.getName())) {
List fks = (List) createFK.get(ent.getName());
Iterator fkIt = fks.iterator();
while (fkIt.hasNext()) {
safeExecute(connection, (String) fkIt.next());
}
}
}
}
// drop PK
if (shouldDropPKSupport) {
List dropAutoPKSQL = getAdapter().getPkGenerator().dropAutoPkStatements(
dbEntitiesRequiringAutoPK);
Iterator it = dropAutoPKSQL.iterator();
while (it.hasNext()) {
safeExecute(connection, (String) it.next());
}
}
// create pk
if (shouldCreatePKSupport) {
List createAutoPKSQL = getAdapter()
.getPkGenerator()
.createAutoPkStatements(dbEntitiesRequiringAutoPK);
Iterator it = createAutoPKSQL.iterator();
while (it.hasNext()) {
safeExecute(connection, (String) it.next());
}
}
}
finally {
connection.close();
}
}
/**
* Builds and executes a SQL statement, catching and storing SQL exceptions resulting
* from invalid SQL. Only non-recoverable exceptions are rethrown.
*
* @since 1.1
*/
protected boolean safeExecute(Connection connection, String sql) throws SQLException {
Statement statement = connection.createStatement();
try {
QueryLogger.logQuery(sql, null);
statement.execute(sql);
return true;
}
catch (SQLException ex) {
if (this.failures == null) {
this.failures = new ValidationResult();
}
failures.addFailure(new SimpleValidationFailure(sql, ex.getMessage()));
QueryLogger.logQueryError(ex);
return false;
}
finally {
statement.close();
}
}
/**
* Returns an array of queries to create foreign key constraints for a particular
* DbEntity. Throws CayenneRuntimeException, if called for adapter that does not
* support FK constraints.
*/
public List createFkConstraintsQueries(DbEntity dbEnt) {
if (!getAdapter().supportsFkConstraints()) {
throw new CayenneRuntimeException(
"FK constraints are not supported by adapter.");
}
List list = new ArrayList();
Iterator it = dbEnt.getRelationships().iterator();
while (it.hasNext()) {
DbRelationship rel = (DbRelationship) it.next();
if (rel.isToMany()) {
continue;
}
// skip FK to a different DB
if (domain != null) {
DataMap srcMap = rel.getSourceEntity().getDataMap();
DataMap targetMap = rel.getTargetEntity().getDataMap();
if (srcMap != null && targetMap != null && srcMap != targetMap) {
if (domain.lookupDataNode(srcMap) != domain.lookupDataNode(targetMap)) {
continue;
}
}
}
// create an FK CONSTRAINT only if the relationship is to PK
// and if this is not a dependent PK
// create UNIQUE CONSTRAINT on FK if reverse relationship is to-one
if (rel.isToPK() && !rel.isToDependentPK()) {
if (getAdapter().supportsUniqueConstraints()) {
DbRelationship reverse = rel.getReverseRelationship();
if (reverse != null && !reverse.isToMany() && !reverse.isToPK()) {
list.add(getAdapter().createUniqueConstraint(
(DbEntity) rel.getSourceEntity(),
rel.getSourceAttributes()));
}
}
list.add(getAdapter().createFkConstraint(rel));
}
}
return list;
}
/**
* Returns an object representing a collection of failures that occurred on the last
* "runGenerator" invocation, or null if there were no failures. Failures usually
* indicate problems with generated DDL (such as "create...", "drop...", etc.) and
* usually happen due to the DataMap being out of sync with the database.
*
* @since 1.1
*/
public ValidationResult getFailures() {
return failures;
}
/**
* Returns whether DbGenerator is configured to create primary key support for DataMap
* entities.
*/
public boolean shouldCreatePKSupport() {
return shouldCreatePKSupport;
}
/**
* Returns whether DbGenerator is configured to create tables for DataMap entities.
*/
public boolean shouldCreateTables() {
return shouldCreateTables;
}
public boolean shouldDropPKSupport() {
return shouldDropPKSupport;
}
public boolean shouldDropTables() {
return shouldDropTables;
}
public boolean shouldCreateFKConstraints() {
return shouldCreateFKConstraints;
}
public void setShouldCreatePKSupport(boolean shouldCreatePKSupport) {
this.shouldCreatePKSupport = shouldCreatePKSupport;
}
public void setShouldCreateTables(boolean shouldCreateTables) {
this.shouldCreateTables = shouldCreateTables;
}
public void setShouldDropPKSupport(boolean shouldDropPKSupport) {
this.shouldDropPKSupport = shouldDropPKSupport;
}
public void setShouldDropTables(boolean shouldDropTables) {
this.shouldDropTables = shouldDropTables;
}
public void setShouldCreateFKConstraints(boolean shouldCreateFKConstraints) {
this.shouldCreateFKConstraints = shouldCreateFKConstraints;
}
/**
* Returns a DataDomain used by the DbGenerator to detect cross-database
* relationships. By default DataDomain is null.
*
* @since 1.2
*/
public DataDomain getDomain() {
return domain;
}
/**
* Helper method that orders DbEntities to satisfy referential constraints and returns
* an ordered list. It also filters out DerivedDbEntities.
*/
private void prepareDbEntities(Collection excludedEntities) {
if (excludedEntities == null) {
excludedEntities = Collections.EMPTY_LIST;
}
// remove derived db entities
List tables = new ArrayList();
List tablesWithAutoPk = new ArrayList();
Iterator it = map.getDbEntities().iterator();
while (it.hasNext()) {
DbEntity nextEntity = (DbEntity) it.next();
// do sanity checks...
// derived DbEntities are not included in generated SQL
if (nextEntity instanceof DerivedDbEntity) {
continue;
}
// tables with no columns are not included
if (nextEntity.getAttributes().size() == 0) {
logObj
.info("Skipping entity with no attributes: "
+ nextEntity.getName());
continue;
}
// check if this entity is explicitly excluded
if (excludedEntities.contains(nextEntity)) {
continue;
}
// tables with invalid DbAttributes are not included
boolean invalidAttributes = false;
Iterator nextDbAtributes = nextEntity.getAttributes().iterator();
while (nextDbAtributes.hasNext()) {
DbAttribute attr = (DbAttribute) nextDbAtributes.next();
if (attr.getType() == TypesMapping.NOT_DEFINED) {
logObj.info("Skipping entity, attribute type is undefined: "
+ nextEntity.getName()
+ "."
+ attr.getName());
invalidAttributes = true;
break;
}
}
if (invalidAttributes) {
continue;
}
tables.add(nextEntity);
// check if an automatic PK generation can be potentailly supported
// in this entity. For now simply check that the key is not propagated
Iterator relationships = nextEntity.getRelationships().iterator();
// create a copy of the original PK list,
// since the list will be modified locally
List pkAttributes = new ArrayList(nextEntity.getPrimaryKey());
while (pkAttributes.size() > 0 && relationships.hasNext()) {
DbRelationship nextRelationship = (DbRelationship) relationships.next();
if (!nextRelationship.isToMasterPK()) {
continue;
}
// supposedly all source attributes of the relationship
// to master entity must be a part of primary key,
// so
Iterator joins = nextRelationship.getJoins().iterator();
while (joins.hasNext()) {
DbJoin join = (DbJoin) joins.next();
pkAttributes.remove(join.getSource());
}
}
// primary key is needed only if at least one of the primary key attributes
// is not propagated via releationship
if (pkAttributes.size() > 0) {
tablesWithAutoPk.add(nextEntity);
}
}
// sort table list
if (tables.size() > 1) {
DataNode node = new DataNode("temp");
node.addDataMap(map);
node.getEntitySorter().sortDbEntities(tables, false);
}
this.dbEntitiesInInsertOrder = tables;
this.dbEntitiesRequiringAutoPK = tablesWithAutoPk;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy