/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 | |
* 03/24/2011-2.3 Guy Pelletier | |
* - 337323: Multi-tenant with shared schema support (part 1) | |
* 05/30/2012-2.4 Guy Pelletier | |
* - 354678: Temp classloader is still being used during metadata processing | |
* 02/11/2013-2.5 Guy Pelletier | |
* - 365931: @JoinColumn(name="FK_DEPT",insertable = false, updatable = true) causes INSERT statement to include this data value that it is associated with | |
******************************************************************************/ | |
package org.eclipse.persistence.internal.helper; | |
//javase imports | |
import java.io.Serializable; | |
import java.security.AccessController; | |
import java.security.PrivilegedActionException; | |
import static java.lang.Integer.MIN_VALUE; | |
//EclipseLink imports | |
import org.eclipse.persistence.exceptions.ValidationException; | |
import org.eclipse.persistence.internal.core.helper.CoreField; | |
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; | |
import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform; | |
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; | |
import org.eclipse.persistence.internal.security.PrivilegedClassForName; | |
/** | |
* INTERNAL: | |
* <p><b>Purpose</b>: | |
* Define a fully qualified field name.<p> | |
* <b>Responsibilities</b>: <ul> | |
* <li> Know its name and its table. | |
* </ul> | |
* @see DatabaseTable | |
*/ | |
public class DatabaseField implements Cloneable, Serializable, CoreField { | |
/** Variables used for generating DDL **/ | |
protected int scale; | |
protected int length; | |
protected int precision; | |
protected boolean isUnique; | |
protected boolean isNullable; | |
protected boolean isUpdatable; | |
protected boolean isInsertable; | |
protected boolean isCreatable; | |
protected boolean isPrimaryKey; | |
protected String columnDefinition; | |
/** Column name of the field. */ | |
protected String name; | |
/** PERF: Cache fully qualified table.field-name. */ | |
protected String qualifiedName; | |
/** Fields table (encapsulates name + creator). */ | |
protected DatabaseTable table; | |
/** | |
* Respective Java type desired for the field's value, used to optimize performance and for binding. | |
* PERF: Allow direct variable access from getObject. | |
*/ | |
public transient Class type; | |
public String typeName; // shadow variable - string name of above Class type variable | |
/** | |
* Respective JDBC type of the field's value. | |
* This overrides the class type, which the JDBC type is normally computed from. | |
* PERF: Allow direct variable access from getObject. | |
*/ | |
public int sqlType; | |
/** | |
* Store normal index of field in result set to optimize performance. | |
* PERF: Allow direct variable access from getIndicatingNoEntry. | |
*/ | |
public int index; | |
protected boolean useDelimiters = false; | |
/** | |
* If this is set, it will be used in determining equality (unless delimiters are used) and the hashcode. | |
* @see getNameForComparisons | |
*/ | |
protected String nameForComparisons; | |
/** | |
* setting to true will cause getNameForComparisons to lazy initialize nameForComparisons using | |
* the value from getName().toUpperCase(). | |
*/ | |
protected boolean useUpperCaseForComparisons = false; | |
/** | |
* used to represent the value when it has not being defined | |
*/ | |
public static final int NULL_SQL_TYPE = MIN_VALUE; | |
/** | |
* Returns true if this field was translated. | |
*/ | |
protected boolean isTranslated = false; | |
/** | |
* Indicates whether the field should be kept in the record after the object is created. | |
* Used by ObjectLevelReadQuery ResultSetAccessOptimization. | |
*/ | |
public boolean keepInRow; | |
public DatabaseField() { | |
this("", new DatabaseTable()); | |
} | |
public DatabaseField(String qualifiedName) { | |
this(qualifiedName, null, null); | |
} | |
public DatabaseField(String qualifiedName, String startDelimiter, String endDelimiter) { | |
this.index = -1; | |
this.sqlType = NULL_SQL_TYPE; | |
int index = qualifiedName.lastIndexOf('.'); | |
if (index == -1) { | |
setName(qualifiedName, startDelimiter, endDelimiter); | |
this.table = new DatabaseTable(); | |
} else { | |
setName(qualifiedName.substring(index + 1, qualifiedName.length()), startDelimiter, endDelimiter); | |
this.table = new DatabaseTable(qualifiedName.substring(0, index), startDelimiter, endDelimiter); | |
} | |
initDDLFields(); | |
} | |
public DatabaseField(String fieldName, String tableName) { | |
this(fieldName, new DatabaseTable(tableName)); | |
} | |
public DatabaseField(String fieldName, DatabaseTable databaseTable) { | |
this(fieldName, databaseTable, null, null); | |
} | |
public DatabaseField(String fieldName, DatabaseTable databaseTable, String startDelimiter, String endDelimiter) { | |
this.index = -1; | |
this.sqlType = NULL_SQL_TYPE; | |
setName(fieldName, startDelimiter, endDelimiter); | |
this.table = databaseTable; | |
initDDLFields(); | |
} | |
/** | |
* Inits the DDL generation fields with our defaults. Note: we used to | |
* initialize the length to the JPA default of 255 but since this default | |
* value should only apply for string fields we set it to 0 to indicate | |
* that it was not specified and rely on the default (255) to come from | |
* individual platforms. | |
*/ | |
public void initDDLFields() { | |
scale = 0; | |
length = 0; | |
precision = 0; | |
isUnique = false; | |
isNullable = true; | |
isUpdatable = true; | |
isInsertable = true; | |
isCreatable = true; | |
isPrimaryKey = false; | |
columnDefinition = ""; | |
} | |
/** | |
* The table is not cloned because it is treated as an automatic value. | |
*/ | |
public DatabaseField clone() { | |
try { | |
return (DatabaseField)super.clone(); | |
} catch (CloneNotSupportedException exception) { | |
throw new InternalError(exception.getMessage()); | |
} | |
} | |
/* | |
* INTERNAL: | |
* Convert all the class-name-based settings in this mapping to actual | |
* class-based settings. This method is implemented by subclasses as | |
* necessary. | |
* @param classLoader | |
*/ | |
public void convertClassNamesToClasses(ClassLoader classLoader) { | |
if (type == null && typeName != null) { | |
try { | |
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ | |
try { | |
type = (Class) AccessController.doPrivileged(new PrivilegedClassForName(typeName, true, classLoader)); | |
} catch (PrivilegedActionException e) { | |
throw ValidationException.classNotFoundWhileConvertingClassNames(typeName, e.getException()); | |
} | |
} else { | |
type = org.eclipse.persistence.internal.security.PrivilegedAccessHelper.getClassForName(typeName, true, classLoader); | |
} | |
} catch (Exception exception) { | |
throw ValidationException.classNotFoundWhileConvertingClassNames(typeName, exception); | |
} | |
} | |
} | |
/** | |
* Determine whether the receiver is equal to a DatabaseField. | |
* Return true if the receiver and field have the same name and table. | |
* Also return true if the table of the receiver or field are unspecified, | |
* ie. have no name. | |
*/ | |
public boolean equals(Object object) { | |
if (!(object instanceof DatabaseField)) { | |
return false; | |
} | |
return equals((DatabaseField)object); | |
} | |
/** | |
* Determine whether the receiver is equal to a DatabaseField. | |
* Return true if the receiver and field have the same name and table. | |
* Also return true if the table of the receiver or field are unspecified, | |
* ie. have no name. | |
*/ | |
public boolean equals(DatabaseField field) { | |
if (this == field) { | |
return true; | |
} | |
if (field != null) { | |
// PERF: Optimize common cases first. | |
// PERF: Use direct variable access. | |
if (getQualifiedName().equals(field.getQualifiedName())) { | |
return true; | |
} | |
//preserve old behavior if static shouldIgnoreCaseOnFieldComparisons is set | |
if (DatabasePlatform.shouldIgnoreCaseOnFieldComparisons()) { | |
if (this.name.equalsIgnoreCase(field.name)) { | |
//getTableName will cause NPE if there isn't a table. use hasTableName instead | |
if ((!hasTableName()) || (!field.hasTableName())) { | |
return true; | |
} | |
return (this.table.equals(field.table)); | |
} | |
} else { | |
String ourNameToCompare; | |
String fieldNameToCompare; | |
if (field.shouldUseDelimiters() || shouldUseDelimiters()) { | |
ourNameToCompare = this.name; | |
fieldNameToCompare = field.name; | |
} else { | |
ourNameToCompare = getNameForComparisons(); | |
fieldNameToCompare = field.getNameForComparisons(); | |
} | |
if (this.name.equals(field.name) || ourNameToCompare.equals(fieldNameToCompare)) { | |
//getTableName will cause NPE if there isn't a table. use hasTableName instead | |
if ((!hasTableName()) || (!field.hasTableName())) { | |
return true; | |
} | |
return (this.table.equals(field.table)); | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* Get the SQL fragment that is used when generating the DDL for the column. | |
*/ | |
public String getColumnDefinition() { | |
return this.columnDefinition; | |
} | |
/** | |
* Return the expected index that this field will occur in the result set | |
* row. This is used to optimize performance of database row field lookups. | |
*/ | |
public int getIndex() { | |
return index; | |
} | |
/** | |
* Used to specify the column length when generating DDL. | |
*/ | |
public int getLength() { | |
return this.length; | |
} | |
/** | |
* Return the unqualified name of the field. | |
*/ | |
@Override | |
public String getName() { | |
return name; | |
} | |
/** | |
* Returns this fields name with database delimiters if useDelimiters is true. | |
* This method should be called any time the field name is requested for writing SQL. | |
*/ | |
public String getNameDelimited(DatasourcePlatform platform) { | |
if (this.useDelimiters){ | |
return platform.getStartDelimiter() + this.name + platform.getEndDelimiter(); | |
} | |
return this.name; | |
} | |
/** | |
* Returns the precision for a decimal column when generating DDL. | |
*/ | |
public int getPrecision() { | |
return this.precision; | |
} | |
public String getQualifiedName(){ | |
if (this.qualifiedName == null) { | |
if (hasTableName()) { | |
this.qualifiedName = this.table.getQualifiedName() + "." + getName(); | |
} else { | |
this.qualifiedName = getName(); | |
} | |
} | |
return this.qualifiedName; | |
} | |
/** | |
* Return the qualified name of the field. | |
* PERF: Cache the qualified name. | |
*/ | |
public String getQualifiedNameDelimited(DatasourcePlatform platform) { | |
if (hasTableName()) { | |
return this.table.getQualifiedNameDelimited(platform) + "." + getNameDelimited(platform); | |
} else { | |
return getNameDelimited(platform); | |
} | |
} | |
/** | |
* Returns the scale for a decimal column when generating DDL. | |
*/ | |
public int getScale() { | |
return this.scale; | |
} | |
public DatabaseTable getTable() { | |
return table; | |
} | |
public String getTableName() { | |
return getTable().getName(); | |
} | |
public void setTableName(String tableName) { | |
setTable(new DatabaseTable(tableName)); | |
} | |
@Override | |
public Class getType() { | |
if ((this.type == null) && (this.typeName != null)) { | |
convertClassNamesToClasses(getClass().getClassLoader()); | |
} | |
return this.type; | |
} | |
public String getTypeName() { | |
return typeName; | |
} | |
public void setTypeName(String typeName) { | |
this.typeName = typeName; | |
} | |
/** | |
* Return the JDBC type that corresponds to the field. | |
* The JDBC type is normally determined from the class type, | |
* but this allows it to be overridden for types that do not match directly to a Java type, | |
* such as MONEY or ARRAY, STRUCT, XMLTYPE, etc. | |
* This can be used for binding or stored procedure usage. | |
*/ | |
public int getSqlType() { | |
return sqlType; | |
} | |
/** | |
* Return the hashcode of the name, because it is fairly unique. | |
*/ | |
public int hashCode() { | |
return getNameForComparisons().hashCode(); | |
} | |
public boolean hasTableName() { | |
if (this.table == null) { | |
return false; | |
} | |
if (this.table.getName() == null) { | |
return false; | |
} | |
return !(this.table.getName().equals("")); | |
} | |
/** | |
* PUBLIC: | |
* Return if this is an ObjectRelationalDatabaseField. | |
*/ | |
public boolean isObjectRelationalDatabaseField(){ | |
return false; | |
} | |
/** | |
* Used to specify whether the column should be included in SQL UPDATE | |
* statements. | |
*/ | |
public boolean isInsertable() { | |
return this.isInsertable; | |
} | |
/** | |
* Used for generating DDL. Returns true if the database column is | |
* nullable. | |
*/ | |
public boolean isNullable() { | |
return this.isNullable; | |
} | |
/** | |
* Used to specify whether the column should be included in the primary | |
* on the database table. | |
*/ | |
public boolean isPrimaryKey() { | |
return this.isPrimaryKey; | |
} | |
/** | |
* Return true if this database field is a translation. | |
*/ | |
public boolean isTranslated() { | |
return this.isTranslated; | |
} | |
/** | |
* Used for generating DDL. Returns true if the field is a unique key. | |
*/ | |
public boolean isUnique() { | |
return this.isUnique; | |
} | |
/** | |
* Returns true is this database field should be read only. | |
*/ | |
public boolean isReadOnly() { | |
return (! isUpdatable && ! isInsertable); | |
} | |
public boolean keepInRow() { | |
return keepInRow; | |
} | |
/** | |
* Returns whether the column should be included in SQL INSERT | |
* statements. | |
*/ | |
public boolean isUpdatable() { | |
return this.isUpdatable; | |
} | |
/** | |
* Reset the field's name and table from the qualified name. | |
*/ | |
public void resetQualifiedName(String qualifiedName) { | |
setIndex(-1); | |
int index = qualifiedName.lastIndexOf('.'); | |
if (index == -1) { | |
setName(qualifiedName); | |
getTable().setName(""); | |
getTable().setTableQualifier(""); | |
} else { | |
setName(qualifiedName.substring(index + 1, qualifiedName.length())); | |
getTable().setPossiblyQualifiedName(qualifiedName.substring(0, index)); | |
} | |
} | |
/** | |
* Set the SQL fragment that is used when generating the DDL for the column. | |
*/ | |
public void setColumnDefinition(String columnDefinition) { | |
this.columnDefinition = columnDefinition; | |
} | |
/** | |
* Set the expected index that this field will occur in the result set row. | |
* This is used to optimize performance of database row field lookups. | |
*/ | |
public void setIndex(int index) { | |
this.index = index; | |
} | |
/** | |
* Used to specify whether the column should be included in SQL UPDATE | |
* statements. | |
*/ | |
public void setInsertable(boolean isInsertable) { | |
this.isInsertable = isInsertable; | |
} | |
public void setKeepInRow(boolean keepInRow) { | |
this.keepInRow = keepInRow; | |
} | |
/** | |
* Set the isTranslated flag. | |
*/ | |
public void setIsTranslated(boolean isTranslated) { | |
this.isTranslated = isTranslated; | |
} | |
/** | |
* Used to specify the column length when generating DDL. | |
*/ | |
public void setLength(int length) { | |
this.length = length; | |
} | |
/** | |
* Set the unqualified name of the field. | |
*/ | |
@Override | |
public void setName(String name) { | |
setName(name, null, null); | |
} | |
/** | |
* Set the unqualified name of the field. | |
* | |
* If the name contains database delimiters, they will be stripped and a flag will be set to have them | |
* added when the DatabaseField is written to SQL | |
*/ | |
public void setName(String name, DatasourcePlatform platform){ | |
setName(name, platform.getStartDelimiter(), platform.getEndDelimiter()); | |
} | |
/** | |
* Set the unqualified name of the field. | |
* | |
* If the name contains database delimiters, they will be stripped and a flag will be set to have them | |
* added when the DatabaseField is written to SQL | |
*/ | |
public void setName(String name, String startDelimiter, String endDelimiter) { | |
if ((startDelimiter != null) && (endDelimiter != null) && !startDelimiter.equals("")&& !endDelimiter.equals("") && name.startsWith(startDelimiter) && name.endsWith(endDelimiter)){ | |
this.name = name.substring(startDelimiter.length(), name.length() - endDelimiter.length()); | |
this.useDelimiters = true; | |
} else { | |
this.name = name; | |
} | |
this.nameForComparisons = null; | |
this.qualifiedName = null; | |
} | |
/** | |
* Used for generating DDL. Set to true if the database column is | |
* nullable. | |
*/ | |
public void setNullable(boolean isNullable) { | |
this.isNullable = isNullable; | |
} | |
/** | |
* Used to specify the precision for a decimal column when generating DDL. | |
*/ | |
public void setPrecision(int precision) { | |
this.precision = precision; | |
} | |
/** | |
* Used to specify whether the column should be included in primary key | |
* on the database table. | |
*/ | |
public void setPrimaryKey(boolean isPrimaryKey) { | |
this.isPrimaryKey = isPrimaryKey; | |
} | |
/** | |
* Used to specify the scale for a decimal column when generating DDL. | |
*/ | |
public void setScale(int scale) { | |
this.scale = scale; | |
} | |
/** | |
* Set the JDBC type that corresponds to the field. | |
* The JDBC type is normally determined from the class type, | |
* but this allows it to be overridden for types that do not match directly | |
* to a Java type, such as MONEY or ARRAY, STRUCT, XMLTYPE, etc. | |
* This can be used for binding or stored procedure usage. | |
*/ | |
public void setSqlType(int sqlType) { | |
this.sqlType = sqlType; | |
} | |
/** | |
* Set the table for the field. | |
*/ | |
public void setTable(DatabaseTable table) { | |
this.table = table; | |
this.qualifiedName = null; | |
} | |
/** | |
* Set the Java class type that corresponds to the field. | |
* The JDBC type is determined from the class type, | |
* this is used to optimize performance, and for binding. | |
*/ | |
@Override | |
public void setType(Class type) { | |
this.type = type; | |
if (this.type != null && typeName == null) { | |
typeName = this.type.getName(); | |
} | |
} | |
/** | |
* Used for generating DDL. Set to true if the field is a unique key. | |
*/ | |
public void setUnique(boolean isUnique) { | |
this.isUnique = isUnique; | |
} | |
/** | |
* Used to specify whether the column should be included in SQL INSERT | |
* statements. | |
*/ | |
public void setUpdatable(boolean isUpdatable) { | |
this.isUpdatable = isUpdatable; | |
} | |
public String toString() { | |
return this.getQualifiedName(); | |
} | |
public void setUseDelimiters(boolean useDelimiters) { | |
this.useDelimiters = useDelimiters; | |
} | |
public boolean shouldUseDelimiters() { | |
return this.useDelimiters; | |
} | |
/** | |
* INTERNAL: | |
* Sets the useUpperCaseForComparisons flag which is used to force using the uppercase version of the field's | |
* name to determine field equality and its hashcode, but will still use the original name when writing/printing | |
* operations. If this isn't a change, it is ignored, otherwise it sets the nameForComparisons to null. | |
*/ | |
public void useUpperCaseForComparisons(boolean useUpperCaseForComparisons){ | |
if (this.useUpperCaseForComparisons != useUpperCaseForComparisons){ | |
this.useUpperCaseForComparisons = useUpperCaseForComparisons; | |
this.setNameForComparisons(null); | |
} | |
} | |
public boolean getUseUpperCaseForComparisons(){ | |
return this.useUpperCaseForComparisons; | |
} | |
/** | |
* INTERNAL: | |
* sets the string to be used for equality checking and determining the hashcode of this field. | |
* This will overwrite the useUpperCaseForEquality setting with the passed in string. | |
*/ | |
public void setNameForComparisons(String name){ | |
this.nameForComparisons = name; | |
} | |
public boolean isCreatable() { | |
return isCreatable; | |
} | |
public void setCreatable(boolean isCreatable) { | |
this.isCreatable = isCreatable; | |
} | |
/** | |
* INTERNAL: | |
* gets the string used for comparisons and in determining the hashcode. | |
*/ | |
public String getNameForComparisons(){ | |
if (this.nameForComparisons == null) { | |
if ((!this.useUpperCaseForComparisons) || (this.name == null)) { | |
this.nameForComparisons = this.name; | |
} else { | |
this.nameForComparisons = this.name.toUpperCase(); | |
} | |
} | |
return this.nameForComparisons; | |
} | |
} |