org.eclipse.persistence.eis.mappings.EISOneToOneMapping Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 1998, 2018 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.eis.mappings;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.annotations.CacheKeyType;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.indirection.ValueHolder;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
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.ObjectReferenceMapping;
import org.eclipse.persistence.queries.*;
/**
* An EIS one-to-one mapping is a reference mapping that represents the relationship between
* a single source object and a single mapped persistent Java object. The source object usually
* contains a foreign key (pointer) to the target object (key on source); alternatively, the target
* object may contain a foreign key to the source object (key on target). Because both the source
* and target objects use interactions, they must both be configured as root object types.
*
*
*
* Record Type
* Description
*
*
* Indexed
* Ordered collection of record elements. The indexed record EIS format
* enables Java class attribute values to be retrieved by position or index.
*
*
* Mapped
* Key-value map based representation of record elements. The mapped record
* EIS format enables Java class attribute values to be retrieved by an object key.
*
*
* XML
* Record/Map representation of an XML DOM element.
*
*
*
* @see org.eclipse.persistence.eis.EISDescriptor#useIndexedRecordFormat
* @see org.eclipse.persistence.eis.EISDescriptor#useMappedRecordFormat
* @see org.eclipse.persistence.eis.EISDescriptor#useXMLRecordFormat
*
* @since Oracle TopLink 10g Release 2 (10.1.3)
*/
public class EISOneToOneMapping extends ObjectReferenceMapping implements EISMapping {
/** Maps the source foreign/primary key fields to the target primary/foreign key fields. */
protected Map sourceToTargetKeyFields;
/** Maps the target primary/foreign key fields to the source foreign/primary key fields. */
protected Map targetToSourceKeyFields;
/** These are used for non-unit of work modification to check if the value of the 1-1 was changed and a deletion is required. */
protected boolean shouldVerifyDelete;
protected transient Expression privateOwnedCriteria;
public EISOneToOneMapping() {
this.selectionQuery = new ReadObjectQuery();
this.foreignKeyFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1);
this.sourceToTargetKeyFields = new HashMap(2);
this.targetToSourceKeyFields = new HashMap(2);
}
/**
* INTERNAL:
*/
@Override
public boolean isEISMapping() {
return true;
}
/**
* INTERNAL:
*/
@Override
public boolean isOneToOneMapping() {
return true;
}
/**
* PUBLIC:
* Define the source foreign key relationship in the one-to-one mapping.
* This method is used to add foreign key relationships to the mapping.
* Both the source foreign key field name and the corresponding
* target primary key field name must be specified.
*/
@Override
public void addForeignKeyField(DatabaseField sourceForeignKeyField, DatabaseField targetKeyField) {
getSourceToTargetKeyFields().put(sourceForeignKeyField, targetKeyField);
getTargetToSourceKeyFields().put(targetKeyField, sourceForeignKeyField);
getForeignKeyFields().add(sourceForeignKeyField);
setIsForeignKeyRelationship(true);
}
/**
* PUBLIC:
* Define the source foreign key relationship in the one-to-one mapping.
* This method is used to add foreign key relationships to the mapping.
* Both the source foreign key field name and the corresponding
* target primary key field name must be specified.
*/
public void addForeignKeyFieldName(String sourceForeignKeyFieldName, String targetKeyFieldName) {
this.addForeignKeyField(new DatabaseField(sourceForeignKeyFieldName), new DatabaseField(targetKeyFieldName));
}
/**
* INTERNAL:
* This methods clones all the fields and ensures that each collection refers to
* the same clones.
*/
@Override
public Object clone() {
EISOneToOneMapping clone = (EISOneToOneMapping)super.clone();
clone.setForeignKeyFields(org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(getForeignKeyFields().size()));
clone.setSourceToTargetKeyFields(new HashMap(getSourceToTargetKeyFields().size()));
clone.setTargetToSourceKeyFields(new HashMap(getTargetToSourceKeyFields().size()));
Map setOfFields = new HashMap(getTargetToSourceKeyFields().size());
for (Enumeration enumtr = getForeignKeyFields().elements(); enumtr.hasMoreElements();) {
DatabaseField field = (DatabaseField)enumtr.nextElement();
DatabaseField fieldClone = field.clone();
setOfFields.put(field, fieldClone);
clone.getForeignKeyFields().addElement(fieldClone);
}
//get clones from set for source hashtable. If they do not exist, create a new one.
Iterator sourceKeyIterator = getSourceToTargetKeyFields().keySet().iterator();
while (sourceKeyIterator.hasNext()) {
DatabaseField sourceField = (DatabaseField)sourceKeyIterator.next();
DatabaseField targetField = getSourceToTargetKeyFields().get(sourceField);
DatabaseField targetClone = (DatabaseField)setOfFields.get(targetField);
if (targetClone == null) {
targetClone = targetField.clone();
setOfFields.put(targetField, targetClone);
}
DatabaseField sourceClone = sourceField.clone();
if (sourceClone == null) {
sourceClone = sourceField.clone();
setOfFields.put(sourceField, sourceClone);
}
clone.getSourceToTargetKeyFields().put(sourceClone, targetClone);
}
//get clones from set for target hashtable. If they do not exist, create a new one.
Iterator targetKeyIterator = getTargetToSourceKeyFields().keySet().iterator();
while (targetKeyIterator.hasNext()) {
DatabaseField targetField = (DatabaseField)targetKeyIterator.next();
DatabaseField sourceField = getTargetToSourceKeyFields().get(targetField);
DatabaseField targetClone = (DatabaseField)setOfFields.get(targetField);
if (targetClone == null) {
targetClone = targetField.clone();
setOfFields.put(targetField, targetClone);
}
DatabaseField sourceClone = (DatabaseField)setOfFields.get(sourceField);
if (sourceClone == null) {
sourceClone = sourceField.clone();
setOfFields.put(sourceField, sourceClone);
}
clone.getTargetToSourceKeyFields().put(targetClone, sourceClone);
}
return clone;
}
/**
* INTERNAL:
* Return the primary key for the reference object (i.e. the object
* object referenced by domainObject and specified by mapping).
* This key will be used by a RemoteValueHolder.
*/
@Override
public Object extractPrimaryKeysForReferenceObjectFromRow(AbstractRecord row) {
List primaryKeyFields = getReferenceDescriptor().getPrimaryKeyFields();
Object[] result = new Object[primaryKeyFields.size()];
for (int index = 0; index < primaryKeyFields.size(); index++) {
DatabaseField targetKeyField = (DatabaseField)primaryKeyFields.get(index);
DatabaseField sourceKeyField = getTargetToSourceKeyFields().get(targetKeyField);
if (sourceKeyField == null) {
return null;
}
result[index] = row.get(sourceKeyField);
if (getReferenceDescriptor().getCachePolicy().getCacheKeyType() == CacheKeyType.ID_VALUE) {
return result[index];
}
}
return new CacheId(result);
}
/**
* INTERNAL:
* Initialize the mapping.
*/
@Override
public void initialize(AbstractSession session) throws DescriptorException {
super.initialize(session);
// Must build foreign keys fields.
List foreignKeyFields = getForeignKeyFields();
int size = foreignKeyFields.size();
for (int index = 0; index < size; index++) {
DatabaseField foreignKeyField = (DatabaseField)foreignKeyFields.get(index);
foreignKeyField = getDescriptor().buildField(foreignKeyField);
foreignKeyFields.set(index, foreignKeyField);
}
initializeForeignKeys(session);
if (shouldInitializeSelectionCriteria()) {
initializeSelectionCriteria(session);
} else {
setShouldVerifyDelete(false);
}
setFields(collectFields());
}
/**
* INTERNAL:
* The foreign keys primary keys are stored as database fields in the hashtable.
*/
protected void initializeForeignKeys(AbstractSession session) {
HashMap newSourceToTargetKeyFields = new HashMap(getSourceToTargetKeyFields().size());
HashMap newTargetToSourceKeyFields = new HashMap(getTargetToSourceKeyFields().size());
Iterator iterator = getSourceToTargetKeyFields().entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next();
DatabaseField sourceField = (DatabaseField)entry.getKey();
DatabaseField targetField = (DatabaseField)entry.getValue();
sourceField = getDescriptor().buildField(sourceField);
targetField = getReferenceDescriptor().buildField(targetField);
newSourceToTargetKeyFields.put(sourceField, targetField);
newTargetToSourceKeyFields.put(targetField, sourceField);
}
setSourceToTargetKeyFields(newSourceToTargetKeyFields);
setTargetToSourceKeyFields(newTargetToSourceKeyFields);
}
/**
* INTERNAL:
* Selection criteria is created with source foreign keys and target keys.
* This criteria is then used to read target records from the table.
*
* CR#3922 - This method is almost the same as buildSelectionCriteria() the difference
* is that getSelectionCriteria() is called
*/
protected void initializeSelectionCriteria(AbstractSession session) {
if (this.getSourceToTargetKeyFields().isEmpty()) {
throw DescriptorException.noForeignKeysAreSpecified(this);
}
Expression criteria;
Expression builder = new ExpressionBuilder();
Iterator keyIterator = getSourceToTargetKeyFields().keySet().iterator();
while (keyIterator.hasNext()) {
DatabaseField foreignKey = (DatabaseField)keyIterator.next();
DatabaseField targetKey = getSourceToTargetKeyFields().get(foreignKey);
Expression expression = builder.getField(targetKey).equal(builder.getParameter(foreignKey));
criteria = expression.and(getSelectionCriteria());
setSelectionCriteria(criteria);
}
}
/**
* INTERNAL:
* Reads the private owned object.
*/
@Override
protected Object readPrivateOwnedForObject(ObjectLevelModifyQuery modifyQuery) throws DatabaseException {
if (modifyQuery.getSession().isUnitOfWork()) {
return getRealAttributeValueFromObject(modifyQuery.getBackupClone(), modifyQuery.getSession());
} else {
if (!shouldVerifyDelete()) {
return null;
}
ReadObjectQuery readQuery = (ReadObjectQuery)getSelectionQuery().clone();
readQuery.setSelectionCriteria(getPrivateOwnedCriteria());
return modifyQuery.getSession().executeQuery(readQuery, modifyQuery.getTranslationRow());
}
}
/**
* INTERNAL:
* Selection criteria is created with source foreign keys and target keys.
*/
protected void initializePrivateOwnedCriteria() {
if (!isForeignKeyRelationship()) {
setPrivateOwnedCriteria(getSelectionCriteria());
} else {
Expression pkCriteria = getDescriptor().getObjectBuilder().getPrimaryKeyExpression();
ExpressionBuilder builder = new ExpressionBuilder();
Expression backRef = builder.getManualQueryKey(getAttributeName() + "-back-ref", getDescriptor());
Expression newPKCriteria = pkCriteria.rebuildOn(backRef);
Expression twistedSelection = backRef.twist(getSelectionCriteria(), builder);
if (getDescriptor().getQueryManager().getAdditionalJoinExpression() != null) {
// We don't have to twist the additional join because it's all against the same node, which is our base
// but we do have to rebuild it onto the manual query key
Expression rebuiltAdditional = getDescriptor().getQueryManager().getAdditionalJoinExpression().rebuildOn(backRef);
if (twistedSelection == null) {
twistedSelection = rebuiltAdditional;
} else {
twistedSelection = twistedSelection.and(rebuiltAdditional);
}
}
setPrivateOwnedCriteria(newPKCriteria.and(twistedSelection));
}
}
/**
* INTERNAL:
* Return the value of the field from the row or a value holder on the query to obtain the object.
* Check for batch + aggregation reading.
*/
@Override
public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query, CacheKey cacheKey, AbstractSession session, boolean isTargetProtected, Boolean[] wasCacheUsed) throws DatabaseException {
if (this.descriptor.getCachePolicy().isProtectedIsolation()) {
if (this.isCacheable && isTargetProtected && cacheKey != null) {
//cachekey will be null when isolating to uow
//used cached collection
Object result = null;
Object cached = cacheKey.getObject();
if (cached != null) {
if (wasCacheUsed != null){
wasCacheUsed[0] = Boolean.TRUE;
}
return this.getAttributeValueFromObject(cached);
}
return result;
} else if (!this.isCacheable && !isTargetProtected && cacheKey != null) {
return this.indirectionPolicy.buildIndirectObject(new ValueHolder(null));
}
}
// If any field in the foreign key is null then it means there are no referenced objects
// Skip for partial objects as fk may not be present.
if (!query.hasPartialAttributeExpressions()) {
for (Enumeration enumeration = getFields().elements(); enumeration.hasMoreElements();) {
DatabaseField field = (DatabaseField)enumeration.nextElement();
if (row.get(field) == null) {
return getIndirectionPolicy().nullValueFromRow();
}
}
}
// Call the default which executes the selection query,
// or wraps the query with a value holder.
//return super.valueFromRow(row, query);
ReadQuery targetQuery = getSelectionQuery();
// if the source query is cascading then the target query must use the same settings
if (targetQuery.isObjectLevelReadQuery() && (query.shouldCascadeAllParts() || (query.shouldCascadePrivateParts() && isPrivateOwned()) || (query.shouldCascadeByMapping() && this.cascadeRefresh))) {
targetQuery = (ObjectLevelReadQuery)targetQuery.clone();
((ObjectLevelReadQuery)targetQuery).setShouldRefreshIdentityMapResult(query.shouldRefreshIdentityMapResult());
targetQuery.setCascadePolicy(query.getCascadePolicy());
//CR #4365
targetQuery.setQueryId(query.getQueryId());
// For queries that have turned caching off, such as aggregate collection, leave it off.
if (targetQuery.shouldMaintainCache()) {
targetQuery.setShouldMaintainCache(query.shouldMaintainCache());
}
}
return getIndirectionPolicy().valueFromQuery(targetQuery, row, query.getSession());
}
/**
* INTERNAL:
* Get a value from the object and set that in the respective field of the row.
*/
@Override
public void writeFromObjectIntoRow(Object object, AbstractRecord Record, AbstractSession session, WriteType writeType) {
if (isReadOnly() || (!isForeignKeyRelationship())) {
return;
}
AbstractRecord referenceRow = getIndirectionPolicy().extractReferenceRow(getAttributeValueFromObject(object));
if (referenceRow == null) {
// Extract from object.
Object referenceObject = getRealAttributeValueFromObject(object, session);
for (int i = 0; i < getForeignKeyFields().size(); i++) {
DatabaseField sourceKey = getForeignKeyFields().get(i);
DatabaseField targetKey = getSourceToTargetKeyFields().get(sourceKey);
Object referenceValue = null;
// If privately owned part is null then method cannot be invoked.
if (referenceObject != null) {
referenceValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(referenceObject, targetKey, session);
}
Record.add(sourceKey, referenceValue);
}
} else {
for (int i = 0; i < getForeignKeyFields().size(); i++) {
DatabaseField sourceKey = getForeignKeyFields().get(i);
Record.add(sourceKey, referenceRow.get(sourceKey));
}
}
}
/**
* INTERNAL:
* Return the classifiction for the field contained in the mapping.
* This is used to convert the row value to a consistent java value.
*/
@Override
public Class getFieldClassification(DatabaseField fieldToClassify) throws DescriptorException {
DatabaseField fieldInTarget = getSourceToTargetKeyFields().get(fieldToClassify);
if (fieldInTarget == null) {
return null;// Can be registered as multiple table secondary field mapping
}
DatabaseMapping mapping = getReferenceDescriptor().getObjectBuilder().getMappingForField(fieldInTarget);
if (mapping == null) {
return null;// Means that the mapping is read-only
}
return mapping.getFieldClassification(fieldInTarget);
}
/**
* INTERNAL:
* The private owned criteria is only used outside of the unit of work to compare the previous value of the reference.
*/
public Expression getPrivateOwnedCriteria() {
if (privateOwnedCriteria == null) {
initializePrivateOwnedCriteria();
}
return privateOwnedCriteria;
}
/**
* INTERNAL:
* Private owned criteria is used to verify the deletion of the target.
* It joins from the source table on the foreign key to the target table,
* with a parameterization of the primary key of the source object.
*/
protected void setPrivateOwnedCriteria(Expression expression) {
privateOwnedCriteria = expression;
}
/**
* PUBLIC: Verify delete is used during delete and update on private 1:1's
* outside of a unit of work only. It checks for the previous value of the
* target object through joining the source and target tables. By default it
* is always done, but may be disabled for performance on distributed
* database reasons. In the unit of work the previous value is obtained from
* the backup-clone so it is never used.
*
* @param shouldVerifyDelete
* Sets whether delete verification should be performed
*/
public void setShouldVerifyDelete(boolean shouldVerifyDelete) {
this.shouldVerifyDelete = shouldVerifyDelete;
}
/**
* PUBLIC: Verify delete is used during delete and update outside of a unit
* of work only. It checks for the previous value of the target object
* through joining the source and target tables.
*
* @return TRUE if verify delete has been enabled
*/
public boolean shouldVerifyDelete() {
return shouldVerifyDelete;
}
/**
* INTERNAL: Gets the foreign key fields.
*
* @return The mapping from source to target key fields
*/
public Map getSourceToTargetKeyFields() {
return sourceToTargetKeyFields;
}
/**
* INTERNAL: Gets the target foreign key fields.
*
* @return The mapping from target to source key fields
*/
public Map getTargetToSourceKeyFields() {
return targetToSourceKeyFields;
}
/**
* INTERNAL: Set the source keys to target keys fields association.
*
* @param sourceToTargetKeyFields
* The mapping from source keys to target keys
*/
public void setSourceToTargetKeyFields(Map sourceToTargetKeyFields) {
this.sourceToTargetKeyFields = sourceToTargetKeyFields;
}
/**
* INTERNAL: Set the target keys to source keys fields association.
*
* @param targetToSourceKeyFields
* The mapping from target keys to source keys
*/
public void setTargetToSourceKeyFields(Map targetToSourceKeyFields) {
this.targetToSourceKeyFields = targetToSourceKeyFields;
}
/**
* INTERNAL:
* This method is not supported in an EIS environment.
*/
@Override
public void setSelectionSQLString(String sqlString) {
throw DescriptorException.invalidMappingOperation(this, "setSelectionSQLString");
}
}