| /* |
| * Copyright (c) 1998, 2021 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 |
| // 08/23/2010-2.2 Michael O'Brien |
| // - 323043: application.xml module ordering may cause weaving not to occur causing an NPE. |
| // warn if expected "_persistence_//_vh" method not found |
| // instead of throwing NPE during deploy validation. |
| package org.eclipse.persistence.mappings.foundation; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.exceptions.ConversionException; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.indirection.ValueHolderInterface; |
| import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; |
| import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator; |
| import org.eclipse.persistence.internal.descriptors.FieldTransformation; |
| import org.eclipse.persistence.internal.descriptors.InstanceVariableAttributeAccessor; |
| import org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor; |
| import org.eclipse.persistence.internal.descriptors.MethodBasedFieldTransformation; |
| import org.eclipse.persistence.internal.descriptors.TransformerBasedFieldTransformation; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.indirection.BasicIndirectionPolicy; |
| import org.eclipse.persistence.internal.indirection.ContainerIndirectionPolicy; |
| import org.eclipse.persistence.internal.indirection.DatabaseValueHolder; |
| import org.eclipse.persistence.internal.indirection.IndirectionPolicy; |
| import org.eclipse.persistence.internal.indirection.NoIndirectionPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| 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.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.ChangeRecord; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.TransformationMappingChangeRecord; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.internal.sessions.remote.RemoteValueHolder; |
| import org.eclipse.persistence.mappings.Association; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.transformers.AttributeTransformer; |
| import org.eclipse.persistence.mappings.transformers.FieldTransformer; |
| import org.eclipse.persistence.mappings.transformers.MethodBasedAttributeTransformer; |
| import org.eclipse.persistence.mappings.transformers.MethodBasedFieldTransformer; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadObjectQuery; |
| import org.eclipse.persistence.queries.WriteObjectQuery; |
| import org.eclipse.persistence.sessions.CopyGroup; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.sessions.Project; |
| import org.eclipse.persistence.sessions.remote.DistributedSession; |
| |
| /** |
| * <p><b>Purpose</b>: A transformation mapping is used for a specialized translation between how |
| * a value is represented in Java and its representation on the databae. Transformation mappings |
| * should only be used when other mappings are inadequate. |
| * |
| * @author Sati |
| * @since TOPLink/Java 1.0 |
| */ |
| public abstract class AbstractTransformationMapping extends DatabaseMapping { |
| |
| /** Name of the class which implements AttributeTransformer to be used to retrieve the attribute value */ |
| protected String attributeTransformerClassName; |
| |
| /** attributeTransformerClassName is converter to an instance of AttributeTransformer */ |
| protected AttributeTransformer attributeTransformer; |
| |
| /** Stores field name and the class name of a FieldTransformer in a vector to preserve order */ |
| protected List<FieldTransformation> fieldTransformations; |
| |
| /** The TransformerClassNames are converted into instances of FieldTransformer */ |
| protected List<Object[]> fieldToTransformers; |
| |
| /** PERF: Indicates if this mapping's attribute is a simple value which cannot be modified only replaced. */ |
| protected boolean isMutable; |
| |
| /** Implements indirection behaviour */ |
| protected IndirectionPolicy indirectionPolicy; |
| |
| /** |
| * PUBLIC: |
| * Default constructor. |
| */ |
| protected AbstractTransformationMapping() { |
| fieldTransformations = new ArrayList(); |
| fieldToTransformers = new ArrayList(); |
| setIsMutable(true); |
| dontUseIndirection(); |
| this.setWeight(WEIGHT_TRANSFORM); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the field and the name of the method |
| * that returns the value to be placed in said field |
| * when the object is written to the database. |
| * The method may take zero arguments, or it may |
| * take a single argument of type |
| * <code>org.eclipse.persistence.sessions.Session</code>. |
| */ |
| public void addFieldTransformation(DatabaseField field, String methodName) { |
| MethodBasedFieldTransformation transformation = new MethodBasedFieldTransformation(); |
| transformation.setField(field); |
| transformation.setMethodName(methodName); |
| getFieldTransformations().add(transformation); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the name of field and the name of the method |
| * that returns the value to be placed in said field |
| * when the object is written to the database. |
| * The method may take zero arguments, or it may |
| * take a single argument of type |
| * <code>org.eclipse.persistence.sessions.Session</code>. |
| */ |
| public void addFieldTransformation(String fieldName, String methodName) { |
| addFieldTransformation(new DatabaseField(fieldName), methodName); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the name of a field and the name of a class which implements |
| * the FieldTransformer interface. When the object is written, the transform |
| * method will be called on the FieldTransformer to acquire the value to put |
| * in the field. |
| */ |
| public void addFieldTransformerClassName(String fieldName, String className) { |
| addFieldTransformerClassName(new DatabaseField(fieldName), className); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the name of a field and the name of a class which implements |
| * the FieldTransformer interface. When the object is written, the transform |
| * method will be called on the FieldTransformer to acquire the value to put |
| * in the field. |
| */ |
| public void addFieldTransformerClassName(DatabaseField field, String className) { |
| TransformerBasedFieldTransformation transformation = new TransformerBasedFieldTransformation(); |
| transformation.setField(field); |
| transformation.setTransformerClassName(className); |
| |
| getFieldTransformations().add(transformation); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the name of field and the transformer |
| * that returns the value to be placed in the field |
| * when the object is written to the database. |
| */ |
| public void addFieldTransformer(String fieldName, FieldTransformer transformer) { |
| this.addFieldTransformer(new DatabaseField(fieldName), transformer); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the field and the transformer |
| * that returns the value to be placed in the field |
| * when the object is written to the database. |
| */ |
| public void addFieldTransformer(DatabaseField field, FieldTransformer transformer) { |
| TransformerBasedFieldTransformation transformation = new TransformerBasedFieldTransformation(transformer); |
| transformation.setField(field); |
| |
| getFieldTransformations().add(transformation); |
| } |
| |
| /** |
| * INTERNAL: |
| * The referenced object is checked if it is instantiated or not |
| */ |
| protected boolean areObjectsToBeProcessedInstantiated(Object object) { |
| return this.indirectionPolicy.objectIsInstantiated(getAttributeValueFromObject(object)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the attribute from the clone and assign it to the backup. |
| */ |
| @Override |
| public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) { |
| // If mapping is a no-attribute transformation mapping, do nothing |
| if (isWriteOnly()) { |
| return; |
| } |
| Object attributeValue = getAttributeValueFromObject(clone); |
| Object clonedAttributeValue = this.indirectionPolicy.backupCloneAttribute(attributeValue, clone, backup, unitOfWork); |
| setAttributeValueInObject(backup, clonedAttributeValue); |
| } |
| |
| /** |
| * INTERNAL |
| * Build a phantom row that contains only the fields |
| * for the mapping, populated with the values generated by |
| * invoking the field methods on the specified object. |
| */ |
| protected AbstractRecord buildPhantomRowFrom(Object domainObject, AbstractSession session) { |
| AbstractRecord row = new DatabaseRecord(this.fieldToTransformers.size()); |
| for (Object[] pair : this.fieldToTransformers) { |
| DatabaseField field = (DatabaseField)pair[0]; |
| FieldTransformer transformer = (FieldTransformer)pair[1]; |
| Object fieldValue = this.invokeFieldTransformer(field, transformer, domainObject, session); |
| row.put(field, fieldValue); |
| } |
| return row; |
| } |
| |
| /** |
| * INTERNAL: |
| * Builds a shallow original object. Only direct attributes and primary |
| * keys are populated. In this way the minimum original required for |
| * instantiating a working copy clone can be built without placing it in |
| * the shared cache (no concern over cycles). |
| * @param original later the input to buildCloneFromRow |
| */ |
| @Override |
| public void buildShallowOriginalFromRow(AbstractRecord record, Object original, JoinedAttributeManager joinManager, ObjectBuildingQuery query, AbstractSession executionSession) { |
| // In this case we know it is a primary key mapping, so hope that it |
| // is essentially a direct mapping. If it is a 1-1 with a |
| // no-indirection pointer back to original, then will get a stack |
| // overflow. |
| // Only solution to this is to trigger the transformation using the root |
| // session. |
| UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)query.getSession(); |
| query.setSession(unitOfWork.getParent()); |
| try { |
| readFromRowIntoObject(record, joinManager, original, null, query, executionSession, false); |
| } finally { |
| query.setSession(unitOfWork); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Used during building the backup shallow copy to copy the vector without re-registering the target objects. |
| * For 1-1 or ref the reference is from the clone so it is already registered. |
| */ |
| @Override |
| public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) { |
| return buildCloneForPartObject(attributeValue, clone, null, backup, unitOfWork, null, true, true); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the attribute from the original and assign it to the clone. |
| */ |
| @Override |
| public void buildClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) { |
| // If mapping is a no-attribute transformation mapping, do nothing |
| if (isWriteOnly()) { |
| return; |
| } |
| Object attributeValue = getAttributeValueFromObject(original); |
| Object clonedAttributeValue = this.indirectionPolicy.cloneAttribute(attributeValue, original, cacheKey, clone, refreshCascade, cloningSession, false);// building clone from an original not a row. |
| setAttributeValueInObject(clone, clonedAttributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract value from the row and set the attribute to this value in the |
| * working copy clone. |
| * In order to bypass the shared cache when in transaction a UnitOfWork must |
| * be able to populate working copies directly from the row. |
| */ |
| @Override |
| public void buildCloneFromRow(AbstractRecord record, JoinedAttributeManager joinManager, Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) { |
| // If mapping is a no-attribute transformation mapping, do nothing |
| if (isWriteOnly()) { |
| return; |
| } |
| |
| // This will set the value in the clone automatically. |
| Object attributeValue = readFromRowIntoObject(record, joinManager, clone, sharedCacheKey, sourceQuery, executionSession, true); |
| if (usesIndirection()) { |
| boolean wasCacheUsed = this.isCacheable && sharedCacheKey != null && this.descriptor.getCachePolicy().isProtectedIsolation() |
| && sharedCacheKey.getObject() != null; |
| //it would be better if wasCacheUsed could be calculated within readFromRowIntoObject but that would require changing the signature of all mappings just for |
| //transformation mapping. |
| if (!wasCacheUsed){ |
| //if the cache was used then the attribute has already been cloned by readFromRowIntoObject |
| attributeValue = this.indirectionPolicy.cloneAttribute(attributeValue, null,// no original |
| null, clone, null, unitOfWork, true);// build clone directly from row. |
| } |
| setAttributeValueInObject(clone, attributeValue); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Require for cloning, the part must be cloned. |
| * Ignore the attribute value, go right to the object itself. |
| */ |
| @Override |
| public Object buildCloneForPartObject(Object attributeValue, Object original, CacheKey cacheKey, Object clone, AbstractSession cloningSession, Integer refreshCascade, boolean isExisting, boolean isFromSharedCache) { |
| if (isReadOnly() || !isMutable()) { |
| return attributeValue; |
| } |
| AbstractRecord row = buildPhantomRowFrom(original, cloningSession); |
| return invokeAttributeTransformer(row, clone, cloningSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Copy of the attribute of the object. |
| * This is NOT used for unit of work but for templatizing an object. |
| */ |
| @Override |
| public void buildCopy(Object copy, Object original, CopyGroup group) { |
| // If mapping is a no-attribute transformation mapping, do nothing |
| if (isWriteOnly()) { |
| return; |
| } |
| |
| Object clonedAttributeValue; |
| // If the mapping is read-only, a direct pass through of the value will be performed. |
| // This is done because the method invocation is not possible as the row will be |
| // empty and we have no way to clone the value. |
| // Since the value cannot change anyway we just pass it through. |
| if (isReadOnly() || !isMutable()) { |
| clonedAttributeValue = getRealAttributeValueFromObject(original, group.getSession()); |
| } else { |
| AbstractRecord row = buildPhantomRowFrom(original, group.getSession()); |
| clonedAttributeValue = invokeAttributeTransformer(row, copy, group.getSession()); |
| } |
| this.indirectionPolicy.reset(copy); |
| setRealAttributeValueInObject(copy, clonedAttributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade perform delete through mappings that require the cascade |
| */ |
| @Override |
| public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects){ |
| //objects referenced by this mapping are not registered as they have |
| // no identity, this is a no-op. |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade registerNew for Create through mappings that require the cascade |
| */ |
| @Override |
| public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects){ |
| //Objects referenced through transformation mappings are not registered as |
| // they have no identity, this is a no-op. |
| } |
| |
| /** |
| * INTERNAL: |
| * The mapping clones itself to create deep copy. |
| */ |
| @Override |
| public Object clone() { |
| AbstractTransformationMapping clone = (AbstractTransformationMapping)super.clone(); |
| clone.setFieldToTransformers(new ArrayList(this.fieldToTransformers.size())); |
| |
| for (Object[] pair : this.fieldToTransformers) { |
| Object[] transformation = new Object[2]; |
| transformation[0] = pair[0]; |
| transformation[1] = pair[1]; |
| clone.getFieldToTransformers().add(transformation); |
| } |
| |
| clone.setIndirectionPolicy((IndirectionPolicy)indirectionPolicy.clone()); |
| |
| return clone; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return all the fields with this mapping. |
| */ |
| @Override |
| protected Vector collectFields() { |
| Vector databaseFields = new Vector(this.fieldToTransformers.size()); |
| for (Object[] pair : this.fieldToTransformers) { |
| databaseFields.add(pair[0]); |
| } |
| return databaseFields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) { |
| if (isReadOnly() || isWriteOnly()) { |
| return null; |
| } |
| Object cloneAttribute = getAttributeValueFromObject(clone); |
| Object backUpAttribute = null; |
| if ((cloneAttribute != null) && (!this.indirectionPolicy.objectIsInstantiated(cloneAttribute))) { |
| return null; |
| } |
| boolean difference = false; |
| Object backupValue = null; |
| if (owner.isNew()) { |
| difference = true; |
| } else { |
| if (backUp != null) { |
| backUpAttribute = getAttributeValueFromObject(backUp); |
| backupValue = this.indirectionPolicy.getRealAttributeValueFromObject(backUp, backUpAttribute); |
| } |
| boolean backUpIsInstantiated = ((backUpAttribute == null) || (this.indirectionPolicy.objectIsInstantiated(backUpAttribute))); |
| Object cloneValue = this.indirectionPolicy.getRealAttributeValueFromObject(clone, cloneAttribute); |
| if (backUpIsInstantiated) { |
| if (cloneValue == backupValue) { |
| return null; |
| } |
| if (((cloneValue != null && (backupValue != null)) && cloneValue.equals(backupValue))) { |
| return null; |
| } |
| } |
| |
| for (Object[] pair : this.fieldToTransformers) { |
| DatabaseField field = (DatabaseField)pair[0]; |
| FieldTransformer transformer = (FieldTransformer)pair[1]; |
| Object cloneFieldValue = null; |
| Object backUpFieldValue = null; |
| if (clone != null) { |
| cloneFieldValue = invokeFieldTransformer(field, transformer, clone, session); |
| } |
| if ((backUpIsInstantiated) && (backUp != null)) { |
| backUpFieldValue = invokeFieldTransformer(field, transformer, backUp, session); |
| } |
| |
| if (cloneFieldValue == backUpFieldValue) { |
| continue; // skip this iteration, go to the next one |
| } |
| if ((cloneFieldValue == null) || (backUpFieldValue == null)) { |
| difference = true; |
| break; // There is a difference. |
| } |
| if (cloneFieldValue.equals(backUpFieldValue)) { |
| continue; // skip this iteration, go to the next one |
| } |
| if (Helper.comparePotentialArrays(cloneFieldValue, backUpFieldValue)) { |
| continue; // skip this iteration, go to the next one |
| } |
| difference = true; |
| break; // There is a difference. |
| } |
| } |
| if (difference) { |
| return internalBuildChangeRecord(clone, backupValue, owner, session); |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Directly build a change record without comparison |
| */ |
| @Override |
| public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) { |
| return internalBuildChangeRecord(clone, null, owner, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Build a change record. |
| */ |
| public ChangeRecord internalBuildChangeRecord(Object clone, Object oldValue, ObjectChangeSet owner, AbstractSession session) { |
| TransformationMappingChangeRecord changeRecord = new TransformationMappingChangeRecord(owner); |
| changeRecord.setRow(buildPhantomRowFrom(clone, session)); |
| changeRecord.setAttribute(getAttributeName()); |
| changeRecord.setMapping(this); |
| changeRecord.setOldValue(oldValue); |
| return changeRecord; |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { |
| if (!isWriteOnly()) { |
| // PERF: Checks if attribute values are equal first before apply field translation. |
| Object firstValue = getRealAttributeValueFromObject(firstObject, session); |
| Object secondValue = getRealAttributeValueFromObject(secondObject, session); |
| if (firstValue == secondValue) { |
| return true; |
| } |
| if ((firstValue == null) || (secondValue == null)) { |
| return false; |
| } |
| if (firstValue.equals(secondValue)) { |
| return true; |
| } |
| } |
| |
| for (Object[] pair : this.fieldToTransformers) { |
| DatabaseField field = (DatabaseField)pair[0]; |
| FieldTransformer transformer = (FieldTransformer)pair[1]; |
| Object firstFieldValue = invokeFieldTransformer(field, transformer, firstObject, session); |
| Object secondFieldValue = invokeFieldTransformer(field, transformer, secondObject, session); |
| |
| if (firstFieldValue == secondFieldValue) { |
| continue; // skip this iteration, go to the next one |
| } |
| if ((firstFieldValue == null) || (secondFieldValue == null)) { |
| return false; |
| } |
| if (!firstFieldValue.equals(secondFieldValue)) { |
| if (!Helper.comparePotentialArrays(firstFieldValue, secondFieldValue)) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert all the class-name-based settings in this mapping to actual class-based |
| * settings |
| */ |
| @Override |
| public void convertClassNamesToClasses(ClassLoader classLoader){ |
| super.convertClassNamesToClasses(classLoader); |
| |
| if (attributeTransformerClassName != null) { |
| Class attributeTransformerClass = null; |
| try { |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) { |
| try { |
| attributeTransformerClass = AccessController.doPrivileged(new PrivilegedClassForName<>(attributeTransformerClassName, true, classLoader)); |
| } catch (PrivilegedActionException exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(attributeTransformerClassName, exception.getException()); |
| } |
| } else { |
| attributeTransformerClass = PrivilegedAccessHelper.getClassForName(attributeTransformerClassName, true, classLoader); |
| } |
| } catch (ClassNotFoundException exc){ |
| throw ValidationException.classNotFoundWhileConvertingClassNames(attributeTransformerClassName, exc); |
| } |
| |
| this.setAttributeTransformerClass(attributeTransformerClass); |
| } |
| |
| for (FieldTransformation transformation : getFieldTransformations()) { |
| if (transformation instanceof TransformerBasedFieldTransformation) { |
| TransformerBasedFieldTransformation transformer = (TransformerBasedFieldTransformation)transformation; |
| String transformerClassName = transformer.getTransformerClassName(); |
| if (transformerClassName == null) { |
| return; |
| } |
| Class transformerClass = null; |
| try { |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ |
| try { |
| transformerClass = AccessController.doPrivileged(new PrivilegedClassForName<>(transformerClassName, true, classLoader)); |
| } catch (PrivilegedActionException exception) { |
| throw ValidationException.classNotFoundWhileConvertingClassNames(transformerClassName, exception.getException()); |
| } |
| } else { |
| transformerClass = PrivilegedAccessHelper.getClassForName(transformerClassName, true, classLoader); |
| } |
| } catch (ClassNotFoundException exc){ |
| throw ValidationException.classNotFoundWhileConvertingClassNames(transformerClassName, exc); |
| } |
| transformer.setTransformerClass(transformerClass); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Builder the unit of work value holder. |
| * Ignore the original object. |
| * @param buildDirectlyFromRow indicates that we are building the clone directly |
| * from a row as opposed to building the original from the row, putting it in |
| * the shared cache, and then cloning the original. |
| */ |
| @Override |
| public DatabaseValueHolder createCloneValueHolder(ValueHolderInterface attributeValue, Object original, Object clone, AbstractRecord row, AbstractSession cloningSession, boolean buildDirectlyFromRow) { |
| return cloningSession.createCloneTransformationValueHolder(attributeValue, original, clone, this); |
| } |
| |
| /** |
| * PUBLIC: |
| * Indirection means that a ValueHolder will be put in-between the attribute and the real object. |
| * This defaults to false and only required for transformations that perform database access. |
| */ |
| public void dontUseIndirection() { |
| setIndirectionPolicy(new NoIndirectionPolicy()); |
| } |
| |
| /** |
| * INTERNAL: |
| * An object has been serialized from the server to the client. |
| * Replace the transient attributes of the remote value holders |
| * with client-side objects. |
| */ |
| @Override |
| public void fixObjectReferences(Object object, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, DistributedSession session) { |
| this.indirectionPolicy.fixObjectReferences(object, objectDescriptors, processedObjects, query, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * The attributeTransformer stores an instance of the class which implements |
| * AttributeTransformer. |
| */ |
| public AttributeTransformer getAttributeTransformer() { |
| return attributeTransformer; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the attribute transformation method name. |
| */ |
| public String getAttributeMethodName() { |
| if (this.attributeTransformer instanceof MethodBasedAttributeTransformer) { |
| return ((MethodBasedAttributeTransformer)this.attributeTransformer).getMethodName(); |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the attribute transformer's class. |
| * This is used to map to XML. |
| */ |
| public Class getAttributeTransformerClass() { |
| if ((this.attributeTransformer == null) || (this.attributeTransformer instanceof MethodBasedAttributeTransformer)) { |
| return null; |
| } |
| return this.attributeTransformer.getClass(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the attribute transformer's class. |
| * This is used to map from XML. |
| */ |
| public void setAttributeTransformerClass(Class attributeTransformerClass) { |
| if (attributeTransformerClass == null) { |
| return; |
| } |
| try { |
| Object instance = null; |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) { |
| try { |
| instance = AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(attributeTransformerClass)); |
| } catch (PrivilegedActionException ex) { |
| throw (Exception)ex.getCause(); |
| } |
| } else { |
| instance = PrivilegedAccessHelper.newInstanceFromClass(attributeTransformerClass); |
| } |
| setAttributeTransformer((AttributeTransformer)instance); |
| } catch (Exception exception) { |
| throw DescriptorException.attributeTransformerClassInvalid(attributeTransformerClass.getName(), this, exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the attribute transformer class name |
| */ |
| public String getAttributeTransformerClassName() { |
| return attributeTransformerClassName; |
| } |
| |
| /** |
| * INTERNAL: |
| * Check for write-only, one-way transformation. |
| */ |
| @Override |
| public Object getAttributeValueFromObject(Object object) throws DescriptorException { |
| if (isWriteOnly()) { |
| return null; |
| } |
| Object attributeValue = super.getAttributeValueFromObject(object); |
| return this.indirectionPolicy.validateAttributeOfInstantiatedObject(attributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns a Vector which stores fieldnames and the respective method/transformer names. |
| */ |
| public List<FieldTransformation> getFieldTransformations() { |
| return fieldTransformations; |
| } |
| |
| /** |
| * INTERNAL: |
| * @return a vector which stores fields and their respective transformers. |
| */ |
| public List<Object[]> getFieldToTransformers() { |
| return fieldToTransformers; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the mapping's indirection policy. |
| */ |
| public IndirectionPolicy getIndirectionPolicy() { |
| return indirectionPolicy; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the real attribute value from the reference object's attribute value. |
| * If the attribute is using indirection the value of the value-holder is returned. |
| * If the value holder is not instantiated then it is instantiated. |
| */ |
| @Override |
| public Object getRealAttributeValueFromAttribute(Object attributeValue, Object object, AbstractSession session) { |
| return this.indirectionPolicy.getRealAttributeValueFromObject(object, attributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Trigger the instantiation of the attribute if lazy. |
| */ |
| @Override |
| public void instantiateAttribute(Object object, AbstractSession session) { |
| this.indirectionPolicy.instantiateObject(object, getAttributeValueFromObject(object)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract and return the appropriate value from the |
| * specified remote value holder. |
| */ |
| @Override |
| public Object getValueFromRemoteValueHolder(RemoteValueHolder remoteValueHolder) { |
| return this.indirectionPolicy.getValueFromRemoteValueHolder(remoteValueHolder); |
| } |
| |
| /** |
| * INTERNAL: |
| * The mapping is initialized with the given session. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| super.initialize(session); |
| initializeAttributeTransformer(session); |
| initializeFieldToTransformers(session); |
| setFields(collectFields()); |
| this.indirectionPolicy.initialize(); |
| if (usesIndirection()) { |
| for (DatabaseField field : this.fields) { |
| field.setKeepInRow(true); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert the attribute transformer class name into an AttributeTransformer |
| * If the old-style method name in set, then use a MethodBasedAttributeTRansformer |
| */ |
| protected void initializeAttributeTransformer(AbstractSession databaseSession) throws DescriptorException { |
| if (isWriteOnly()) { |
| return; |
| } |
| this.attributeTransformer.initialize(this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Required for reverse compatibility and test cases: |
| * @return a hash table containing the fieldName and their respective method names |
| */ |
| public Hashtable getFieldNameToMethodNames() { |
| Hashtable table = new Hashtable(getFieldTransformations().size()); |
| Iterator<FieldTransformation> transformations = getFieldTransformations().iterator(); |
| while (transformations.hasNext()) { |
| FieldTransformation transformation = transformations.next(); |
| if (transformation instanceof MethodBasedFieldTransformation) { |
| table.put(transformation.getField().getQualifiedName(), ((MethodBasedFieldTransformation)transformation).getMethodName()); |
| } |
| } |
| return table; |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert the field names and their corresponding method names to |
| * DatabaseFields and Methods. |
| */ |
| protected void initializeFieldToTransformers(AbstractSession session) throws DescriptorException { |
| for (Object[] pair : this.fieldToTransformers) { |
| pair[0] = getDescriptor().buildField(((DatabaseField)pair[0])); |
| ((FieldTransformer)pair[1]).initialize(this); |
| } |
| for (FieldTransformation transformation : getFieldTransformations()) { |
| DatabaseField field = getDescriptor().buildField(transformation.getField()); |
| String transformerClassName = "MethodBasedFieldTransformer"; |
| FieldTransformer transformer = null; |
| try { |
| transformer = transformation.buildTransformer(); |
| } catch (ConversionException ex) { |
| if (transformation instanceof TransformerBasedFieldTransformation) { |
| transformerClassName = ((TransformerBasedFieldTransformation)transformation).getTransformerClassName(); |
| } |
| |
| throw DescriptorException.fieldTransformerClassNotFound(transformerClassName, this, ex); |
| } catch (Exception ex) { |
| if (transformation instanceof TransformerBasedFieldTransformation) { |
| transformerClassName = ((TransformerBasedFieldTransformation)transformation).getTransformerClassName(); |
| } |
| |
| throw DescriptorException.fieldTransformerClassInvalid(transformerClassName, this, ex); |
| } |
| |
| transformer.initialize(this); |
| // Attempt to ensure a type is set on the field. |
| if (field.getType() == null) { |
| if (transformer instanceof MethodBasedFieldTransformer) { |
| field.setType(((MethodBasedFieldTransformer)transformer).getFieldType()); |
| } else if (field.getColumnDefinition() != null) { |
| // Search for the type for this field definition. |
| if (session.getDatasourcePlatform() instanceof DatabasePlatform) { |
| Iterator<Map.Entry<Class<?>, FieldTypeDefinition>> iterator = session.getPlatform().getFieldTypes().entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<Class<?>, FieldTypeDefinition> entry = iterator.next(); |
| if (entry.getValue().getName().equals(field.getColumnDefinition())) { |
| field.setType(entry.getKey()); |
| break; |
| } |
| } |
| } |
| } |
| } |
| Object[] fieldToTransformer = new Object[2]; |
| fieldToTransformer[0] = field; |
| fieldToTransformer[1] = transformer; |
| |
| this.fieldToTransformers.add(fieldToTransformer); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Invoke the buildAttributeValue method on the AttributeTransformer |
| */ |
| public Object invokeAttributeTransformer(AbstractRecord record, Object domainObject, AbstractSession session) throws DescriptorException { |
| return this.attributeTransformer.buildAttributeValue(record, domainObject, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Invoke the buildFieldValue on the appropriate FieldTransformer |
| */ |
| protected Object invokeFieldTransformer(DatabaseField field, FieldTransformer transformer, Object domainObject, AbstractSession session) throws DescriptorException { |
| return transformer.buildFieldValue(domainObject, field.getName(), session); |
| } |
| |
| protected Object invokeFieldTransformer(DatabaseField field, Object domainObject, AbstractSession session) { |
| for (Object[] pair : this.fieldToTransformers) { |
| if (field.equals(pair[0])) { |
| return invokeFieldTransformer(field, (FieldTransformer)pair[1], domainObject, session); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return true if the attribute for this mapping is not a simple atomic value that cannot be modified, |
| * only replaced. |
| * This is true by default for non-primitives, but can be set to false to avoid cloning |
| * and change comparison in the unit of work. |
| */ |
| public boolean isMutable() { |
| return isMutable; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if read-only is explicitly set to true; |
| * otherwise return whether the transformation has no fields |
| * (no fields = read-only) |
| */ |
| @Override |
| public boolean isReadOnly() { |
| if (super.isReadOnly()) { |
| return true; |
| } else { |
| return getFieldTransformations().isEmpty() && this.fieldToTransformers.isEmpty(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isTransformationMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the transformation has no attribute, is write only. |
| */ |
| @Override |
| public boolean isWriteOnly() { |
| return (getAttributeName() == null) && ((this.attributeTransformer == null) && (this.attributeTransformerClassName == null)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Perform the iteration opperation on the iterators current objects attributes. |
| * Only require if primitives are desired. |
| */ |
| @Override |
| public void iterate(DescriptorIterator iterator) { |
| Object attributeValue = getAttributeValueFromObject(iterator.getVisitedParent()); |
| this.indirectionPolicy.iterateOnAttributeValue(iterator, attributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate on the attribute value. |
| * The value holder has already been processed. |
| */ |
| @Override |
| public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) { |
| iterator.iteratePrimitiveForMapping(realAttributeValue, this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. Which is the original from the parent UnitOfWork |
| */ |
| @Override |
| public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (isWriteOnly()) { |
| return; |
| } |
| // PERF: If not mutable then just set the value from the source. |
| if (!isMutable() && (source != null)) { |
| setRealAttributeValueInObject(target, getRealAttributeValueFromObject(source, mergeManager.getSession())); |
| return; |
| } |
| AbstractRecord record = (AbstractRecord)((TransformationMappingChangeRecord)changeRecord).getRecord(); |
| Object attributeValue = invokeAttributeTransformer(record, target, targetSession); |
| setRealAttributeValueInObject(target, attributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| */ |
| @Override |
| public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| if (isWriteOnly()) { |
| return; |
| } |
| |
| // do refresh check first as I may need to reset remote value holder |
| if (mergeManager.shouldRefreshRemoteObject() && usesIndirection()) { |
| this.indirectionPolicy.mergeRemoteValueHolder(target, source, mergeManager); |
| return; |
| } |
| |
| if (mergeManager.isForRefresh()) { |
| if (!areObjectsToBeProcessedInstantiated(target)) { |
| // This will occur when the clone's value has not been instantiated yet and we do not need |
| // the refresh that attribute |
| return; |
| } |
| } else if (!areObjectsToBeProcessedInstantiated(source)) { |
| // I am merging from a clone into an original. No need to do merge if the attribute was never |
| // modified |
| return; |
| } |
| |
| if (isTargetUnInitialized) { |
| // This will happen if the target object was removed from the cache before the commit was attempted |
| if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!areObjectsToBeProcessedInstantiated(source))) { |
| setAttributeValueInObject(target, this.indirectionPolicy.getOriginalIndirectionObject(getAttributeValueFromObject(source), targetSession)); |
| return; |
| } |
| } |
| if (isReadOnly()) { |
| // if it is read only then we do not have any fields specified for the |
| // transformer, without fields we can not build the row, so just copy |
| // over the value alternatively we could build the entire row for the object. |
| setRealAttributeValueInObject(target, getRealAttributeValueFromObject(source, mergeManager.getSession())); |
| return; |
| } |
| if (!isMutable()) { |
| Object attribute = getRealAttributeValueFromObject(source, mergeManager.getSession()); |
| if (this.descriptor.getObjectChangePolicy().isObjectChangeTrackingPolicy()) { |
| // Object level or attribute level so lets see if we need to raise the event? |
| Object targetAttribute = getRealAttributeValueFromObject(target, mergeManager.getSession()); |
| if ((mergeManager.shouldMergeCloneIntoWorkingCopy() || mergeManager.shouldMergeCloneWithReferencesIntoWorkingCopy()) && !mergeManager.isForRefresh() |
| && (((targetAttribute == null) && (attribute != null)) || ((targetAttribute != null) && ((attribute == null) || ((!targetAttribute.equals(attribute)) && (!Helper.comparePotentialArrays(targetAttribute, attribute))))))) { |
| this.descriptor.getObjectChangePolicy().raiseInternalPropertyChangeEvent(target, getAttributeName(), targetAttribute, attribute); |
| } |
| } |
| setRealAttributeValueInObject(target, attribute); |
| return; |
| } |
| |
| // This dumps the attribute into the row and back. |
| AbstractRecord row = buildPhantomRowFrom(source, mergeManager.getSession()); |
| Object attributeValue = invokeAttributeTransformer(row, source, mergeManager.getSession()); |
| AbstractRecord targetRow = buildPhantomRowFrom(target, mergeManager.getSession()); |
| setRealAttributeValueInObject(target, attributeValue); |
| //set the change after the set on the object as this mapping uses the object to build the change record. |
| if (this.descriptor.getObjectChangePolicy().isObjectChangeTrackingPolicy()) { |
| for (Enumeration keys = targetRow.keys(); keys.hasMoreElements(); ){ |
| Object field = keys.nextElement(); |
| if ((mergeManager.shouldMergeCloneIntoWorkingCopy() || mergeManager.shouldMergeCloneWithReferencesIntoWorkingCopy()) |
| && (!row.get(field).equals(targetRow.get(field)))) { |
| this.descriptor.getObjectChangePolicy().raiseInternalPropertyChangeEvent(target, getAttributeName(), invokeAttributeTransformer(targetRow, source, mergeManager.getSession()), attributeValue); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Allow for initialization of properties and validation. |
| */ |
| @Override |
| public void preInitialize(AbstractSession session) throws DescriptorException { |
| if (isWriteOnly()) { |
| return;// Allow for one-way transformations. |
| } |
| |
| super.preInitialize(session); |
| |
| // PERF: Also auto-set mutable to false is the attribute type is a primitive. |
| // This means it is not necessary to clone the value (through double transformation). |
| if ((getAttributeClassification() != null) && (getAttributeClassification().isPrimitive() || Helper.isPrimitiveWrapper(getAttributeClassification()) || getAttributeClassification().equals(ClassConstants.STRING) || getAttributeClassification().equals(ClassConstants.BIGDECIMAL) || getAttributeClassification().equals(ClassConstants.NUMBER))) { |
| setIsMutable(false); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Extracts value from return row and set the attribute to the value in the object. |
| * Return row is merged into object after execution of insert or update call |
| * according to ReturningPolicy. |
| */ |
| public Object readFromReturnRowIntoObject(AbstractRecord row, Object object, ReadObjectQuery query, Collection handledMappings, ObjectChangeSet changeSet) throws DatabaseException { |
| int size = this.fields.size(); |
| AbstractRecord transformationRow = new DatabaseRecord(size); |
| for (int i = 0; i < size; i++) { |
| DatabaseField field = this.fields.get(i); |
| Object value; |
| if (row.containsKey(field)) { |
| value = row.get(field); |
| } else { |
| value = valueFromObject(object, field, query.getSession()); |
| } |
| transformationRow.add(field, value); |
| } |
| |
| if(changeSet != null && (!changeSet.isNew() || (query.getDescriptor() != null && query.getDescriptor().shouldUseFullChangeSetsForNewObjects()))) { |
| TransformationMappingChangeRecord record = (TransformationMappingChangeRecord)changeSet.getChangesForAttributeNamed(attributeName); |
| if (record == null) { |
| record = new TransformationMappingChangeRecord(changeSet); |
| record.setAttribute(attributeName); |
| record.setMapping(this); |
| record.setOldValue(getAttributeValueFromObject(object)); |
| changeSet.addChange(record); |
| } |
| record.setRow(transformationRow); |
| } |
| |
| Object attributeValue = readFromRowIntoObject(transformationRow, null, object, null, query, query.getSession(), true); |
| if (handledMappings != null) { |
| handledMappings.add(this); |
| } |
| |
| return attributeValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract value from the row and set the attribute to the value in the object. |
| */ |
| @Override |
| public Object readFromRowIntoObject(AbstractRecord row, JoinedAttributeManager joinManager, Object object, CacheKey parentCacheKey, ObjectBuildingQuery query, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException { |
| if (isWriteOnly()) { |
| return null; |
| } |
| if (this.descriptor.getCachePolicy().isProtectedIsolation()) { |
| if (this.isCacheable && isTargetProtected && parentCacheKey != null) { |
| Object cached = parentCacheKey.getObject(); |
| if (cached != null) { |
| Object attributeValue = getAttributeValueFromObject(cached); |
| Integer refreshCascade = null; |
| if (query != null && query.shouldRefreshIdentityMapResult()){ |
| refreshCascade = query.getCascadePolicy(); |
| } |
| return this.indirectionPolicy.cloneAttribute(attributeValue, cached, parentCacheKey, object, refreshCascade, executionSession, false); |
| } |
| return null; |
| } |
| } |
| if (row != null && row.hasSopObject()) { |
| return getAttributeValueFromObject(row.getSopObject()); |
| } |
| Object attributeValue = this.indirectionPolicy.valueFromMethod(object, row, query.getSession()); |
| Object oldAttribute = null; |
| if (executionSession.isUnitOfWork() && query.shouldRefreshIdentityMapResult()){ |
| oldAttribute = this.getAttributeValueFromObject(object); |
| } |
| try { |
| this.attributeAccessor.setAttributeValueInObject(object, attributeValue); |
| } catch (DescriptorException exception) { |
| exception.setMapping(this); |
| throw exception; |
| } |
| if (executionSession.isUnitOfWork() && query.shouldRefreshIdentityMapResult()){ |
| if (this.indirectionPolicy.objectIsInstantiatedOrChanged(oldAttribute)){ |
| this.indirectionPolicy.instantiateObject(object, attributeValue); |
| } |
| } |
| return attributeValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Needed for backwards compatibility |
| */ |
| public Vector getFieldNameToMethodNameAssociations() { |
| Vector associations = new Vector(); |
| for (Iterator<FieldTransformation> source = getFieldTransformations().iterator(); source.hasNext();) { |
| FieldTransformation tf = source.next(); |
| if (tf instanceof MethodBasedFieldTransformation) { |
| Association ass = new Association(); |
| ass.setKey(tf.getField().getQualifiedName()); |
| ass.setValue(((MethodBasedFieldTransformation)tf).getMethodName()); |
| associations.addElement(ass); |
| } |
| } |
| return associations; |
| } |
| |
| /** |
| * INTERNAL: |
| * needed for backwards compatibility |
| */ |
| public void setFieldNameToMethodNameAssociations(Vector associations) { |
| setFieldTransformations(org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(associations.size())); |
| for (Iterator source = associations.iterator(); source.hasNext();) { |
| Association ass = (Association)source.next(); |
| MethodBasedFieldTransformation tf = new MethodBasedFieldTransformation(); |
| tf.setField(new DatabaseField((String)ass.getKey())); |
| tf.setMethodName((String)ass.getValue()); |
| getFieldTransformations().add(tf); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Once descriptors are serialized to the remote session. All its mappings and reference descriptors are traversed. Usually |
| * mappings are initialized and serialized reference descriptors are replaced with local descriptors if they already exist on the |
| * remote session. |
| */ |
| @Override |
| public void remoteInitialization(DistributedSession session) { |
| setFieldToTransformers(new Vector()); |
| |
| // Remote mappings is initialized here again because while serializing only the uninitialized data is passed |
| // as the initialized data is not serializable. |
| if (!isWriteOnly()) { |
| super.remoteInitialization(session); |
| initializeAttributeTransformer(session); |
| } |
| initializeFieldToTransformers(session); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the AttributeTransformer, this transformer will be used to extract the value for the |
| * object's attribute from the database row. |
| */ |
| public void setAttributeTransformer(AttributeTransformer transformer) { |
| attributeTransformer = transformer; |
| if ((transformer != null) && !(transformer instanceof MethodBasedAttributeTransformer)) { |
| attributeTransformerClassName = transformer.getClass().getName(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the Attribute Transformer Class Name |
| */ |
| public void setAttributeTransformerClassName(String className) { |
| attributeTransformerClassName = className; |
| } |
| |
| |
| /** |
| * PUBLIC: |
| * To set the attribute method name. The method is invoked internally by TopLink |
| * to retrieve the value to store in the domain object. The method receives Record |
| * as its parameter and optionally Session, and should extract the value from the |
| * record to set into the object, but should not set the value on the object, only return it. |
| */ |
| public void setAttributeTransformation(String methodName) { |
| if ((methodName != null) && (!methodName.isEmpty())) { |
| setAttributeTransformer(new MethodBasedAttributeTransformer(methodName)); |
| } else { |
| setAttributeTransformer(null); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Check for write-only, one-way transformations. |
| */ |
| @Override |
| public void setAttributeValueInObject(Object object, Object value) { |
| if (isWriteOnly()) { |
| return; |
| } |
| |
| super.setAttributeValueInObject(object, value); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set if the value of the attribute is atomic or a complex mutable object and can be modified without replacing the entire object. |
| * This defaults to true for non-primitives, but can be set to false to optimize object cloning and change comparison. |
| */ |
| public void setIsMutable(boolean mutable) { |
| this.isMutable = mutable; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the value of the attribute mapped by this mapping, |
| * placing it inside a value holder if necessary. |
| * If the value holder is not instantiated then it is instantiated. |
| * Check for write-only, one-way transformations. |
| */ |
| @Override |
| public void setRealAttributeValueInObject(Object object, Object value) throws DescriptorException { |
| if (isWriteOnly()) { |
| return; |
| } |
| this.indirectionPolicy.setRealAttributeValueInObject(object, value); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the field to method name associations. |
| */ |
| public void setFieldTransformations(List<FieldTransformation> fieldTransformations) { |
| this.fieldTransformations = fieldTransformations; |
| } |
| |
| protected void setFieldToTransformers(List<Object[]> fieldToTransformers) { |
| this.fieldToTransformers = fieldToTransformers; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the indirection policy. |
| */ |
| public void setIndirectionPolicy(IndirectionPolicy indirectionPolicy) { |
| this.indirectionPolicy = indirectionPolicy; |
| indirectionPolicy.setMapping(this); |
| } |
| |
| /** |
| * INTERNAL: |
| * Will be used by Gromit. For details see usesIndirection(). |
| * @see #useBasicIndirection() |
| * @see #dontUseIndirection() |
| */ |
| public void setUsesIndirection(boolean usesIndirection) { |
| if (usesIndirection) { |
| useBasicIndirection(); |
| } else { |
| dontUseIndirection(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Either create a new change record or update the change record with the new value. |
| * This is used by attribute change tracking. |
| */ |
| @Override |
| public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) { |
| TransformationMappingChangeRecord changeRecord = (TransformationMappingChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName()); |
| Object updatedObject = descriptor.getInstantiationPolicy().buildNewInstance(); |
| this.setAttributeValueInObject(updatedObject, newValue); |
| if (!isWriteOnly()) { |
| if (changeRecord == null) { |
| objectChangeSet.addChange(internalBuildChangeRecord(updatedObject, oldValue, objectChangeSet, uow)); |
| } else { |
| changeRecord.setRow(this.buildPhantomRowFrom(updatedObject, uow)); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if this mapping supports change tracking. |
| */ |
| @Override |
| public boolean isChangeTrackingSupported(Project project) { |
| return ! isMutable(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the specified object is instantiated. |
| */ |
| @Override |
| public boolean isAttributeValueFromObjectInstantiated(Object object) { |
| return this.indirectionPolicy.objectIsInstantiated(object); |
| } |
| |
| /** |
| * PUBLIC: |
| * Indirection means that a ValueHolder will be put in-between the attribute and the real object. |
| * This defaults to false and only required for transformations that perform database access. |
| */ |
| public void useBasicIndirection() { |
| setIndirectionPolicy(new BasicIndirectionPolicy()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Indirection means that a IndirectContainer (wrapping a ValueHolder) will be put in-between |
| * the attribute and the real object. |
| * This allows for the reading of the target from the database to be delayed until accessed. |
| * This defaults to true and is strongly suggested as it give a huge performance gain. |
| */ |
| public void useContainerIndirection(Class containerClass) { |
| ContainerIndirectionPolicy policy = new ContainerIndirectionPolicy(); |
| policy.setContainerClass(containerClass); |
| setIndirectionPolicy(policy); |
| } |
| |
| /** |
| * PUBLIC: |
| * Indirection means that a ValueHolder will be put in-between the attribute and the real object. |
| * This defaults to false and only required for transformations that perform database access. |
| * @see #useBasicIndirection() |
| */ |
| public void useIndirection() { |
| useBasicIndirection(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Indirection means that a ValueHolder will be put in-between the attribute and the real object. |
| * This defaults to false and only required for transformations that perform database access. |
| * @see org.eclipse.persistence.internal.indirection.IndirectionPolicy |
| */ |
| public boolean usesIndirection() { |
| return this.indirectionPolicy.usesIndirection(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Validate mapping declaration |
| */ |
| @Override |
| public void validateBeforeInitialization(AbstractSession session) throws DescriptorException { |
| super.validateBeforeInitialization(session); |
| |
| if (isWriteOnly()) { |
| return; |
| } |
| |
| if ((this.attributeTransformer == null) && (this.attributeTransformerClassName == null)) { |
| session.getIntegrityChecker().handleError(DescriptorException.noAttributeTransformationMethod(this)); |
| } |
| |
| if (getAttributeAccessor() instanceof InstanceVariableAttributeAccessor) { |
| Class attributeType = ((InstanceVariableAttributeAccessor)getAttributeAccessor()).getAttributeType(); |
| this.indirectionPolicy.validateDeclaredAttributeType(attributeType, session.getIntegrityChecker()); |
| } else if (getAttributeAccessor().isMethodAttributeAccessor()) { |
| // 323403 |
| Class returnType = ((MethodAttributeAccessor)getAttributeAccessor()).getGetMethodReturnType(); |
| this.indirectionPolicy.validateGetMethodReturnType(returnType, session.getIntegrityChecker()); |
| |
| Class parameterType = ((MethodAttributeAccessor)getAttributeAccessor()).getSetMethodParameterType(); |
| this.indirectionPolicy.validateSetMethodParameterType(parameterType, session.getIntegrityChecker()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| */ |
| @Override |
| public Object valueFromObject(Object object, DatabaseField field, AbstractSession session) { |
| return invokeFieldTransformer(field, object, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| */ |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| if (isReadOnly()) { |
| return; |
| } |
| |
| for (Object[] pair : this.fieldToTransformers) { |
| DatabaseField field = (DatabaseField)pair[0]; |
| FieldTransformer transformer = (FieldTransformer)pair[1]; |
| Object fieldValue = invokeFieldTransformer(field, transformer, object, session); |
| row.put(field, fieldValue); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| */ |
| @Override |
| public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| if (isReadOnly()) { |
| return; |
| } |
| |
| for (Object[] pair : this.fieldToTransformers) { |
| DatabaseField field = (DatabaseField)pair[0]; |
| Object fieldValue = ((TransformationMappingChangeRecord)changeRecord).getRecord().get(field); |
| row.put(field, fieldValue); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get a value from the object and set that in the respective field of the row. |
| * But before that check if the reference object is instantiated or not. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForUpdate(WriteObjectQuery query, AbstractRecord record) { |
| if (!areObjectsToBeProcessedInstantiated(query.getObject())) { |
| return; |
| } |
| |
| if (query.getSession().isUnitOfWork()) { |
| if (compareObjects(query.getBackupClone(), query.getObject(), query.getSession())) { |
| return; |
| } |
| } |
| |
| writeFromObjectIntoRow(query.getObject(), record, query.getSession(), WriteType.UPDATE); |
| } |
| |
| /** |
| * INTERNAL: |
| * Write fields needed for insert into the template for with null values. |
| */ |
| @Override |
| public void writeInsertFieldsIntoRow(AbstractRecord record, AbstractSession session) { |
| if (isReadOnly()) { |
| return; |
| } |
| |
| for (Object[] pair : this.fieldToTransformers) { |
| DatabaseField field = (DatabaseField)pair[0]; |
| record.put(field, null); |
| } |
| } |
| } |