/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * tware - added handling of database delimiters * 11/19/2012-2.5 Guy Pelletier * - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support) * 11/22/2012-2.5 Guy Pelletier * - 389090: JPA 2.1 DDL Generation Support (index metadata support) * 12/07/2012-2.5 Guy Pelletier * - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support) ******************************************************************************/ package org.eclipse.persistence.internal.helper; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.persistence.internal.core.helper.CoreTable; import org.eclipse.persistence.internal.databaseaccess.*; import org.eclipse.persistence.internal.expressions.ExpressionSQLPrinter; import org.eclipse.persistence.tools.schemaframework.ForeignKeyConstraint; import org.eclipse.persistence.tools.schemaframework.IndexDefinition; /** * INTERNAL: * <p> <b>Purpose</b>: * Define a fully qualified table name.<p> * <b>Responsibilities</b>: <ul> * <li> Allow specification of a qualifier to the table, i.e. creator or database. * </ul> *@see DatabaseField */ public class DatabaseTable implements CoreTable, Cloneable, Serializable { protected String name; protected String tableQualifier; protected String qualifiedName; /** JPA 2.1 Foreign key specification data */ protected Map<String, ForeignKeyConstraint> foreignKeyConstraints; /** * Contains the user specified unique constraints. JPA 2.0 introduced * the name element, therefore, if specified we will use that name * to create the constraint. Constraints with no name will be added to the * map under the null key and generated with a default name. * Therefore, when a name is given the list size should only ever be * 1. We will validate. The null key could have multiples however they will * have their names defaulted (as we did before). */ protected Map<String, List<List<String>>> uniqueConstraints; /** * Store the set of indexes defined through meta-data for the table. */ protected List<IndexDefinition> indexes; protected boolean useDelimiters = false; protected String creationSuffix; /** * Initialize the newly allocated instance of this class. * By default their is no qualifier. */ public DatabaseTable() { name = ""; tableQualifier = ""; } public DatabaseTable(String possiblyQualifiedName) { this(possiblyQualifiedName, null, null); } public DatabaseTable(String possiblyQualifiedName, String startDelimiter, String endDelimiter) { setPossiblyQualifiedName(possiblyQualifiedName, startDelimiter, endDelimiter); } public DatabaseTable(String tableName, String qualifier) { this(tableName, qualifier, false, null, null); } public DatabaseTable(String tableName, String qualifier, boolean useDelimiters, String startDelimiter, String endDelimiter) { setName(tableName, startDelimiter, endDelimiter); this.tableQualifier = qualifier; this.useDelimiters = useDelimiters; } public void addForeignKeyConstraint(ForeignKeyConstraint foreignKeyConstraint) { if (foreignKeyConstraints == null) { foreignKeyConstraints = new HashMap<String, ForeignKeyConstraint>(); } foreignKeyConstraints.put(foreignKeyConstraint.getName(), foreignKeyConstraint); } /** * Add an index definition to this table. */ public void addIndex(IndexDefinition index) { getIndexes().add(index); } /** * Add the unique constraint for the columns names. Used for DDL generation. * For now we just add all the unique constraints as we would have before * when we didn't have a name. */ public void addUniqueConstraints(String name, List<String> columnNames) { if (getUniqueConstraints().containsKey(name)) { getUniqueConstraints().get(name).add(columnNames); } else { List<List<String>> value = new ArrayList<List<String>>(); value.add(columnNames); getUniqueConstraints().put(name, value); } } /** * Return a shallow copy of the receiver. */ public DatabaseTable clone() { try { return (DatabaseTable)super.clone(); } catch (CloneNotSupportedException exception) { throw new InternalError(exception.getMessage()); } } /** * Two tables are equal if their names and tables are equal, * or their names are equal and one does not have a qualifier assigned. * This allows an unqualified table to equal the same fully qualified one. */ public boolean equals(Object object) { if (object instanceof DatabaseTable) { return equals((DatabaseTable)object); } return false; } /** * Two tables are equal if their names and tables are equal, * or their names are equal and one does not have a qualifier assigned. * This allows an unqualified table to equal the same fully qualified one. */ public boolean equals(DatabaseTable table) { if (this == table) { return true; } if (table == null) { return false; } if (DatabasePlatform.shouldIgnoreCaseOnFieldComparisons) { if (this.name.equalsIgnoreCase(table.name)) { if ((this.tableQualifier.length() == 0) || (table.tableQualifier.length() == 0) || (this.tableQualifier.equalsIgnoreCase(table.tableQualifier))) { return true; } } } else { if (this.name.equals(table.name)) { if ((this.tableQualifier.length() == 0) || (table.tableQualifier.length() == 0) || (this.tableQualifier.equals(table.tableQualifier))) { return true; } } } return false; } /** * returns the suffix applied to the CREATE table statement on this field for DDL generation. */ public String getCreationSuffix() { return creationSuffix; } public ForeignKeyConstraint getForeignKeyConstraint(String name) { return foreignKeyConstraints.get(name); } public Map<String, ForeignKeyConstraint> getForeignKeyConstraints() { return foreignKeyConstraints; } /** * Return a list of index definitions. * Used for DDL generation. */ public List<IndexDefinition> getIndexes() { if (this.indexes == null) { this.indexes = new ArrayList<IndexDefinition>(); } return this.indexes; } /** * Get method for table name. */ public String getName() { return name; } /** * Get method for table name. */ public String getNameDelimited(DatasourcePlatform platform) { if (useDelimiters){ return platform.getStartDelimiter() + name + platform.getEndDelimiter(); } return name; } public String getQualifiedName() { if (qualifiedName == null) { if (tableQualifier.equals("")) { qualifiedName = getName(); } else { qualifiedName = getTableQualifier() + "." + getName(); } } return qualifiedName; } public String getQualifiedNameDelimited(DatasourcePlatform platform) { if (tableQualifier.equals("")) { if (useDelimiters){ return platform.getStartDelimiter() + getName() + platform.getEndDelimiter(); } else { return getName(); } } else { if (useDelimiters){ return platform.getStartDelimiter() + getTableQualifier() + platform.getEndDelimiter() + "." + platform.getStartDelimiter() + getName() + platform.getEndDelimiter(); } else { return getTableQualifier() + "." + getName(); } } } /** * Print the table's SQL from clause. */ public void printSQL(ExpressionSQLPrinter printer) throws IOException { printer.getWriter().write(getQualifiedNameDelimited(printer.getPlatform())); } public String getTableQualifierDelimited(DatasourcePlatform platform) { if (useDelimiters && tableQualifier != null && !tableQualifier.equals("")){ return platform.getStartDelimiter() + tableQualifier + platform.getEndDelimiter(); } return tableQualifier; } public String getTableQualifier() { return tableQualifier; } public boolean hasUniqueConstraints() { return (this.uniqueConstraints != null) && (!this.uniqueConstraints.isEmpty()); } public boolean hasForeignKeyConstraints() { return foreignKeyConstraints != null; } /** * Return the hashcode of the name, because it is fairly unique. */ public int hashCode() { return getName().hashCode(); } public boolean hasIndexes() { return (this.indexes != null) && (!this.indexes.isEmpty()); } /** * Return a list of the unique constraints for this table. * Used for DDL generation. */ public Map<String, List<List<String>>> getUniqueConstraints() { if (this.uniqueConstraints == null) { this.uniqueConstraints = new HashMap<String, List<List<String>>>(); } return this.uniqueConstraints; } /** * Determine whether the receiver has any identification information. * Return true if the name or qualifier of the receiver are nonempty. */ public boolean hasName() { if ((getName().length() == 0) && (getTableQualifier().length() == 0)) { return false; } return true; } /** * INTERNAL: * Is this decorated / has an AS OF (some past time) clause. * <b>Example:</b> * SELECT ... FROM EMPLOYEE AS OF TIMESTAMP (exp) t0 ... */ public boolean isDecorated() { return false; } protected void resetQualifiedName() { this.qualifiedName = null; } public void setCreationSuffix(String creationSuffix) { this.creationSuffix = creationSuffix; } /** * Set the table name. * Used when aliasing table names. * @param name */ public void setName(String name) { setName(name, null, null); } /** * Set the table name. * Used when aliasing table names. * * If the name contains database delimiters, they will be stripped and a flag will be set to have them * added when the DatabaseTable is written to SQL * * @param name */ public void setName(String name, String startDelimiter, String endDelimiter) { if (name != null && (startDelimiter != null) && (endDelimiter != null) && !startDelimiter.equals("")&& !endDelimiter.equals("") && name.startsWith(startDelimiter) && name.endsWith(endDelimiter)){ this.name = name.substring(startDelimiter.length(), name.length() - endDelimiter.length()); useDelimiters = true; } else { this.name = name ; } resetQualifiedName(); } /** * Used to map the project xml. Any time a string name is read from the * project xml, we must check if it is fully qualified and split the * actual name from the qualifier. * * @param possiblyQualifiedName */ public void setPossiblyQualifiedName(String possiblyQualifiedName) { setPossiblyQualifiedName(possiblyQualifiedName, null, null); } public void setPossiblyQualifiedName(String possiblyQualifiedName, String startDelimiter, String endDelimiter) { resetQualifiedName(); int index = possiblyQualifiedName.lastIndexOf('.'); if (index == -1) { setName(possiblyQualifiedName, startDelimiter, endDelimiter); this.tableQualifier = ""; } else { setName(possiblyQualifiedName.substring(index + 1, possiblyQualifiedName.length()), startDelimiter, endDelimiter); setTableQualifier(possiblyQualifiedName.substring(0, index), startDelimiter, endDelimiter); if((startDelimiter != null) && possiblyQualifiedName.startsWith(startDelimiter) && (endDelimiter != null) && possiblyQualifiedName.endsWith(endDelimiter)) { // It's 'Qualifier.Name' - it should be treated as a single string. // Would that be 'Qualifier'.'Name' both setName and setTableQualifier methods would have set useDelimeters to true. if(!this.useDelimiters) { setName(possiblyQualifiedName); this.tableQualifier = ""; } } } } public void setTableQualifier(String qualifier) { setTableQualifier(qualifier, null, null); } public void setTableQualifier(String qualifier, String startDelimiter, String endDelimiter) { if ((startDelimiter != null) && (endDelimiter != null) && !startDelimiter.equals("")&& !endDelimiter.equals("") && qualifier.startsWith(startDelimiter) && qualifier.endsWith(endDelimiter)){ this.tableQualifier = qualifier.substring(startDelimiter.length(), qualifier.length() - endDelimiter.length()); useDelimiters = true; } else { this.tableQualifier = qualifier; } resetQualifiedName(); } public String toString() { return "DatabaseTable(" + getQualifiedName() + ")"; } public void setUseDelimiters(boolean useDelimiters) { this.useDelimiters = useDelimiters; } public boolean shouldUseDelimiters() { return useDelimiters; } }