blob: ffd782a6d9d4e7146d66be7285f254283a9ff593 [file] [log] [blame]
/*******************************************************************************
* 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:
* 12/12/2008-1.1 Guy Pelletier
* - 249860: Implement table per class inheritance support.
******************************************************************************/
package org.eclipse.persistence.descriptors;
import java.io.*;
import java.util.*;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.mappings.ManyToManyMapping;
import org.eclipse.persistence.mappings.OneToManyMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
/**
* <p><b>Purpose</b>: Provides the functionality to support a TABLE_PER_CLASS
* inheritance strategy. Resolves relational mappings and querying.
*/
public class TablePerClassPolicy extends InterfacePolicy implements Serializable, Cloneable {
/**
* Selection queries from read all mappings will be cached and re-used.
* E.G Entity A has a 1-M to Entity D (who has a subclass Entity E). We will
* build a selection query to Entity E based on the selection query from the
* 1-M mapping from Entity A to Entity D.
*/
protected HashMap<DatabaseMapping, DatabaseQuery> selectionQueriesForAllObjects;
/**
* INTERNAL:
* Create a new policy.
* Only descriptors involved in inheritance should have a policy.
*/
public TablePerClassPolicy(ClassDescriptor descriptor) {
setDescriptor(descriptor);
selectionQueriesForAllObjects = new HashMap<DatabaseMapping, DatabaseQuery>();
}
/**
* INTERNAL:
*/
protected void addSelectionQuery(ForeignReferenceMapping cloneMapping, ForeignReferenceMapping sourceMapping, AbstractSession session) {
// Set the new reference class
cloneMapping.setReferenceClass(getDescriptor().getJavaClass());
cloneMapping.setReferenceClassName(getDescriptor().getJavaClassName());
// Force the selection criteria to be re-built.
cloneMapping.setForceInitializationOfSelectionCriteria(true);
// Now initialize the mapping
cloneMapping.initialize(session);
// The selection query should be initialized with all the right
// goodies now, cache it for quick retrieval.
ObjectLevelReadQuery selectionQuery = (ObjectLevelReadQuery) cloneMapping.getSelectionQuery();
selectionQuery.getExpressionBuilder().setQueryClassAndDescriptor(descriptor.getJavaClass(), descriptor);
// By default its source mapping will be the cloned mapping, we
// need to set the actual source mapping so that we can look it
// back up correctly.
selectionQuery.setSourceMapping(sourceMapping);
// Cache the selection query for this source mapping.
selectionQueriesForAllObjects.put(sourceMapping, selectionQuery);
}
/**
* INTERNAL:
*/
public boolean isTablePerClassPolicy() {
return true;
}
/**
* INTERNAL:
* This method is called from individual mappings during their
* initialization. If the mapping is to a class within a TABLE_PER_CLASS
* inheritance hierarchy, what this method will do is prepare a selection
* query to execute for every child descriptor in the hierarchy.
*
* The selection queries are created by cloning the source mapping,
* updating the necessary database fields on the mapping and then
* initializing the mapping to create the internal selection query.
* This query is then cached where needed using the source mapping's
* selection query name as the key.
*
* @see selectAllObjects(ReadAllQuery)
* @see selectOneObject(ReadObjectQuery)
*/
public void prepareChildrenSelectionQuery(DatabaseMapping sourceMapping, AbstractSession session) {
// From collection mappings we must execute a query on every
// subclass table to build our results. Tell all children
// descriptors to prepare their selection queries.
for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
childDescriptor.getTablePerClassPolicy().prepareSelectionQuery(sourceMapping, session);
}
}
/**
* INTERNAL:
*/
protected void prepareManyToManySelectionQuery(ManyToManyMapping sourceMapping, AbstractSession session) {
// Clone the mapping because in reality that is what we have, that
// is, a M-M mapping to each class of the hierarchy.
ManyToManyMapping manyToMany = (ManyToManyMapping) sourceMapping.clone();
// Update the foreign key fields on the mapping. Basically, take the
// table name off and let the descriptor figure it out.
for (DatabaseField keyField : manyToMany.getTargetKeyFields()) {
keyField.setTable(new DatabaseTable());
}
addSelectionQuery(manyToMany, sourceMapping, session);
}
/**
* INTERNAL:
*/
protected void prepareOneToManySelectionQuery(OneToManyMapping sourceMapping, AbstractSession session) {
// Clone the mapping because in reality that is what we have, that
// is, a 1-M mapping to each class of the hierarchy.
OneToManyMapping oneToMany = (OneToManyMapping) sourceMapping.clone();
// Update the foreign key fields on the mapping. Basically, take the
// table name off and let the descriptor figure it out.
Vector<DatabaseField> targetForeignKeyFields = new Vector<DatabaseField>();
for (DatabaseField fkField : oneToMany.getTargetForeignKeysToSourceKeys().keySet()) {
targetForeignKeyFields.add(new DatabaseField(fkField.getName()));
}
// Update our foreign key fields and clear the key maps.
oneToMany.setTargetForeignKeyFields(targetForeignKeyFields);
oneToMany.getTargetForeignKeysToSourceKeys().clear();
oneToMany.getSourceKeysToTargetForeignKeys().clear();
addSelectionQuery(oneToMany, sourceMapping, session);
}
/**
* INTERNAL:
*/
protected void prepareOneToOneSelectionQuery(OneToOneMapping sourceMapping, AbstractSession session) {
// Clone the mapping because in reality that is what we have, that
// is, a 1-1 mapping to each class of the hierarchy.
OneToOneMapping oneToOne = (OneToOneMapping) sourceMapping.clone();
// Update the target keys to have an empty table (descriptor will figure it out)
for (DatabaseField targetField : oneToOne.getTargetToSourceKeyFields().keySet()) {
targetField.setTable(new DatabaseTable());
}
addSelectionQuery(oneToOne, sourceMapping, session);
}
/**
* INTERNAL:
* The selection queries are created by cloning the source mapping,
* updating the necessary database fields on the mapping and then
* initializing the mapping to create the internal selection query.
* This query is then cached where needed using the source mapping
* as the key. A prepare is performed for each child of the hierarchy.
*/
protected void prepareSelectionQuery(DatabaseMapping sourceMapping, AbstractSession session) {
// Recurse through our child descriptors to set up the selection query
// before execution.
for (ClassDescriptor childDescriptor : (Vector<ClassDescriptor>) getChildDescriptors()) {
childDescriptor.getTablePerClassPolicy().prepareSelectionQuery(sourceMapping, session);
}
if (sourceMapping.isOneToManyMapping()) {
prepareOneToManySelectionQuery((OneToManyMapping) sourceMapping, session);
} else if (sourceMapping.isManyToManyMapping()) {
prepareManyToManySelectionQuery((ManyToManyMapping) sourceMapping, session);
} else if (sourceMapping.isOneToOneMapping()) {
prepareOneToOneSelectionQuery((OneToOneMapping) sourceMapping, session);
}
// No support for other mappings at this point.
}
/**
* INTERNAL:
* Select all objects for a concrete descriptor.
*/
@Override
protected Object selectAllObjects(ReadAllQuery query) {
if (this.descriptor.isAbstract()) {
return query.getContainerPolicy().containerInstance();
}
// If we came from a source mapping the execute the selection query
// we prepared from it.
if (selectionQueriesForAllObjects.containsKey(query.getSourceMapping())) {
return query.getExecutionSession().executeQuery(selectionQueriesForAllObjects.get(query.getSourceMapping()), query.getTranslationRow());
} else {
return super.selectAllObjects(query);
}
}
/**
* INTERNAL:
* Select one object of any concrete subclass.
*/
@Override
protected Object selectOneObject(ReadObjectQuery query) throws DescriptorException {
if (this.descriptor.isAbstract()) {
return null;
}
// If we came from a source mapping the execute the selection query
// we prepared from it.
if (selectionQueriesForAllObjects.containsKey(query.getSourceMapping())) {
return query.getExecutionSession().executeQuery(selectionQueriesForAllObjects.get(query.getSourceMapping()), query.getTranslationRow());
} else {
// Assuming we're doing a find by primary key ...
// We have to update the translation row to be to the correct field.
AbstractRecord translationRow = query.getTranslationRow().clone();
Vector allFields = new Vector();
for (DatabaseField field : translationRow.getFields()) {
// Remove the table and let the descriptor figure it out.
allFields.add(new DatabaseField(field.getName()));
}
translationRow.getFields().clear();
translationRow.getFields().addAll(allFields);
return query.getSession().executeQuery(getDescriptor().getQueryManager().getReadObjectQuery(), translationRow);
}
}
}