org.apache.torque.engine.database.model.Table Maven / Gradle / Ivy
package org.apache.torque.engine.database.model;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.torque.engine.EngineException;
import org.xml.sax.Attributes;
/**
* Data about a table used in an application.
*
* @author Leon Messerschmidt
* @author Jason van Zyl
* @author John McNally
* @author Byron Foster
* @author
* A hook for the SAX XML parser to call when this table has been fully loaded from the XML, and all nested elements
* have been processed.
*
*
*
* Performs heavy indexing and naming of elements which weren't provided with a name.
*
*/
public void doFinalInitialization() {
// Heavy indexing must wait until after all columns composing
// a table's primary key have been parsed.
if (heavyIndexing) {
doHeavyIndexing();
}
// Name any indices which are missing a name using the
// appropriate algorithm.
doNaming();
}
/**
*
* Adds extra indices for multi-part primary key columns.
*
*
*
* For databases like MySQL, values in a where clause must match key part order from the left to right. So, in the
* key definition PRIMARY KEY (FOO_ID, BAR_ID)
, FOO_ID
must be the first element
* used in the where
clause of the SQL query used against this table for the primary key index to be
* used. This feature could cause problems under MySQL with heavily indexed tables, as MySQL currently only supports
* 16 indices per table (i.e. it might cause too many indices to be created).
*
*
*
* See the manual for a better description of why heavy
* indexing is useful for quickly searchable database tables.
*
*/
private void doHeavyIndexing() {
if (log.isDebugEnabled()) {
log.debug("doHeavyIndex() called on table " + name);
}
List pk = getPrimaryKey();
int size = pk.size();
try {
// We start at an offset of 1 because the entire column
// list is generally implicitly indexed by the fact that
// it's a primary key.
for (int i = 1; i < size; i++) {
addIndex(new Index(this, pk.subList(i, size)));
}
} catch (EngineException e) {
log.error(e, e);
}
}
/**
* Names composing objects which haven't yet been named. This currently consists of foreign-key and index entities.
*/
private void doNaming() {
int i;
int size;
String name;
// Assure names are unique across all databases.
try {
for (i = 0, size = foreignKeys.size(); i < size; i++) {
ForeignKey fk = (ForeignKey) foreignKeys.get(i);
name = fk.getName();
if (StringUtils.isEmpty(name)) {
name = acquireConstraintName("FK", i + 1);
fk.setName(name);
}
}
for (i = 0, size = indices.size(); i < size; i++) {
Index index = (Index) indices.get(i);
name = index.getName();
if (StringUtils.isEmpty(name)) {
name = acquireConstraintName("I", i + 1);
index.setName(name);
}
}
for (i = 0, size = unices.size(); i < size; i++) {
Unique unique = (Unique) unices.get(i);
name = unique.getName();
if (StringUtils.isEmpty(name)) {
name = acquireConstraintName("U", i + 1);
unique.setName(name);
}
}
} catch (EngineException nameAlreadyInUse) {
log.error(nameAlreadyInUse, nameAlreadyInUse);
}
}
/**
* Macro to a constraint name.
*
* @param nameType
* constraint type
* @param nbr
* unique number for this constraint type
* @return unique name for constraint
* @throws EngineException
*/
private final String acquireConstraintName(String nameType, int nbr) throws EngineException {
List inputs = new ArrayList(4);
inputs.add(getDatabase());
inputs.add(getName());
inputs.add(nameType);
inputs.add(new Integer(nbr));
return NameFactory.generateName(NameFactory.CONSTRAINT_GENERATOR, inputs);
}
/**
* Gets the value of base class for classes produced from this table.
*
* @return The base class for classes produced from this table.
*/
public String getBaseClass() {
if (isAlias() && baseClass == null) {
return alias;
} else if (baseClass == null) {
return getDatabase().getBaseClass();
} else {
return baseClass;
}
}
/**
* Set the value of baseClass.
*
* @param v
* Value to assign to baseClass.
*/
public void setBaseClass(String v) {
this.baseClass = v;
}
/**
* Get the value of basePeer.
*
* @return value of basePeer.
*/
public String getBasePeer() {
if (isAlias() && basePeer == null) {
return alias + "Peer";
} else if (basePeer == null) {
return getDatabase().getBasePeer();
} else {
return basePeer;
}
}
/**
* Set the value of basePeer.
*
* @param v
* Value to assign to basePeer.
*/
public void setBasePeer(String v) {
this.basePeer = v;
}
/**
* A utility function to create a new column from attrib and add it to this table.
*
* @param attrib
* xml attributes for the column to add
* @return the added column
*/
public Column addColumn(Attributes attrib) {
Column col = new Column();
col.setTable(this);
col.setCorrectGetters(false);
col.loadFromXML(attrib);
addColumn(col);
return col;
}
/**
* Adds a new column to the column list and set the parent table of the column to the current table
*
* @param col
* the column to add
*/
public void addColumn(Column col) {
col.setTable(this);
if (col.isInheritance()) {
inheritanceColumn = col;
}
columnList.add(col);
columnsByName.put(col.getName(), col);
columnsByJavaName.put(col.getJavaName(), col);
col.setPosition(columnList.size());
needsTransactionInPostgres |= col.requiresTransactionInPostgres();
}
/**
* A utility function to create a new foreign key from attrib and add it to this table.
*
* @param attrib
* the xml attributes
* @return the created ForeignKey
*/
public ForeignKey addForeignKey(Attributes attrib) {
ForeignKey fk = new ForeignKey();
fk.loadFromXML(attrib);
addForeignKey(fk);
return fk;
}
/**
* Gets the column that subclasses of the class representing this table can be produced from.
*/
public Column getChildrenColumn() {
return inheritanceColumn;
}
/**
* Get the objects that can be created from this table.
*/
public List getChildrenNames() {
if (inheritanceColumn == null || !inheritanceColumn.isEnumeratedClasses()) {
return null;
}
List children = inheritanceColumn.getChildren();
List names = new ArrayList(children.size());
for (int i = 0; i < children.size(); i++) {
names.add(((Inheritance) children.get(i)).getClassName());
}
return names;
}
/**
* Adds the foreign key from another table that refers to this table.
*
* @param fk
* A foreign key refering to this table
*/
public void addReferrer(ForeignKey fk) {
if (referrers == null) {
referrers = new ArrayList(5);
}
referrers.add(fk);
}
/**
* Get list of references to this table.
*
* @return A list of references to this table
*/
public List getReferrers() {
return referrers;
}
/**
* Set whether this table contains a foreign PK
*
* @param b
*/
public void setContainsForeignPK(boolean b) {
containsForeignPK = b;
}
/**
* Determine if this table contains a foreign PK
*/
public boolean getContainsForeignPK() {
return containsForeignPK;
}
/**
* A list of tables referenced by foreign keys in this table
*
* @return A list of tables
*/
public List getForeignTableNames() {
if (foreignTableNames == null) {
foreignTableNames = new ArrayList(1);
}
return foreignTableNames;
}
/**
* Adds a new FK to the FK list and set the parent table of the column to the current table
*
* @param fk
* A foreign key
*/
public void addForeignKey(ForeignKey fk) {
fk.setTable(this);
foreignKeys.add(fk);
if (foreignTableNames == null) {
foreignTableNames = new ArrayList(5);
}
if (!foreignTableNames.contains(fk.getForeignTableName())) {
foreignTableNames.add(fk.getForeignTableName());
}
}
/**
* Return true if the column requires a transaction in Postgres
*/
public boolean requiresTransactionInPostgres() {
return needsTransactionInPostgres;
}
/**
* A utility function to create a new id method parameter from attrib and add it to this table.
*/
public IdMethodParameter addIdMethodParameter(Attributes attrib) {
IdMethodParameter imp = new IdMethodParameter();
imp.loadFromXML(attrib);
addIdMethodParameter(imp);
return imp;
}
/**
* Adds a new ID method parameter to the list and sets the parent table of the column associated with the supplied
* parameter to this table.
*
* @param imp
* The column to add as an ID method parameter.
*/
public void addIdMethodParameter(IdMethodParameter imp) {
imp.setTable(this);
if (idMethodParameters == null) {
idMethodParameters = new ArrayList(2);
}
idMethodParameters.add(imp);
}
/**
* Adds a new index to the index list and set the parent table of the column to the current table
*/
public void addIndex(Index index) {
index.setTable(this);
indices.add(index);
}
/**
* A utility function to create a new index from attrib and add it to this table.
*/
public Index addIndex(Attributes attrib) {
Index index = new Index();
index.loadFromXML(attrib);
addIndex(index);
return index;
}
/**
* Adds a new Unique to the Unique list and set the parent table of the column to the current table
*/
public void addUnique(Unique unique) {
unique.setTable(this);
unices.add(unique);
}
/**
* A utility function to create a new Unique from attrib and add it to this table.
*
* @param attrib
* the xml attributes
*/
public Unique addUnique(Attributes attrib) {
Unique unique = new Unique();
unique.loadFromXML(attrib);
addUnique(unique);
return unique;
}
/**
* Get the name of the Table
*/
public String getName() {
return name;
}
/**
* Set the name of the Table
*/
public void setName(String newName) {
name = newName;
}
/**
* Get the description for the Table
*/
public String getDescription() {
return description;
}
/**
* Set the description for the Table
*
* @param newDescription
* description for the Table
*/
public void setDescription(String newDescription) {
description = newDescription;
}
/**
* Get name to use in Java sources
*/
public String getJavaName() {
if (javaName == null) {
List inputs = new ArrayList(2);
inputs.add(name);
inputs.add(javaNamingMethod);
try {
javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR, inputs);
} catch (EngineException e) {
log.error(e, e);
}
}
return javaName;
}
/**
* Set name to use in Java sources
*/
public void setJavaName(String javaName) {
this.javaName = javaName;
}
/**
* Get the method for generating pk's
*/
public String getIdMethod() {
if (idMethod == null) {
return IDMethod.NO_ID_METHOD;
} else {
return idMethod;
}
}
/**
* Set the method for generating pk's
*/
public void setIdMethod(String idMethod) {
this.idMethod = idMethod;
}
/**
* Skip generating sql for this table (in the event it should not be created from scratch).
*
* @return value of skipSql.
*/
public boolean isSkipSql() {
return (skipSql || isAlias() || isForReferenceOnly());
}
/**
* Set whether this table should have its creation sql generated.
*
* @param v
* Value to assign to skipSql.
*/
public void setSkipSql(boolean v) {
this.skipSql = v;
}
/**
* JavaName of om object this entry references.
*
* @return value of external.
*/
public String getAlias() {
return alias;
}
/**
* Is this table specified in the schema or is there just a foreign key reference to it.
*
* @return value of external.
*/
public boolean isAlias() {
return (alias != null);
}
/**
* Set whether this table specified in the schema or is there just a foreign key reference to it.
*
* @param v
* Value to assign to alias.
*/
public void setAlias(String v) {
this.alias = v;
}
/**
* Interface which objects for this table will implement
*
* @return value of interface.
*/
public String getInterface() {
return enterface;
}
/**
* Interface which objects for this table will implement
*
* @param v
* Value to assign to interface.
*/
public void setInterface(String v) {
this.enterface = v;
}
/**
* When a table is abstract, it marks the business object class that is generated as being abstract. If you have a
* table called "FOO", then the Foo BO will be public abstract class Foo
This helps support class
* hierarchies
*
* @return value of abstractValue.
*/
public boolean isAbstract() {
return abstractValue;
}
/**
* When a table is abstract, it marks the business object class that is generated as being abstract. If you have a
* table called "FOO", then the Foo BO will be public abstract class Foo
This helps support class
* hierarchies
*
* @param v
* Value to assign to abstractValue.
*/
public void setAbstract(boolean v) {
this.abstractValue = v;
}
/**
* Get the value of package.
*
* @return value of package.
*/
public String getPackage() {
if (pkg != null) {
return pkg;
} else {
return this.getDatabase().getPackage();
}
}
/**
* Set the value of package.
*
* @param v
* Value to assign to package.
*/
public void setPackage(String v) {
this.pkg = v;
}
/**
* Returns a List containing all the columns in the table
*
* @return a List containing all the columns
*/
public List getColumns() {
return columnList;
}
/**
* Utility method to get the number of columns in this table
*/
public int getNumColumns() {
return columnList.size();
}
/**
* Returns a List containing all the FKs in the table
*
* @return a List containing all the FKs
*/
public List getForeignKeys() {
return foreignKeys;
}
/**
* Returns a Collection of parameters relevant for the chosen id generation method.
*/
public List getIdMethodParameters() {
return idMethodParameters;
}
/**
* A name to use for creating a sequence if one is not specified.
*
* @return name of the sequence
*/
public String getSequenceName() {
String result = null;
if (getIdMethod().equals(NATIVE)) {
List idMethodParams = getIdMethodParameters();
if (idMethodParams == null) {
result = getName() + "_SEQ";
} else {
result = ((IdMethodParameter) idMethodParams.get(0)).getValue();
}
}
return result;
}
/**
* Returns a List containing all the indices in the table
*
* @return A List containing all the indices
*/
public List getIndices() {
return indices;
}
/**
* Returns a List containing all the UKs in the table
*
* @return A List containing all the UKs
*/
public List getUnices() {
return unices;
}
/**
* Returns a specified column.
*
* @param name
* name of the column
* @return Return a Column object or null if it does not exist.
*/
public Column getColumn(String name) {
return (Column) columnsByName.get(name);
}
/**
* Returns a specified column.
*
* @param javaName
* java name of the column
* @return Return a Column object or null if it does not exist.
*/
public Column getColumnByJavaName(String javaName) {
return (Column) columnsByJavaName.get(javaName);
}
/**
* Return the first foreign key that includes col in it's list of local columns. Eg. Foreign key (a,b,c) refrences
* tbl(x,y,z) will be returned of col is either a,b or c.
*
* @param col
* column name included in the key
* @return Return a Column object or null if it does not exist.
*/
public ForeignKey getForeignKey(String col) {
ForeignKey firstFK = null;
for (Iterator iter = foreignKeys.iterator(); iter.hasNext();) {
ForeignKey key = (ForeignKey) iter.next();
if (key.getLocalColumns().contains(col)) {
if (firstFK == null) {
firstFK = key;
} else {
// System.out.println(col+" is in multiple FKs. This is not"
// + " being handled properly.");
// throw new IllegalStateException("Cannot call method if " +
// "column is referenced multiple times");
}
}
}
return firstFK;
}
/**
* Returns true if the table contains a specified column
*
* @param col
* the column
* @return true if the table contains the column
*/
public boolean containsColumn(Column col) {
return columnList.contains(col);
}
/**
* Returns true if the table contains a specified column
*
* @param name
* name of the column
* @return true if the table contains the column
*/
public boolean containsColumn(String name) {
return (getColumn(name) != null);
}
/**
* Set the parent of the table
*
* @param parent
* the parant database
*/
public void setDatabase(Database parent) {
tableParent = parent;
}
/**
* Get the parent of the table
*
* @return the parant database
*/
public Database getDatabase() {
return tableParent;
}
/**
* Flag to determine if code/sql gets created for this table. Table will be skipped, if return true.
*
* @return value of forReferenceOnly.
*/
public boolean isForReferenceOnly() {
return forReferenceOnly;
}
/**
* Flag to determine if code/sql gets created for this table. Table will be skipped, if set to true.
*
* @param v
* Value to assign to forReferenceOnly.
*/
public void setForReferenceOnly(boolean v) {
this.forReferenceOnly = v;
}
/**
* Returns a XML representation of this table.
*
* @return XML representation of this table
*/
public String toString() {
StringBuffer result = new StringBuffer();
result.append("\n");
if (columnList != null) {
for (Iterator iter = columnList.iterator(); iter.hasNext();) {
result.append(iter.next());
}
}
if (foreignKeys != null) {
for (Iterator iter = foreignKeys.iterator(); iter.hasNext();) {
result.append(iter.next());
}
}
if (idMethodParameters != null) {
Iterator iter = idMethodParameters.iterator();
while (iter.hasNext()) {
result.append(iter.next());
}
}
result.append("
\n");
return result.toString();
}
/**
* Returns the collection of Columns which make up the single primary key for this table.
*
* @return A list of the primary key parts.
*/
public List getPrimaryKey() {
List pk = new ArrayList(columnList.size());
Iterator iter = columnList.iterator();
while (iter.hasNext()) {
Column col = (Column) iter.next();
if (col.isPrimaryKey()) {
pk.add(col);
}
}
return pk;
}
/**
* Determine whether this table has a primary key.
*
* @return Whether this table has any primary key parts.
*/
public boolean hasPrimaryKey() {
return (getPrimaryKey().size() > 0);
}
/**
* Returns all parts of the primary key, separated by commas.
*
* @return A CSV list of primary key parts.
*/
public String printPrimaryKey() {
return printList(columnList);
}
/**
* Returns the elements of the list, separated by commas.
*
* @param list
* a list of Columns
* @return A CSV list.
*/
private String printList(List list) {
StringBuffer result = new StringBuffer();
boolean comma = false;
for (Iterator iter = list.iterator(); iter.hasNext();) {
Column col = (Column) iter.next();
if (col.isPrimaryKey()) {
if (comma) {
result.append(',');
} else {
comma = true;
}
result.append(col.getName());
}
}
return result.toString();
}
/**
* Force all columns to set the correctGetters property.
*
* @param value
* The new value of the correctGetters property.
* @since 3.2
*/
public void setCorrectGetters(Boolean value) {
boolean correctGetters = value != null && value.booleanValue();
for (Iterator it = columnList.iterator(); it.hasNext();) {
Column col = (Column) it.next();
col.setCorrectGetters(correctGetters);
}
}
/**
* Add an XML Specified option key/value pair to this element's option set.
*
* @param key
* the key of the option.
* @param value
* the value of the option.
*/
public void addOption(String key, String value) {
options.put(key, value);
}
/**
* Get the value that was associated with this key in an XML option element.
*
* @param key
* the key of the option.
* @return The value for the key or a null.
*/
public String getOption(String key) {
return (String) options.get(key);
}
/**
* Gets the full ordered hashtable array of items specified by XML option statements under this element.
*
*
* Note, this is not thread save but since it's only used for generation which is single threaded, there should be
* minimum danger using this in Velocity.
*
* @return An Map of all options. Will not be null but may be empty.
*/
public Map getOptions() {
return options;
}
}