/******************************************************************************* | |
* 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 | |
* 04/01/2011-2.3 Guy Pelletier | |
* - 337323: Multi-tenant with shared schema support (part 2) | |
* 08/18/2011-2.3.1 Guy Pelletier | |
* - 355093: Add new 'includeCriteria' flag to Multitenant metadata | |
* 09/09/2011-2.3.1 Guy Pelletier | |
* - 356197: Add new VPD type to MultitenantType | |
* 08/01/2012-2.5 Chris Delahunt | |
* - 371950: Metadata caching | |
******************************************************************************/ | |
package org.eclipse.persistence.descriptors; | |
import java.io.*; | |
import java.lang.reflect.*; | |
import java.security.AccessController; | |
import java.security.PrivilegedActionException; | |
import java.util.*; | |
import org.eclipse.persistence.core.descriptors.CoreInheritancePolicy; | |
import org.eclipse.persistence.descriptors.invalidation.CacheInvalidationPolicy; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy; | |
import org.eclipse.persistence.internal.expressions.*; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.internal.queries.*; | |
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; | |
import org.eclipse.persistence.internal.security.PrivilegedClassForName; | |
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.mappings.*; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.sessions.remote.*; | |
/** | |
* <p><b>Purpose</b>: Allows customization of an object's inheritance. | |
* The primary supported inheritance model uses a class type indicator | |
* column in the table that stores the object's class type. | |
* The class-to-type mapping is specified on this policy. | |
* The full class name can also be used for the indicator instead of the mapping. | |
* <p>Each subclass can either share their parents table, or in addition add their | |
* own table(s). | |
* <p>For legacy models a customized inheritance class-extractor can be provided. | |
* This allows Java code to be used to compute the class type to use for a row. | |
* When this customized inheritance model is used an only-instances and with-all-subclasses | |
* filter expression may be required for concrete and branch querying. | |
*/ | |
public class InheritancePolicy extends CoreInheritancePolicy<AbstractRecord, AbstractSession, ClassDescriptor, DatabaseField> implements Serializable, Cloneable { | |
protected Class parentClass; | |
protected String parentClassName; | |
protected ClassDescriptor parentDescriptor; | |
protected List<ClassDescriptor> childDescriptors; | |
protected DatabaseField classIndicatorField; | |
protected transient Map classIndicatorMapping; | |
protected Map classNameIndicatorMapping; | |
protected transient boolean shouldUseClassNameAsIndicator; | |
protected transient Boolean shouldReadSubclasses; | |
protected DatabaseTable readAllSubclassesView; | |
protected transient List<Object> allChildClassIndicators; | |
protected transient Expression onlyInstancesExpression; | |
protected transient Expression withAllSubclassesExpression; | |
// null if there are no childrenTables, otherwise all tables for reference class plus childrenTables | |
protected transient Vector allTables; | |
// all tables for all subclasses (subclasses of subclasses included), should be in sync with childrenTablesJoinExpressions. | |
protected transient List<DatabaseTable> childrenTables; | |
// join expression for each child table, keyed by the table, should be in sync with childrenTables. | |
protected transient Map<DatabaseTable, Expression> childrenTablesJoinExpressions; | |
// all expressions from childrenTablesJoinExpressions ANDed together | |
protected transient Expression childrenJoinExpression; | |
/** Allow for class extraction method to be specified. */ | |
protected String classExtractorName; | |
protected transient ClassExtractor classExtractor; | |
protected ClassDescriptor descriptor; | |
protected boolean shouldAlwaysUseOuterJoin = false; | |
//CR 4005 | |
protected boolean useDescriptorsToValidateInheritedObjects = false; | |
/** Define if an outer join should be used to read subclasses. */ | |
protected boolean shouldOuterJoinSubclasses = false; | |
// used by the entity-mappings XML writer to determine inheritance strategy | |
protected boolean isJoinedStrategy; | |
/** PERF: Cache root descriptor. */ | |
protected ClassDescriptor rootParentDescriptor; | |
protected boolean describesNonPersistentSubclasses = false; | |
/** | |
* INTERNAL: | |
* Create a new policy. | |
* Only descriptors involved in inheritance should have a policy. | |
*/ | |
public InheritancePolicy() { | |
this.classIndicatorMapping = new HashMap(10); | |
this.classNameIndicatorMapping = new HashMap(10); | |
this.shouldUseClassNameAsIndicator = false; | |
this.allChildClassIndicators = new ArrayList(4); | |
this.childDescriptors = new ArrayList(4); | |
this.setJoinedStrategy(); | |
} | |
/** | |
* INTERNAL: | |
* Create a new policy. | |
* Only descriptors involved in inheritance should have a policy. | |
*/ | |
public InheritancePolicy(ClassDescriptor descriptor) { | |
this(); | |
this.descriptor = descriptor; | |
} | |
/** | |
* INTERNAL: | |
* Add child descriptor to the parent descriptor. | |
*/ | |
public void addChildDescriptor(ClassDescriptor childDescriptor) { | |
getChildDescriptors().add(childDescriptor); | |
} | |
/** | |
* INTERNAL: | |
* childrenTablesJoinExpressions, childrenTables, allTables and childrenJoinExpression | |
* are created simultaneously and kept in sync. | |
*/ | |
protected void addChildTableJoinExpression(DatabaseTable table, Expression expression) { | |
if (this.childrenTablesJoinExpressions == null) { | |
this.childrenTablesJoinExpressions = new HashMap(); | |
// childrenTables should've been null, too | |
this.childrenTables = new ArrayList(); | |
// allTables should've been null, too | |
this.allTables = new Vector(getDescriptor().getTables()); | |
} | |
// Avoid duplicates as two independent subclasses may have the same table. | |
if (!this.childrenTables.contains(table)) { | |
this.childrenTables.add(table); | |
} | |
if (!this.allTables.contains(table)) { | |
this.allTables.add(table); | |
} | |
this.childrenTablesJoinExpressions.put(table, expression); | |
this.childrenJoinExpression = expression.and(this.childrenJoinExpression); | |
} | |
/** | |
* INTERNAL: | |
* call addChildTableJoinExpression on all parents | |
*/ | |
public void addChildTableJoinExpressionToAllParents(DatabaseTable table, Expression expression) { | |
ClassDescriptor parentDescriptor = getParentDescriptor(); | |
while(parentDescriptor != null) { | |
InheritancePolicy parentPolicy = parentDescriptor.getInheritancePolicy(); | |
parentPolicy.addChildTableJoinExpression(table, expression); | |
parentDescriptor = parentPolicy.getParentDescriptor(); | |
} | |
} | |
/** | |
* PUBLIC: | |
* Add a class indicator for the root classes subclass. | |
* The indicator is used to determine the class to use for a row read from the database, | |
* and to query only instances of a class from the database. | |
* Every concrete persistent subclass must have a single unique indicator defined for it. | |
* If the root class is concrete then it must also define an indicator. | |
* Only the root class's descriptor of the entire inheritance hierarchy can define the class indicator mapping. | |
*/ | |
public void addClassIndicator(Class childClass, Object typeValue) { | |
// Note we should think about supporting null values. | |
// Store as key and value for bi-directional lookup. | |
getClassIndicatorMapping().put(typeValue, childClass); | |
getClassIndicatorMapping().put(childClass, typeValue); | |
} | |
/** | |
* INTERNAL: | |
* Add the class name reference by class name, used by the MW. | |
*/ | |
public void addClassNameIndicator(String childClassName, Object typeValue) { | |
getClassNameIndicatorMapping().put(childClassName, typeValue); | |
} | |
/** | |
* INTERNAL: | |
* Add abstract class indicator information to the database row. This is | |
* required when building a row for an insert or an update of a concrete child | |
* descriptor. | |
* This is only used to build a template row. | |
*/ | |
public void addClassIndicatorFieldToInsertRow(AbstractRecord databaseRow) { | |
if (hasClassExtractor()) { | |
return; | |
} | |
DatabaseField field = getClassIndicatorField(); | |
databaseRow.put(field, null); | |
} | |
/** | |
* INTERNAL: | |
* Add abstract class indicator information to the database row. This is | |
* required when building a row for an insert or an update of a concrete child | |
* descriptor. | |
*/ | |
public void addClassIndicatorFieldToRow(AbstractRecord databaseRow) { | |
if (hasClassExtractor()) { | |
return; | |
} | |
DatabaseField field = getClassIndicatorField(); | |
Object value = getClassIndicatorValue(); | |
databaseRow.put(field, value); | |
} | |
/** | |
* INTERNAL: | |
* Post initialize the child descriptors | |
*/ | |
protected void addClassIndicatorTypeToParent(Object indicator) { | |
ClassDescriptor parentDescriptor = getDescriptor().getInheritancePolicy().getParentDescriptor(); | |
if (parentDescriptor.getInheritancePolicy().isChildDescriptor()) { | |
if (parentDescriptor.getInheritancePolicy().shouldReadSubclasses()) { | |
parentDescriptor.getInheritancePolicy().getAllChildClassIndicators().add(indicator); | |
} | |
parentDescriptor.getInheritancePolicy().addClassIndicatorTypeToParent(indicator); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Recursively adds fields to all the parents | |
*/ | |
protected void addFieldsToParent(Vector fields) { | |
if (isChildDescriptor()) { | |
if (getParentDescriptor().isInvalid()) { | |
return; | |
} | |
ClassDescriptor parentDescriptor = getParentDescriptor(); | |
if (parentDescriptor.getInheritancePolicy().shouldReadSubclasses()) { | |
Helper.addAllUniqueToVector(parentDescriptor.getAllFields(), fields); | |
} | |
parentDescriptor.getInheritancePolicy().addFieldsToParent(fields); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Return a select statement that will be used to query the class indicators required to query. | |
* This is used in the abstract-multiple read. | |
*/ | |
public SQLSelectStatement buildClassIndicatorSelectStatement(ObjectLevelReadQuery query) { | |
SQLSelectStatement selectStatement; | |
selectStatement = new SQLSelectStatement(); | |
selectStatement.useDistinct(); | |
selectStatement.addTable(classIndicatorField.getTable()); | |
selectStatement.addField(getClassIndicatorField()); | |
// 2612538 - the default size of Map (32) is appropriate | |
Map clonedExpressions = new IdentityHashMap(); | |
selectStatement.setWhereClause(((ExpressionQueryMechanism)query.getQueryMechanism()).buildBaseSelectionCriteria(false, clonedExpressions)); | |
appendWithAllSubclassesExpression(selectStatement); | |
selectStatement.setTranslationRow(query.getTranslationRow()); | |
if (query.isReadAllQuery() && ((ReadAllQuery)query).hasHierarchicalExpressions()) { | |
ReadAllQuery readAllQuery = (ReadAllQuery)query; | |
selectStatement.setHierarchicalQueryExpressions(readAllQuery.getStartWithExpression(), readAllQuery.getConnectByExpression(), readAllQuery.getOrderSiblingsByExpressions()); | |
} | |
selectStatement.setHintString(query.getHintString()); | |
selectStatement.normalize(query.getSession(), getDescriptor(), clonedExpressions); | |
return selectStatement; | |
} | |
/** | |
* INTERNAL: | |
* Append the branch with all subclasses expression to the statement. | |
*/ | |
public void appendWithAllSubclassesExpression(SQLSelectStatement selectStatement) { | |
if (getWithAllSubclassesExpression() != null) { | |
// For Flashback: Must always rebuild with simple expression on right. | |
if (selectStatement.getWhereClause() == null) { | |
selectStatement.setWhereClause((Expression)getWithAllSubclassesExpression().clone()); | |
} else { | |
selectStatement.setWhereClause(selectStatement.getWhereClause().and(getWithAllSubclassesExpression())); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Build a select statement for all subclasses on the view using the same | |
* selection criteria as the query. | |
*/ | |
public SQLSelectStatement buildViewSelectStatement(ObjectLevelReadQuery query) { | |
// 2612538 - the default size of Map (32) is appropriate | |
Map clonedExpressions = new IdentityHashMap(); | |
ExpressionQueryMechanism mechanism = (ExpressionQueryMechanism)query.getQueryMechanism(); | |
// CR#3166555 - Have the mechanism build the statement to avoid duplicating code and ensure that lock-mode, hints, hierarchical, etc. are set. | |
SQLSelectStatement selectStatement = mechanism.buildBaseSelectStatement(false, clonedExpressions); | |
selectStatement.setTables(org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1)); | |
selectStatement.addTable(getReadAllSubclassesView()); | |
// Case, normal read for branch inheritance class that reads subclasses all in its own table(s). | |
if (getWithAllSubclassesExpression() != null) { | |
Expression branchIndicator = (Expression)getWithAllSubclassesExpression().clone(); | |
if (branchIndicator != null) { | |
selectStatement.setWhereClause(branchIndicator.and(selectStatement.getWhereClause())); | |
} | |
} | |
selectStatement.setFields(mechanism.getSelectionFields(selectStatement, true)); | |
selectStatement.normalizeForView(query.getSession(), getDescriptor(), clonedExpressions); | |
// Allow for joining indexes to be computed to ensure distinct rows | |
if (query.hasJoining()) { | |
query.getJoinedAttributeManager().computeJoiningMappingIndexes(false, query.getSession(), 0); | |
} | |
return selectStatement; | |
} | |
/** | |
* INTERNAL: | |
* This method is invoked only for the abstract descriptors. | |
*/ | |
@Override | |
public Class classFromRow(AbstractRecord rowFromDatabase, AbstractSession session) throws DescriptorException { | |
if (hasClassExtractor()) { | |
return getClassExtractor().extractClassFromRow(rowFromDatabase, session); | |
} | |
Object classFieldValue = session.getDatasourcePlatform().getConversionManager().convertObject(rowFromDatabase.get(getClassIndicatorField()), getClassIndicatorField().getType()); | |
if (classFieldValue == null) { | |
throw DescriptorException.missingClassIndicatorField(rowFromDatabase, getDescriptor()); | |
} | |
return classFromValue(classFieldValue, session); | |
} | |
/** | |
* INTERNAL: | |
* This method is used to turn the a raw database field value classFieldValue into a Class object. Used to determine | |
* which class objects to build from database results, and for class type expression | |
*/ | |
public Class classFromValue(Object classFieldValue, AbstractSession session) throws DescriptorException { | |
Class concreteClass; | |
if (!shouldUseClassNameAsIndicator()) { | |
concreteClass = (Class)getClassIndicatorMapping().get(classFieldValue); | |
if (concreteClass == null) { | |
throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor()); | |
} | |
} else { | |
try { | |
String className = (String)classFieldValue; | |
//PWK 2.5.1.7 can not use class for name, must go through conversion manager. | |
//Should use the root ClassDescriptor's classloader to avoid loading from a loader other | |
//than the one that loaded the project | |
concreteClass = getDescriptor().getJavaClass().getClassLoader().loadClass(className); | |
if (concreteClass == null) { | |
throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor()); | |
} | |
} catch (ClassNotFoundException e) { | |
throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor()); | |
} catch (ClassCastException e) { | |
throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor()); | |
} | |
} | |
return concreteClass; | |
} | |
/** | |
* INTERNAL: | |
* Clone the policy | |
*/ | |
public Object clone() { | |
InheritancePolicy clone = null; | |
try { | |
clone = (InheritancePolicy)super.clone(); | |
if (hasClassIndicator()) { | |
clone.setClassIndicatorField(clone.getClassIndicatorField().clone()); | |
} | |
} catch (Exception exception) { | |
throw new InternalError("clone failed"); | |
} | |
return clone; | |
} | |
/** | |
* INTERNAL: | |
* Convert all the class-name-based settings in this InheritancePolicy to actual class-based settings. | |
* This method is used when converting a project that has been built with class names to a project with classes. | |
* It will also convert referenced classes to the versions of the classes from the classLoader. | |
*/ | |
public void convertClassNamesToClasses(ClassLoader classLoader) { | |
Iterator keysEnum = getClassNameIndicatorMapping().keySet().iterator(); | |
Iterator valuesEnum = getClassNameIndicatorMapping().values().iterator(); | |
// Clear old classes (after class names have been lazy initialized). | |
classIndicatorMapping = new HashMap(); | |
while (keysEnum.hasNext()) { | |
Object key = keysEnum.next(); | |
Object value = valuesEnum.next(); | |
Class theClass = convertClassNameToClass((String) key, classLoader); | |
classIndicatorMapping.put(theClass, value); | |
classIndicatorMapping.put(value, theClass); | |
} | |
// Initialize the parent class name. | |
if (getParentClassName() != null){ | |
setParentClass(convertClassNameToClass(getParentClassName(), classLoader)); | |
} | |
// Initialize the class extractor name. | |
if (classExtractorName != null) { | |
Class classExtractorClass = convertClassNameToClass(classExtractorName, classLoader); | |
try { | |
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) { | |
try { | |
setClassExtractor((ClassExtractor) AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(classExtractorClass))); | |
} catch (PrivilegedActionException exception) { | |
throw ValidationException.classNotFoundWhileConvertingClassNames(classExtractorName, exception.getException()); | |
} | |
} else { | |
setClassExtractor((ClassExtractor) PrivilegedAccessHelper.newInstanceFromClass(classExtractorClass)); | |
} | |
} catch (IllegalAccessException ex) { | |
throw ValidationException.reflectiveExceptionWhileCreatingClassInstance(classExtractorName, ex); | |
} catch (InstantiationException e) { | |
throw ValidationException.reflectiveExceptionWhileCreatingClassInstance(classExtractorName, e); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Convert the given className to an actual class. | |
*/ | |
protected Class convertClassNameToClass(String className, ClassLoader classLoader) { | |
try { | |
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ | |
try { | |
return (Class) AccessController.doPrivileged(new PrivilegedClassForName(className, true, classLoader)); | |
} catch (PrivilegedActionException exception) { | |
throw ValidationException.classNotFoundWhileConvertingClassNames(className, (Exception)exception.getCause()); | |
} | |
} else { | |
return PrivilegedAccessHelper.getClassForName(className, true, classLoader); | |
} | |
} catch (ClassNotFoundException exc){ | |
throw ValidationException.classNotFoundWhileConvertingClassNames(className, exc); | |
} | |
} | |
/** | |
* PUBLIC: | |
* Set the descriptor to only read instance of itself when queried. | |
* This is used with inheritance to configure the result of queries. | |
* By default this is true for root inheritance descriptors, and false for all others. | |
*/ | |
public void dontReadSubclassesOnQueries() { | |
setShouldReadSubclasses(false); | |
} | |
/** | |
* PUBLIC: | |
* Set the descriptor not to use the class' full name as the indicator. | |
* The class indicator is used with inheritance to determine the class from a row. | |
* By default a class indicator mapping is required, this can be set to true if usage of the class name is desired. | |
* The field must be of a large enough size to store the fully qualified class name. | |
*/ | |
public void dontUseClassNameAsIndicator() { | |
setShouldUseClassNameAsIndicator(false); | |
} | |
/** | |
* INTERNAL: | |
* Stores class indicators for all child and children's children. | |
* Used for queries on branch classes only. | |
*/ | |
protected List<Object> getAllChildClassIndicators() { | |
if (allChildClassIndicators == null) { | |
allChildClassIndicators = new ArrayList(4); | |
} | |
return allChildClassIndicators; | |
} | |
/** | |
* INTERNAL: | |
* Returns all the child descriptors, even descriptors for subclasses of | |
* subclasses. | |
* Required for bug 3019934. | |
*/ | |
@Override | |
public List<ClassDescriptor> getAllChildDescriptors() { | |
// Guess the number of child descriptors... | |
List<ClassDescriptor> allChildDescriptors = new ArrayList(this.getAllChildClassIndicators().size()); | |
return getAllChildDescriptors(allChildDescriptors); | |
} | |
/** | |
* INTERNAL: | |
* Recursive subroutine of getAllChildDescriptors. | |
*/ | |
protected List<ClassDescriptor> getAllChildDescriptors(List<ClassDescriptor> allChildDescriptors) { | |
for (ClassDescriptor childDescriptor : getChildDescriptors()) { | |
allChildDescriptors.add(childDescriptor); | |
childDescriptor.getInheritancePolicyOrNull().getAllChildDescriptors(allChildDescriptors); | |
} | |
return allChildDescriptors; | |
} | |
/** | |
* INTERNAL: | |
* if reads subclasses, all tables for all read subclasses (indirect included). | |
*/ | |
public List<DatabaseTable> getChildrenTables() { | |
return childrenTables; | |
} | |
/** | |
* INTERNAL: | |
* join expression for each child table, keyed by the table | |
*/ | |
public Map<DatabaseTable, Expression> getChildrenTablesJoinExpressions() { | |
return childrenTablesJoinExpressions; | |
} | |
/** | |
* INTERNAL: | |
* all expressions from childrenTablesJoinExpressions ANDed together | |
*/ | |
public Expression getChildrenJoinExpression() { | |
return childrenJoinExpression; | |
} | |
/** | |
* INTERNAL: | |
* all tables for reference class plus childrenTables | |
*/ | |
public Vector getAllTables() { | |
if (allTables == null) { | |
return this.getDescriptor().getTables(); | |
} else { | |
return allTables; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Return all the immediate child descriptors. Only descriptors from | |
* direct subclasses are returned. | |
*/ | |
public List<ClassDescriptor> getChildDescriptors() { | |
return childDescriptors; | |
} | |
/** | |
* INTERNAL: | |
* Return all the classExtractionMethod | |
*/ | |
protected Method getClassExtractionMethod() { | |
if (classExtractor instanceof MethodClassExtractor) { | |
return ((MethodClassExtractor)classExtractor).getClassExtractionMethod(); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* ADVANCED: | |
* A class extraction method can be registered with the descriptor to override the default inheritance mechanism. | |
* This allows for a user defined class indicator in place of providing an explicit class indicator field. | |
* The method registered must be a static method on the class which has that descriptor. The method must take a | |
* Record as an argument (for example, a DatabaseRecord), and must return the class to use for that record. | |
* This method will be used to decide which class to instantiate when reading from the database. | |
* It is the application's responsibility to populate any typing information in the database required | |
* to determine the class from the record. | |
* If this method is used, then the class indicator field and mapping cannot be used, and in addition, | |
* the descriptor's withAllSubclasses and onlyInstances expressions must also be setup correctly. | |
* | |
* @see #setWithAllSubclassesExpression(Expression) | |
* @see #setOnlyInstancesExpression(Expression) | |
*/ | |
public String getClassExtractionMethodName() { | |
if (classExtractor instanceof MethodClassExtractor) { | |
return ((MethodClassExtractor)classExtractor).getClassExtractionMethodName(); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* ADVANCED: | |
* A class extractor can be registered with the descriptor to override the default inheritance mechanism. | |
* This allows for a user defined class indicator in place of providing an explicit class indicator field. | |
* The instance registered must extend the ClassExtractor class and implement the extractClass(Map) method. | |
* The method must take database row (a Record/Map) as an argument and must return the class to use for that row. | |
* This method will be used to decide which class to instantiate when reading from the database. | |
* It is the application's responsibility to populate any typing information in the database required | |
* to determine the class from the row, such as usage of a direct or transformation mapping for the type fields. | |
* If this method is used then the class indicator field and mapping cannot be used, and in addition, | |
* the descriptor's withAllSubclasses and onlyInstances expressions must also be setup correctly. | |
* | |
* @see #setWithAllSubclassesExpression(Expression) | |
* @see #setOnlyInstancesExpression(Expression) | |
*/ | |
public ClassExtractor getClassExtractor() { | |
return classExtractor; | |
} | |
/** | |
* ADVANCED: | |
* A class extractor can be registered with the descriptor to override the default inheritance mechanism. | |
* This allows for a user defined class indicator in place of providing an explicit class indicator field. | |
* The instance registered must extend the ClassExtractor class and implement the extractClass(Map) method. | |
* The method must take database row (a Record/Map) as an argument and must return the class to use for that row. | |
* This method will be used to decide which class to instantiate when reading from the database. | |
* It is the application's responsibility to populate any typing information in the database required | |
* to determine the class from the row, such as usage of a direct or transformation mapping for the type fields. | |
* If this method is used then the class indicator field and mapping cannot be used, and in addition, | |
* the descriptor's withAllSubclasses and onlyInstances expressions must also be setup correctly. | |
* | |
* @see #setWithAllSubclassesExpression(Expression) | |
* @see #setOnlyInstancesExpression(Expression) | |
*/ | |
public void setClassExtractor(ClassExtractor classExtractor) { | |
this.classExtractor = classExtractor; | |
} | |
/** | |
* ADVANCED: | |
* Set the class extractor class name. At descriptor initialize time this | |
* class will be converted to a Class and set as the ClassExtractor. This | |
* method is called from JPA. | |
* | |
* @see setClassExtractor for more information on the ClassExtractor class. | |
*/ | |
public void setClassExtractorName(String classExtractorName) { | |
this.classExtractorName = classExtractorName; | |
} | |
/** | |
* INTERNAL: | |
* Return the class indicator associations for XML. | |
* List of class-name/value associations. | |
*/ | |
public Vector getClassIndicatorAssociations() { | |
Vector associations = new Vector(getClassNameIndicatorMapping().size() / 2); | |
Iterator classesEnum = getClassNameIndicatorMapping().keySet().iterator(); | |
Iterator valuesEnum = getClassNameIndicatorMapping().values().iterator(); | |
while (classesEnum.hasNext()) { | |
Object className = classesEnum.next(); | |
// If the project was built in runtime is a class, MW is a string. | |
if (className instanceof Class) { | |
className = ((Class)className).getName(); | |
} | |
Object value = valuesEnum.next(); | |
associations.addElement(new TypedAssociation(className, value)); | |
} | |
return associations; | |
} | |
/** | |
* INTERNAL: | |
* Returns field that the class type indicator is store when using inheritance. | |
*/ | |
@Override | |
public DatabaseField getClassIndicatorField() { | |
return classIndicatorField; | |
} | |
/** | |
* PUBLIC: | |
* Return the class indicator field name. | |
* This is the name of the field in the table that stores what type of object this is. | |
*/ | |
public String getClassIndicatorFieldName() { | |
if (getClassIndicatorField() == null) { | |
return null; | |
} else { | |
return getClassIndicatorField().getQualifiedName(); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Return the association of indicators and classes using specified ConversionManager | |
*/ | |
@Override | |
public Map getClassIndicatorMapping() { | |
if (classIndicatorMapping == null) { | |
classIndicatorMapping = new HashMap(10); | |
} | |
return classIndicatorMapping; | |
} | |
/** | |
* INTERNAL: | |
* Return the mapping from class name to indicator, used by MW. | |
*/ | |
public Map getClassNameIndicatorMapping() { | |
if (classNameIndicatorMapping.isEmpty() && classIndicatorMapping!=null && !classIndicatorMapping.isEmpty()) { | |
Iterator keysEnum = classIndicatorMapping.keySet().iterator(); | |
Iterator valuesEnum = classIndicatorMapping.values().iterator(); | |
while (keysEnum.hasNext()) { | |
Object key = keysEnum.next(); | |
Object value = valuesEnum.next(); | |
if (key instanceof Class) { | |
String className = ((Class)key).getName(); | |
classNameIndicatorMapping.put(className, value); | |
} | |
} | |
} | |
return classNameIndicatorMapping; | |
} | |
/** | |
* INTERNAL: | |
* Returns value of the abstract class indicator for the Java class. | |
*/ | |
protected Object getClassIndicatorValue() { | |
return getClassIndicatorValue(getDescriptor().getJavaClass()); | |
} | |
/** | |
* INTERNAL: | |
* Returns the indicator field value for the given class | |
* If no abstract indicator mapping is specified, use the class name. | |
*/ | |
protected Object getClassIndicatorValue(Class javaClass) { | |
if (shouldUseClassNameAsIndicator()) { | |
return javaClass.getName(); | |
} else { | |
return getClassIndicatorMapping().get(javaClass); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Returns the descriptor which the policy belongs to. | |
*/ | |
@Override | |
public ClassDescriptor getDescriptor() { | |
return descriptor; | |
} | |
/** | |
* ADVANCED: | |
* Determines whether the descriptors using this inheritance policy | |
* should be used as descriptors for subclasses of the classes they | |
* describe if those subclasses do not have their own descriptor | |
* | |
* e.g. If Employee.class has a descriptor and EmployeeSubClass does | |
* not have a descriptor, if describesNonPersistenceSubclasses is true | |
* Employee's descriptor will be used as the descriptor for Employee | |
*/ | |
public boolean getDescribesNonPersistentSubclasses(){ | |
return describesNonPersistentSubclasses; | |
} | |
/** | |
* ADVANCED: | |
* Return the 'only instances expression'. | |
*/ | |
public Expression getOnlyInstancesExpression() { | |
return onlyInstancesExpression; | |
} | |
/** | |
* PUBLIC: | |
* Return the parent class. | |
*/ | |
public Class getParentClass() { | |
return parentClass; | |
} | |
/** | |
* INTERNAL: | |
* Return the parent class name. | |
*/ | |
public String getParentClassName() { | |
if ((parentClassName == null) && (parentClass != null)) { | |
parentClassName = parentClass.getName(); | |
} | |
return parentClassName; | |
} | |
/** | |
* INTERNAL: | |
* Return the parent descriptor. | |
*/ | |
public ClassDescriptor getParentDescriptor() { | |
return parentDescriptor; | |
} | |
/** | |
* INTERNAL: | |
* The view can be used to optimize/customize the query for all subclasses where they have multiple tables. | |
* This view can do the outer join, we require the view because we cannot generate dynamic platform independent SQL | |
* for outer joins (i.e. not possible to do so either). | |
*/ | |
public DatabaseTable getReadAllSubclassesView() { | |
return readAllSubclassesView; | |
} | |
/** | |
* ADVANCED: | |
* The view can be used to optimize/customize the query for all subclasses where they have multiple tables. | |
* This view can use outer joins or unions to combine the results of selecting from all of the subclass tables. | |
* If a view is not given then TopLink must make an individual call for each subclass. | |
*/ | |
public String getReadAllSubclassesViewName() { | |
if (getReadAllSubclassesView() == null) { | |
return null; | |
} | |
return getReadAllSubclassesView().getName(); | |
} | |
/** | |
* INTERNAL: | |
* Return the root parent descriptor | |
*/ | |
public ClassDescriptor getRootParentDescriptor() { | |
if (this.rootParentDescriptor == null) { | |
if (isRootParentDescriptor()) { | |
this.rootParentDescriptor = getDescriptor(); | |
} else { | |
this.rootParentDescriptor = getParentDescriptor().getInheritancePolicy().getRootParentDescriptor(); | |
} | |
} | |
return rootParentDescriptor; | |
} | |
/** | |
* INTERNAL: | |
* use aggregate in inheritance | |
*/ | |
public ClassDescriptor getSubclassDescriptor(Class theClass) { | |
if (hasChildren()) { | |
for (Iterator enumtr = getChildDescriptors().iterator(); enumtr.hasNext();) { | |
ClassDescriptor childDescriptor = (ClassDescriptor)enumtr.next(); | |
if (childDescriptor.getJavaClass().equals(theClass)) { | |
return childDescriptor; | |
} else { | |
ClassDescriptor descriptor = childDescriptor.getInheritancePolicy().getSubclassDescriptor(theClass); | |
if (descriptor != null) { | |
return descriptor; | |
} | |
} | |
} | |
} | |
return null; | |
} | |
/** | |
* INTERNAL: | |
* Returns descriptor corresponding to the class owning the policy or its subclass - otherwise null. | |
*/ | |
public ClassDescriptor getDescriptor(Class theClass) { | |
if(getDescriptor().getJavaClass().equals(theClass)) { | |
return getDescriptor(); | |
} else { | |
return getSubclassDescriptor(theClass); | |
} | |
} | |
/** | |
* INTERNAL: | |
* return if we should use the descriptor inheritance to determine | |
* if an object can be returned from the identity map or not. | |
*/ | |
public boolean getUseDescriptorsToValidateInheritedObjects() { | |
return useDescriptorsToValidateInheritedObjects; | |
} | |
/** | |
* ADVANCED: | |
* Return the Expression which gets all subclasses. | |
*/ | |
public Expression getWithAllSubclassesExpression() { | |
return withAllSubclassesExpression; | |
} | |
/** | |
* INTERNAL: | |
* Check if descriptor has children | |
*/ | |
public boolean hasChildren() { | |
return !getChildDescriptors().isEmpty(); | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public boolean hasClassExtractor() { | |
return getClassExtractor() != null; | |
} | |
/** | |
* INTERNAL: | |
* Checks if the class is involved in inheritance | |
*/ | |
public boolean hasClassIndicator() { | |
return getClassIndicatorField() != null; | |
} | |
/** | |
* INTERNAL: | |
* Return if any children of this descriptor require information from another table | |
* not specified at the parent level. | |
*/ | |
public boolean hasMultipleTableChild() { | |
return childrenTables != null; | |
} | |
/** | |
* INTERNAL: | |
* Return if a view is used for inheritance reads. | |
*/ | |
public boolean hasView() { | |
return getReadAllSubclassesView() != null; | |
} | |
/** | |
* INTERNAL: | |
* Initialize the inheritance properties of the descriptor once the mappings are initialized. | |
* This is done before formal postInitialize during the end of mapping initialize. | |
*/ | |
public void initialize(AbstractSession session) { | |
// Must reset this in the case that a child thinks it wants to read its subclasses. | |
if ((this.shouldReadSubclasses == null) || shouldReadSubclasses()) { | |
setShouldReadSubclasses(!getChildDescriptors().isEmpty()); | |
} | |
if (isChildDescriptor()) { | |
getDescriptor().setMappings(Helper.concatenateVectors(getParentDescriptor().getMappings(), getDescriptor().getMappings())); | |
getDescriptor().setQueryKeys(Helper.concatenateMaps(getParentDescriptor().getQueryKeys(), getDescriptor().getQueryKeys())); | |
addFieldsToParent(getDescriptor().getFields()); | |
// Parents fields must be first for indexing to work. | |
Vector parentsFields = (Vector)getParentDescriptor().getFields().clone(); | |
//bug fix on Oracle duplicate field SQL using "order by" | |
Helper.addAllUniqueToVector(parentsFields, getDescriptor().getFields()); | |
getDescriptor().setFields(parentsFields); | |
if (getClassIndicatorValue() != null) { | |
if (shouldReadSubclasses()) { | |
getAllChildClassIndicators().add(getClassIndicatorValue()); | |
} | |
addClassIndicatorTypeToParent(getClassIndicatorValue()); | |
} | |
initializeOptimisticLocking(); | |
if (!getDescriptor().hasReturningPolicy() && getParentDescriptor().hasReturningPolicy()) { | |
getDescriptor().setReturningPolicy(new ReturningPolicy()); | |
} | |
// create CMPPolicy on child if parent has one and it does not. Then copy individual fields | |
CMPPolicy parentCMPPolicy = getDescriptor().getInheritancePolicy().getParentDescriptor().getCMPPolicy(); | |
if (parentCMPPolicy != null) { | |
CMPPolicy cmpPolicy = getDescriptor().getCMPPolicy(); | |
if (cmpPolicy == null) { | |
cmpPolicy = new CMPPolicy(); | |
getDescriptor().setCMPPolicy(cmpPolicy); | |
} | |
//copy pessimistic locking policy from the parent if this child does not have one | |
if (parentCMPPolicy.hasPessimisticLockingPolicy() && !cmpPolicy.hasPessimisticLockingPolicy()) { | |
cmpPolicy.setPessimisticLockingPolicy((PessimisticLockingPolicy)parentCMPPolicy.getPessimisticLockingPolicy().clone()); | |
} | |
// copy forceUpdate value if not set in child | |
if (cmpPolicy.internalGetForceUpdate() == null) { | |
cmpPolicy.internalSetForceUpdate(parentCMPPolicy.internalGetForceUpdate()); | |
} | |
// copy updateAllFields value if not set in child | |
if (cmpPolicy.internalGetUpdateAllFields() == null) { | |
cmpPolicy.internalSetUpdateAllFields(parentCMPPolicy.internalGetUpdateAllFields()); | |
} | |
} | |
// Inherit the native connection requirement. | |
if (getParentDescriptor().isNativeConnectionRequired()) { | |
getDescriptor().setIsNativeConnectionRequired(true); | |
} | |
if (getDescriptor().getPartitioningPolicy() == null) { | |
getDescriptor().setPartitioningPolicy(getParentDescriptor().getPartitioningPolicy()); | |
} | |
} | |
initializeOnlyInstancesExpression(); | |
initializeWithAllSubclassesExpression(); | |
if (hasView()) { | |
// Set the table qualifier on the inheritance view. | |
if ((session.getDatasourcePlatform().getTableQualifier().length() != 0) && (getReadAllSubclassesView().getTableQualifier().length() == 0)) { | |
getReadAllSubclassesView().setTableQualifier(session.getDatasourcePlatform().getTableQualifier()); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Setup the default classExtractionMethod, or if one was specified by the user make sure it is valid. | |
*/ | |
protected void initializeClassExtractor(AbstractSession session) throws DescriptorException { | |
if (getClassExtractor() == null) { | |
if (isChildDescriptor()) { | |
setClassExtractor(getParentDescriptor().getInheritancePolicy().getClassExtractor()); | |
} | |
} else { | |
getClassExtractor().initialize(getDescriptor(), session); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Initialize the expression to use to check the specific type field. | |
*/ | |
protected void initializeOnlyInstancesExpression() throws DescriptorException { | |
if (getOnlyInstancesExpression() == null) { | |
if (hasClassExtractor()) { | |
return; | |
} | |
Object typeValue = getClassIndicatorValue(); | |
if (typeValue == null) { | |
if (shouldReadSubclasses()) { | |
return;// No indicator is allowed in this case. | |
} | |
throw DescriptorException.valueNotFoundInClassIndicatorMapping(getParentDescriptor(), getDescriptor()); | |
} | |
DatabaseField typeField = getClassIndicatorField(); | |
if (typeField == null) { | |
throw DescriptorException.classIndicatorFieldNotFound(getParentDescriptor(), getDescriptor()); | |
} | |
// cr3546 | |
if (shouldAlwaysUseOuterJoin()) { | |
setOnlyInstancesExpression(new ExpressionBuilder().getField(typeField).equalOuterJoin(typeValue)); | |
} else { | |
setOnlyInstancesExpression(new ExpressionBuilder().getField(typeField).equal(typeValue)); | |
} | |
} | |
// If subclasses are read, this is anded dynamically. | |
if (!shouldReadSubclasses()) { | |
getDescriptor().getQueryManager().setAdditionalJoinExpression(getOnlyInstancesExpression().and(getDescriptor().getQueryManager().getAdditionalJoinExpression())); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Potentially override the optimistic locking behavior | |
*/ | |
protected void initializeOptimisticLocking(){ | |
// CR#3214106, do not override if specified in subclass. | |
if (!getDescriptor().usesOptimisticLocking() && getParentDescriptor().usesOptimisticLocking()) { | |
getDescriptor().setOptimisticLockingPolicy((OptimisticLockingPolicy)getParentDescriptor().getOptimisticLockingPolicy().clone()); | |
getDescriptor().getOptimisticLockingPolicy().setDescriptor(getDescriptor()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Potentially override the cache invalidation behavior | |
*/ | |
protected void initializeCacheInvalidationPolicy() { | |
CacheInvalidationPolicy parentPolicyClone = (CacheInvalidationPolicy)getParentDescriptor().getCacheInvalidationPolicy().clone(); | |
getDescriptor().setCacheInvalidationPolicy(parentPolicyClone); | |
} | |
/** | |
* INTERNAL: | |
* Initialize the expression to use for queries to the class and its subclasses. | |
*/ | |
protected void initializeWithAllSubclassesExpression() throws DescriptorException { | |
if (getWithAllSubclassesExpression() == null) { | |
if (hasClassExtractor()) { | |
return; | |
} | |
if (isChildDescriptor() && shouldReadSubclasses()) { | |
setWithAllSubclassesExpression(new ExpressionBuilder().getField(getClassIndicatorField()).in(getAllChildClassIndicators())); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Check if it is a child descriptor. | |
*/ | |
public boolean isChildDescriptor() { | |
return getParentClassName() != null; | |
} | |
/** | |
* INTERNAL: | |
* Indicate whether a single table or joined inheritance strategy is being used. Since we currently do | |
* not support TABLE_PER_CLASS, indicating either joined/not joined is sufficient. | |
* | |
* @return isJoinedStrategy value | |
*/ | |
public boolean isJoinedStrategy() { | |
return isJoinedStrategy; | |
} | |
/** | |
* INTERNAL: | |
* Return whether or not is root parent descriptor | |
*/ | |
public boolean isRootParentDescriptor() { | |
return getParentDescriptor() == null; | |
} | |
/** | |
* INTERNAL: | |
* Initialized the inheritance properties that cannot be initialized | |
* until after the mappings have been. | |
*/ | |
public void postInitialize(AbstractSession session) { | |
if (isChildDescriptor()) { | |
ClassDescriptor parent = getParentDescriptor(); | |
if (getDescriptor().shouldAcquireCascadedLocks()){ | |
parent.setShouldAcquireCascadedLocks(true); | |
} | |
if (getDescriptor().hasRelationships()){ | |
parent.setHasRelationships(true); | |
} | |
while (parent != null) { | |
if (parent.hasMultipleTableConstraintDependecy()) { | |
getDescriptor().setHasMultipleTableConstraintDependecy(true); | |
break; | |
} | |
parent = parent.getInheritancePolicy().getParentDescriptor(); | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Allow the inheritance properties of the descriptor to be initialized. | |
* The descriptor's parent must first be initialized. | |
*/ | |
public void preInitialize(AbstractSession session) throws DescriptorException { | |
// Make sure that parent is already preinitialized. | |
if (isChildDescriptor()) { | |
updateTables(); | |
// Clone the multitenant policy and set on child descriptor. | |
if (getParentDescriptor().hasMultitenantPolicy()) { | |
MultitenantPolicy clonedMultitenantPolicy = getParentDescriptor().getMultitenantPolicy().clone(getDescriptor()); | |
getDescriptor().setMultitenantPolicy(clonedMultitenantPolicy); | |
} | |
setClassIndicatorMapping(getParentDescriptor().getInheritancePolicy().getClassIndicatorMapping()); | |
setShouldUseClassNameAsIndicator(getParentDescriptor().getInheritancePolicy().shouldUseClassNameAsIndicator()); | |
// Initialize properties. | |
getDescriptor().setPrimaryKeyFields(getParentDescriptor().getPrimaryKeyFields()); | |
getDescriptor().setAdditionalTablePrimaryKeyFields(Helper.concatenateMaps(getParentDescriptor().getAdditionalTablePrimaryKeyFields(), getDescriptor().getAdditionalTablePrimaryKeyFields())); | |
Expression localExpression = getDescriptor().getQueryManager().getMultipleTableJoinExpression(); | |
Expression parentExpression = getParentDescriptor().getQueryManager().getMultipleTableJoinExpression(); | |
if (localExpression != null) { | |
getDescriptor().getQueryManager().setInternalMultipleTableJoinExpression(localExpression.and(parentExpression)); | |
} else if (parentExpression != null) { | |
getDescriptor().getQueryManager().setInternalMultipleTableJoinExpression(parentExpression); | |
} | |
Expression localAdditionalExpression = getDescriptor().getQueryManager().getAdditionalJoinExpression(); | |
Expression parentAdditionalExpression = getParentDescriptor().getQueryManager().getAdditionalJoinExpression(); | |
if (localAdditionalExpression != null) { | |
getDescriptor().getQueryManager().setAdditionalJoinExpression(localAdditionalExpression.and(parentAdditionalExpression)); | |
} else if (parentAdditionalExpression != null) { | |
getDescriptor().getQueryManager().setAdditionalJoinExpression(parentAdditionalExpression); | |
} | |
setClassIndicatorField(getParentDescriptor().getInheritancePolicy().getClassIndicatorField()); | |
//if child has sequencing setting, do not bother to call the parent | |
if (!getDescriptor().usesSequenceNumbers()) { | |
getDescriptor().setSequenceNumberField(getParentDescriptor().getSequenceNumberField()); | |
getDescriptor().setSequenceNumberName(getParentDescriptor().getSequenceNumberName()); | |
} | |
} else { | |
// This must be done now before any other initialization occurs. | |
getDescriptor().setInternalDefaultTable(); | |
} | |
initializeClassExtractor(session); | |
if (!isChildDescriptor()) { | |
// build abstract class indicator field. | |
if ((getClassIndicatorField() == null) && (!hasClassExtractor())) { | |
session.getIntegrityChecker().handleError(DescriptorException.classIndicatorFieldNotFound(getDescriptor(), getDescriptor())); | |
} | |
if (getClassIndicatorField() != null) { | |
setClassIndicatorField(getDescriptor().buildField(getClassIndicatorField())); | |
// Determine and set the class indicator classification. | |
if (shouldUseClassNameAsIndicator()) { | |
getClassIndicatorField().setType(ClassConstants.STRING); | |
} else if (!getClassIndicatorMapping().isEmpty()) { | |
Class type = null; | |
Iterator fieldValuesEnum = getClassIndicatorMapping().values().iterator(); | |
while (fieldValuesEnum.hasNext() && (type == null)) { | |
Object value = fieldValuesEnum.next(); | |
if (value.getClass() != getClass().getClass()) { | |
type = value.getClass(); | |
} | |
} | |
getClassIndicatorField().setType(type); | |
} | |
getDescriptor().getFields().addElement(getClassIndicatorField()); | |
} | |
} | |
} | |
/** | |
* PUBLIC: | |
* Set the descriptor to read instance of itself and its subclasses when queried. | |
* This is used with inheritance to configure the result of queries. | |
* By default this is true for root inheritance descriptors, and false for all others. | |
*/ | |
public void readSubclassesOnQueries() { | |
setShouldReadSubclasses(true); | |
} | |
/** | |
* INTERNAL: | |
* Used to initialize a remote descriptor. | |
*/ | |
public void remoteInitialization(DistributedSession session) { | |
if (isChildDescriptor()) { | |
if (session.hasCorrespondingDescriptor(getParentDescriptor())) { | |
setParentDescriptor(session.getDescriptor(getParentClass())); | |
} else { | |
session.privilegedAddDescriptor(getParentDescriptor()); | |
getParentDescriptor().remoteInitialization(session); | |
} | |
} | |
Vector tempChildren = new Vector(getChildDescriptors().size()); | |
for (ClassDescriptor childDescriptor : getChildDescriptors()) { | |
if (session.hasCorrespondingDescriptor(childDescriptor)) { | |
tempChildren.addElement(session.getDescriptor(childDescriptor.getJavaClass())); | |
} else { | |
session.privilegedAddDescriptor(childDescriptor); | |
childDescriptor.remoteInitialization(session); | |
tempChildren.addElement(childDescriptor); | |
} | |
} | |
setChildDescriptors(tempChildren); | |
} | |
/** | |
* INTERNAL: | |
* Return if this descriptor has children that define additional tables and needs to read them. | |
* This case requires a special read, because the query cannot be done through a single SQL call with normal joins. | |
*/ | |
public boolean requiresMultipleTableSubclassRead() { | |
return hasMultipleTableChild() && shouldReadSubclasses(); | |
} | |
/** | |
* INTERNAL: | |
* Select all rows from a abstract table descriptor. | |
* This is accomplished by selecting for all of the concrete classes and then merging the rows. | |
* This does not optimize using type select, as the type information is not known. | |
* @return vector containing database rows. | |
* @exception DatabaseException - an error has occurred on the database. | |
*/ | |
protected Vector selectAllRowUsingCustomMultipleTableSubclassRead(ObjectLevelReadQuery query) throws DatabaseException { | |
Vector rows = new Vector(); | |
// CR#3701077, it must either have a filter only instances expression, or not have subclasses. | |
// This method recurses, so even though this is only called when shouldReadSubclasses is true, it may be false for subclasses. | |
if ((getOnlyInstancesExpression() != null) || (! shouldReadSubclasses())) { | |
ObjectLevelReadQuery concreteQuery = (ObjectLevelReadQuery)query.clone(); | |
concreteQuery.setReferenceClass(getDescriptor().getJavaClass()); | |
concreteQuery.setDescriptor(getDescriptor()); | |
Vector concreteRows = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectAllRowsFromConcreteTable(); | |
rows = Helper.concatenateVectors(rows, concreteRows); | |
} | |
// Recursively collect all rows from all concrete children and their children. | |
// If this descriptor did not have a child with its own table, then the concrete select | |
// would have selected them all. | |
if (hasMultipleTableChild() || !shouldReadSubclasses()) { | |
for (ClassDescriptor concreteDescriptor : getChildDescriptors()) { | |
Vector concreteRows = concreteDescriptor.getInheritancePolicy().selectAllRowUsingCustomMultipleTableSubclassRead(query); | |
rows = Helper.concatenateVectors(rows, concreteRows); | |
} | |
} | |
return rows; | |
} | |
/** | |
* INTERNAL: | |
* Select all rows from a abstract table descriptor. | |
* This is accomplished by selecting for all of the concrete classes and then merging the rows. | |
* @return vector containing database rows. | |
* @exception DatabaseException - an error has occurred on the database. | |
*/ | |
protected Vector selectAllRowUsingDefaultMultipleTableSubclassRead(ObjectLevelReadQuery query) throws DatabaseException, QueryException { | |
// Get all rows for the given class indicator field | |
// The indicator select is prepared in the original query, so can just be executed. | |
List<AbstractRecord> classIndicators = ((ExpressionQueryMechanism)query.getQueryMechanism()).selectAllRowsFromTable(); | |
List<Class> classes = new ArrayList<Class>(); | |
Set<Class> uniqueClasses = new HashSet<Class>(); | |
for (AbstractRecord row : classIndicators) { | |
Class concreteClass = classFromRow(row, query.getSession()); | |
if (!uniqueClasses.contains(concreteClass)) { // Ensure unique (a distinct is used, but may have been disabled) | |
uniqueClasses.add(concreteClass); | |
classes.add(concreteClass); | |
} | |
} | |
Vector rows = new Vector(); | |
// joinedMappingIndexes contains Integer indexes corresponding to the number of fields | |
// to which the query reference class is mapped, for instance: | |
// referenceClass = SmallProject => joinedMappingIndexes(0) = 6; | |
// referenceClass = LargeProject => joinedMappingIndexes(0) = 8; | |
// This information should be preserved in the main query against the parent class, | |
// therefore in this case joinedMappedIndexes contains a Map of classes to Integers: | |
// referenceClass = Project => joinedMappingIndexes(0) = Map {SmallProject -> 6; LargeProject -> 8}. | |
// These maps are populated in the loop below, and set into the main query joinedMappingIndexes. | |
HashMap joinedMappingIndexes = null; | |
if (query.hasJoining()) { | |
joinedMappingIndexes = new HashMap(); | |
} | |
ClassDescriptor rootDescriptor = query.getDescriptor(); | |
for (Class concreteClass : classes) { | |
if (!uniqueClasses.contains(concreteClass)) { | |
continue; | |
} | |
Set<Class> subclasses = new HashSet<Class>(); | |
uniqueClasses.remove(concreteClass); | |
subclasses.add(concreteClass); | |
ClassDescriptor concreteDescriptor = getDescriptor(concreteClass); | |
if (concreteDescriptor == null) { | |
throw QueryException.noDescriptorForClassFromInheritancePolicy(query, concreteClass); | |
} | |
InheritancePolicy concretePolicy = concreteDescriptor.getInheritancePolicy(); | |
ClassDescriptor parentDescriptor = concretePolicy.getParentDescriptor(); | |
// Find the root most parent with its own table. | |
while ((parentDescriptor != null) && (parentDescriptor != rootDescriptor) | |
&& !parentDescriptor.getInheritancePolicy().hasMultipleTableChild() && parentDescriptor.getInheritancePolicy().shouldReadSubclasses()) { | |
concreteDescriptor = parentDescriptor; | |
concreteClass = concreteDescriptor.getJavaClass(); | |
uniqueClasses.remove(concreteClass); | |
subclasses.add(concreteClass); | |
concretePolicy = concreteDescriptor.getInheritancePolicy(); | |
parentDescriptor = concretePolicy.getParentDescriptor(); | |
} | |
// If this class has children select them all. | |
if (concretePolicy.hasChildren() && !concretePolicy.hasMultipleTableChild()) { | |
removeChildren(concreteDescriptor, uniqueClasses, subclasses); | |
} | |
ObjectLevelReadQuery concreteQuery = (ObjectLevelReadQuery)query.clone(); | |
concreteQuery.setReferenceClass(concreteClass); | |
concreteQuery.setDescriptor(concreteDescriptor); | |
Vector concreteRows = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectAllRowsFromConcreteTable(); | |
rows = Helper.concatenateVectors(rows, concreteRows); | |
if (joinedMappingIndexes != null) { | |
// Need to set mapping index for each select, as each row size is different. | |
for (Map.Entry entry : concreteQuery.getJoinedAttributeManager().getJoinedMappingIndexes_().entrySet()) { | |
HashMap mappingIndexes = (HashMap)joinedMappingIndexes.get(entry.getKey()); | |
if (mappingIndexes == null) { | |
mappingIndexes = new HashMap(classes.size()); | |
joinedMappingIndexes.put(entry.getKey(), mappingIndexes); | |
} | |
for (Class subclass : subclasses) { | |
mappingIndexes.put(subclass, entry.getValue()); | |
} | |
} | |
} | |
} | |
if (joinedMappingIndexes != null) { | |
query.getJoinedAttributeManager().setJoinedMappingIndexes_(joinedMappingIndexes); | |
} | |
return rows; | |
} | |
/** | |
* Remove all of the subclasses (and so on) from the set of classes. | |
*/ | |
protected void removeChildren(ClassDescriptor descriptor, Set<Class> classes, Set<Class> subclasses) { | |
for (ClassDescriptor childDescriptor : descriptor.getInheritancePolicy().getChildDescriptors()) { | |
classes.remove(childDescriptor.getJavaClass()); | |
subclasses.add(childDescriptor.getJavaClass()); | |
removeChildren(childDescriptor, classes, subclasses); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Select all rows from a abstract table descriptor. | |
* This is accomplished by selecting for all of the concrete classes and then merging the rows. | |
* @return vector containing database rows. | |
* @exception DatabaseException - an error has occurred on the database. | |
*/ | |
public Vector selectAllRowUsingMultipleTableSubclassRead(ObjectLevelReadQuery query) throws DatabaseException { | |
if (hasClassExtractor()) { | |
return selectAllRowUsingCustomMultipleTableSubclassRead(query); | |
} else { | |
return selectAllRowUsingDefaultMultipleTableSubclassRead(query); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Select one rows from a abstract table descriptor. | |
* This is accomplished by selecting for all of the concrete classes until a row is found. | |
* This does not optimize using type select, as the type information is not known. | |
* @exception DatabaseException - an error has occurred on the database. | |
*/ | |
protected AbstractRecord selectOneRowUsingCustomMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException { | |
// CR#3701077, it must either have a filter only instances expression, or not have subclasses. | |
// This method recurses, so even though this is only called when shouldReadSubclasses is true, it may be false for subclasses. | |
if ((getOnlyInstancesExpression() != null) || (! shouldReadSubclasses())) { | |
ReadObjectQuery concreteQuery = (ReadObjectQuery)query.clone(); | |
concreteQuery.setReferenceClass(getDescriptor().getJavaClass()); | |
concreteQuery.setDescriptor(getDescriptor()); | |
AbstractRecord row = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectOneRowFromConcreteTable(); | |
if (row != null) { | |
return row; | |
} | |
} | |
// Recursively collect all rows from all concrete children and their children. | |
for (ClassDescriptor concreteDescriptor : getChildDescriptors()) { | |
AbstractRecord row = concreteDescriptor.getInheritancePolicy().selectOneRowUsingCustomMultipleTableSubclassRead(query); | |
if (row != null) { | |
return row; | |
} | |
} | |
return null; | |
} | |
/** | |
* INTERNAL: | |
* Select one row of any concrete subclass, | |
* This must use two selects, the first retrieves the type field only. | |
*/ | |
protected AbstractRecord selectOneRowUsingDefaultMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException, QueryException { | |
// Get the row for the given class indicator field | |
// The indicator select is prepared in the original query, so can just be executed. | |
AbstractRecord typeRow = ((ExpressionQueryMechanism)query.getQueryMechanism()).selectOneRowFromTable(); | |
if (typeRow == null) { | |
return null; | |
} | |
Class concreteClass = classFromRow(typeRow, query.getSession()); | |
ClassDescriptor concreteDescriptor = getDescriptor(concreteClass); | |
if (concreteDescriptor == null) { | |
throw QueryException.noDescriptorForClassFromInheritancePolicy(query, concreteClass); | |
} | |
ReadObjectQuery concreteQuery = (ReadObjectQuery)query.clone(); | |
concreteQuery.setReferenceClass(concreteClass); | |
concreteQuery.setDescriptor(concreteDescriptor); | |
AbstractRecord resultRow = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectOneRowFromConcreteTable(); | |
return resultRow; | |
} | |
/** | |
* INTERNAL: | |
* Select one row of any concrete subclass, | |
* This must use two selects, the first retrieves the type field only. | |
*/ | |
public AbstractRecord selectOneRowUsingMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException, QueryException { | |
if (hasClassExtractor()) { | |
return selectOneRowUsingCustomMultipleTableSubclassRead(query); | |
} else { | |
return selectOneRowUsingDefaultMultipleTableSubclassRead(query); | |
} | |
} | |
/** | |
* INTERNAL: | |
*/ | |
protected void setAllChildClassIndicators(Vector allChildClassIndicators) { | |
this.allChildClassIndicators = allChildClassIndicators; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public void setChildDescriptors(List<ClassDescriptor> childDescriptors) { | |
this.childDescriptors = childDescriptors; | |
} | |
/** | |
* ADVANCED: | |
* A class extraction method can be registered with the descriptor to override the default inheritance mechanism. | |
* This allows for a user defined class indicator in place of providing an explicit class indicator field. | |
* The method registered must be a static method on the class which has that descriptor. The method must take Record | |
* as an argument (for example, a DatabaseRecord), and must return the class to use for that record. | |
* This method will be used to decide which class to instantiate when reading from the database. | |
* It is the application's responsibility to populate any typing information in the database required | |
* to determine the class from the record. | |
* If this method is used then the class indicator field and mapping cannot be used, and in addition, | |
* the descriptor's withAllSubclasses and onlyInstances expressions must also be set up correctly. | |
* | |
* @see #setWithAllSubclassesExpression(Expression) | |
* @see #setOnlyInstancesExpression(Expression) | |
*/ | |
public void setClassExtractionMethodName(String staticClassClassExtractionMethod) { | |
if ((staticClassClassExtractionMethod == null) || (staticClassClassExtractionMethod.length() == 0)) { | |
return; | |
} | |
if (!(getClassExtractor() instanceof MethodClassExtractor)) { | |
setClassExtractor(new MethodClassExtractor()); | |
} | |
((MethodClassExtractor)getClassExtractor()).setClassExtractionMethodName(staticClassClassExtractionMethod); | |
} | |
/** | |
* INTERNAL: | |
* Set the class indicator associations from reading the deployment XML. | |
*/ | |
public void setClassIndicatorAssociations(Vector classIndicatorAssociations) { | |
setClassNameIndicatorMapping(new HashMap(classIndicatorAssociations.size() + 1)); | |
setClassIndicatorMapping(new HashMap((classIndicatorAssociations.size() * 2) + 1)); | |
for (Iterator iterator = classIndicatorAssociations.iterator(); iterator.hasNext();) { | |
Association association = (Association)iterator.next(); | |
Object key = association.getKey(); | |
// Allow for 904 format which stored class name, may not use correct class loader. | |
if (key instanceof String) { | |
key = ConversionManager.getDefaultManager().convertClassNameToClass((String)key); | |
} | |
addClassIndicator((Class)key, association.getValue()); | |
} | |
} | |
/** | |
* ADVANCED: | |
* To set the class indicator field. | |
* This can be used for advanced field types, such as XML nodes, or to set the field type. | |
*/ | |
@Override | |
public void setClassIndicatorField(DatabaseField classIndicatorField) { | |
this.classIndicatorField = classIndicatorField; | |
} | |
/** | |
* PUBLIC: | |
* To set the class indicator field name. | |
* This is the name of the field in the table that stores what type of object this is. | |
*/ | |
public void setClassIndicatorFieldName(String fieldName) { | |
if (fieldName == null) { | |
setClassIndicatorField(null); | |
} else { | |
setClassIndicatorField(new DatabaseField(fieldName)); | |
} | |
} | |
/** | |
* PUBLIC: | |
* Set the association of indicators and classes. | |
* This may be desired to be used by clients in strange inheritance models. | |
*/ | |
@Override | |
public void setClassIndicatorMapping(Map classIndicatorMapping) { | |
this.classIndicatorMapping = classIndicatorMapping; | |
} | |
/** | |
* INTERNAL: | |
* Set the class name indicator mapping, used by the MW. | |
*/ | |
public void setClassNameIndicatorMapping(Map classNameIndicatorMapping) { | |
this.classNameIndicatorMapping = classNameIndicatorMapping; | |
} | |
/** | |
* INTERNAL: | |
* Set the descriptor. | |
*/ | |
@Override | |
public void setDescriptor(ClassDescriptor descriptor) { | |
this.descriptor = descriptor; | |
} | |
/** | |
* ADVANCED: | |
* Determines whether the descriptors using this inheritance policy | |
* should be used as descriptors for subclasses of the classes they | |
* describe if those subclasses do not have their own descriptor | |
* | |
* e.g. If Employee.class has a descriptor and EmployeeSubClass does | |
* not have a descriptor, if describesNonPersistenceSubclasses is true | |
* Employee's descriptor will be used as the descriptor for Employee | |
* | |
* @param describesNonPersistenceSubclasses | |
*/ | |
public void setDescribesNonPersistentSubclasses(boolean describesNonPersistentSubclasses){ | |
this.describesNonPersistentSubclasses = describesNonPersistentSubclasses; | |
} | |
/** | |
* INTERNAL: | |
* Used to indicate a JOINED inheritance strategy. | |
* | |
*/ | |
public void setJoinedStrategy() { | |
isJoinedStrategy = true; | |
} | |
/** | |
* ADVANCED: | |
* Sets the expression used to select instance of the class only. Can be used to customize the | |
* inheritance class indicator expression. | |
*/ | |
public void setOnlyInstancesExpression(Expression onlyInstancesExpression) { | |
this.onlyInstancesExpression = onlyInstancesExpression; | |
} | |
/** | |
* PUBLIC: | |
* Set the parent class. | |
* A descriptor can inherit from another descriptor through defining it as its parent. | |
* The root descriptor must define a class indicator field and mapping. | |
* All children must share the same table as their parent but can add additional tables. | |
* All children must share the root descriptor primary key. | |
*/ | |
public void setParentClass(Class parentClass) { | |
this.parentClass = parentClass; | |
if (parentClass != null) { | |
setParentClassName(parentClass.getName()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Set the parent class name, used by MW to avoid referencing the real class for | |
* deployment XML generation. | |
*/ | |
public void setParentClassName(String parentClassName) { | |
this.parentClassName = parentClassName; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public void setParentDescriptor(ClassDescriptor parentDescriptor) { | |
this.parentDescriptor = parentDescriptor; | |
} | |
/** | |
* INTERNAL: | |
* The view can be used to optimize/customize the query for all subclasses where they have multiple tables. | |
* This view can do the outer join, we require the view because we cannot generate dynamic platform independent SQL | |
* for outer joins (i.e. not possible to do so either). | |
*/ | |
protected void setReadAllSubclassesView(DatabaseTable readAllSubclassesView) { | |
this.readAllSubclassesView = readAllSubclassesView; | |
} | |
/** | |
* ADVANCED: | |
* The view can be used to optimize/customize the query for all subclasses where they have multiple tables. | |
* This view can use outer joins or unions to combine the results of selecting from all of the subclass tables. | |
* If a view is not given then TopLink must make an individual call for each subclass. | |
*/ | |
public void setReadAllSubclassesViewName(String readAllSubclassesViewName) { | |
if (readAllSubclassesViewName == null) { | |
setReadAllSubclassesView(null); | |
} else { | |
setReadAllSubclassesView(new DatabaseTable(readAllSubclassesViewName)); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Set the descriptor to read instance of itself and its subclasses when queried. | |
* This is used with inheritance to configure the result of queries. | |
* By default this is true for root inheritance descriptors, and false for all others. | |
*/ | |
public void setShouldReadSubclasses(Boolean shouldReadSubclasses) { | |
this.shouldReadSubclasses = shouldReadSubclasses; | |
} | |
/** | |
* PUBLIC: | |
* Set the descriptor to read instance of itself and its subclasses when queried. | |
* This is used with inheritance to configure the result of queries. | |
* By default this is true for root inheritance descriptors, and false for all others. | |
*/ | |
public void setShouldReadSubclasses(boolean shouldReadSubclasses) { | |
this.shouldReadSubclasses = Boolean.valueOf(shouldReadSubclasses); | |
} | |
/** | |
* PUBLIC: | |
* Set if the descriptor uses the classes fully qualified name as the indicator. | |
* The class indicator is used with inheritance to determine the class from a row. | |
* By default a class indicator mapping is required, this can be set to true if usage of the class | |
* name is desired. | |
* The field must be of a large enough size to store the fully qualified class name. | |
*/ | |
public void setShouldUseClassNameAsIndicator(boolean shouldUseClassNameAsIndicator) { | |
this.shouldUseClassNameAsIndicator = shouldUseClassNameAsIndicator; | |
} | |
/** | |
* PUBLIC: | |
* Sets the inheritance policy to always use an outer join when querying across a relationship of class. | |
* used when using getAllowingNull(), or anyOfAllowingNone() | |
*/ | |
// cr3546 | |
public void setAlwaysUseOuterJoinForClassType(boolean choice) { | |
this.shouldAlwaysUseOuterJoin = choice; | |
} | |
/** | |
* INTERNAL: | |
* Used to indicate a SINGLE_TABLE inheritance strategy. Since only JOINED and SINGLE_TABLE | |
* strategies are supported at this time (no support for TABLE_PER_CLASS) using a | |
* !isJoinedStrategy an an indicator for SINGLE_TABLE is sufficient. | |
* | |
*/ | |
public void setSingleTableStrategy() { | |
isJoinedStrategy = false; | |
} | |
/** | |
* INTERNAL: | |
* Sets if we should use the descriptor inheritance to determine | |
* if an object can be returned from the identity map or not. | |
*/ | |
public void setUseDescriptorsToValidateInheritedObjects(boolean useDescriptorsToValidateInheritedObjects) { | |
//CR 4005 | |
this.useDescriptorsToValidateInheritedObjects = useDescriptorsToValidateInheritedObjects; | |
} | |
/** | |
* ADVANCED: | |
* Sets the expression to be used for querying for a class and all its subclasses. Can be used | |
* to customize the inheritance class indicator expression. | |
*/ | |
public void setWithAllSubclassesExpression(Expression withAllSubclassesExpression) { | |
this.withAllSubclassesExpression = withAllSubclassesExpression; | |
} | |
/** | |
* PUBLIC: | |
* Return true if this descriptor should read instances of itself and subclasses on queries. | |
*/ | |
public boolean shouldReadSubclasses() { | |
if (shouldReadSubclasses == null) { | |
return true; | |
} | |
return shouldReadSubclasses.booleanValue(); | |
} | |
/** | |
* INTERNAL: | |
* Return true if this descriptor should read instances of itself and subclasses on queries. | |
*/ | |
public Boolean shouldReadSubclassesValue() { | |
return shouldReadSubclasses; | |
} | |
/** | |
* PUBLIC: | |
* returns if the inheritance policy will always use an outerjoin when selecting class type | |
*/ | |
// cr3546 | |
public boolean shouldAlwaysUseOuterJoin() { | |
return this.shouldAlwaysUseOuterJoin; | |
} | |
/** | |
* PUBLIC: | |
* Return if an outer join should be used to read subclasses. | |
* By default a separate query is done for each subclass when querying for | |
* a root or branch inheritance class that has subclasses that span multiple tables. | |
*/ | |
public boolean shouldOuterJoinSubclasses() { | |
return shouldOuterJoinSubclasses; | |
} | |
/** | |
* PUBLIC: | |
* Set if an outer join should be used to read subclasses. | |
* By default a separate query is done for each subclass when querying for | |
* a root or branch inheritance class that has subclasses that span multiple tables. | |
*/ | |
public void setShouldOuterJoinSubclasses(boolean shouldOuterJoinSubclasses) { | |
this.shouldOuterJoinSubclasses = shouldOuterJoinSubclasses; | |
} | |
/** | |
* PUBLIC: | |
* Return true if the descriptor use the classes full name as the indicator. | |
* The class indicator is used with inheritance to determine the class from a row. | |
* By default a class indicator mapping is required, this can be set to true if usage of the class | |
* name is desired. | |
* The field must be of a large enough size to store the fully qualified class name. | |
*/ | |
public boolean shouldUseClassNameAsIndicator() { | |
return shouldUseClassNameAsIndicator; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public String toString() { | |
return Helper.getShortClassName(getClass()) + "(" + getDescriptor() + ")"; | |
} | |
/** | |
* INTERNAL: | |
* set the tables on the child descriptor | |
* overridden in org.eclipse.persistence.internal.oxm.QNameInheritancePolicy | |
*/ | |
protected void updateTables(){ | |
// Unique is required because the builder can add the same table many times. | |
Vector<DatabaseTable> childTables = getDescriptor().getTables(); | |
Vector<DatabaseTable> parentTables = getParentDescriptor().getTables(); | |
Vector<DatabaseTable> uniqueTables = Helper.concatenateUniqueVectors(parentTables, childTables); | |
getDescriptor().setTables(uniqueTables); | |
// After filtering out any duplicate tables, set the default table | |
// if one is not already set. This must be done now before any other | |
// initialization occurs. In a joined strategy case, the default | |
// table will be at an index greater than 0. Which is where | |
// setDefaultTable() assumes it is. Therefore, we need to send the | |
// actual default table instead. | |
if (childTables.isEmpty()) { | |
getDescriptor().setInternalDefaultTable(); | |
} else { | |
getDescriptor().setInternalDefaultTable(uniqueTables.get(uniqueTables.indexOf(childTables.get(0)))); | |
} | |
} | |
/** | |
* PUBLIC: | |
* Set the descriptor to use the classes full name as the indicator. | |
* The class indicator is used with inheritance to determine the class from a row. | |
* By default a class indicator mapping is required, this can be set to true if usage of the class | |
* name is desired. | |
* The field must be of a large enough size to store the fully qualified class name. | |
*/ | |
public void useClassNameAsIndicator() { | |
setShouldUseClassNameAsIndicator(true); | |
} | |
} |