| /* |
| * 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 |
| // 06/03/2013-2.5.1 Guy Pelletier |
| // - 402380: 3 jpa21/advanced tests failed on server with |
| // "java.lang.NoClassDefFoundError: org/eclipse/persistence/testing/models/jpa21/advanced/enums/Gender" |
| package org.eclipse.persistence.mappings.foundation; |
| |
| import java.util.*; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.internal.descriptors.*; |
| import org.eclipse.persistence.internal.helper.*; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.oxm.mappings.Field; |
| import org.eclipse.persistence.internal.queries.*; |
| import org.eclipse.persistence.internal.sessions.*; |
| import org.eclipse.persistence.internal.sessions.remote.ObjectDescriptor; |
| import org.eclipse.persistence.mappings.*; |
| import org.eclipse.persistence.mappings.converters.*; |
| import org.eclipse.persistence.mappings.structures.ArrayCollectionMapping; |
| import org.eclipse.persistence.mappings.structures.ArrayCollectionMappingHelper; |
| import org.eclipse.persistence.queries.*; |
| import org.eclipse.persistence.sessions.remote.*; |
| import org.eclipse.persistence.sessions.CopyGroup; |
| |
| /** |
| * <code>AbstractCompositeDirectCollectionMapping</code> consolidates the behavior of mappings that |
| * map collections of "native" data objects (e.g. <code>String</code>s). |
| * These are objects that do not have their own descriptor and repeat within the XML record |
| * for the containing object. |
| * |
| * @author Big Country |
| * @since TOPLink/Java 3.0 |
| */ |
| public abstract class AbstractCompositeDirectCollectionMapping extends DatabaseMapping implements ContainerMapping, ArrayCollectionMapping { |
| |
| /** This is the field holding the nested collection. */ |
| protected DatabaseField field; |
| |
| /** This is the "data type" associated with each element in the nested collection. |
| Depending on the data store, this could be optional. */ |
| protected String elementDataTypeName; |
| |
| /** Allows user defined conversion between the object value and the database value. */ |
| protected Converter valueConverter; |
| |
| /** This determines the type of container used to hold the nested collection |
| in the object. */ |
| private ContainerPolicy containerPolicy; |
| |
| /** |
| * Default constructor. |
| */ |
| protected AbstractCompositeDirectCollectionMapping() { |
| super(); |
| this.containerPolicy = ContainerPolicy.buildDefaultPolicy(); |
| this.elementDataTypeName = ""; |
| this.setWeight(WEIGHT_AGGREGATE); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the converter on the mapping. |
| * A converter can be used to convert between the direct collection's object value and database value. |
| */ |
| public Converter getValueConverter() { |
| return valueConverter; |
| } |
| |
| /** |
| * PUBLIC: |
| * Indicates if there is a converter on the mapping. |
| */ |
| public boolean hasValueConverter() { |
| return getValueConverter() != null; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the converter on the mapping. |
| * A converter can be used to convert between the direct collection's object value and database value. |
| */ |
| public void setValueConverter(Converter valueConverter) { |
| this.valueConverter = valueConverter; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return a new element based on the change set. |
| */ |
| @Override |
| public Object buildAddedElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) { |
| return this.buildElementFromChangeSet(changeSet, mergeManager, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the attribute from the clone and assign it to the backup. |
| * For these mappings, this is the same as building the first clone. |
| */ |
| @Override |
| public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) { |
| this.buildClone(clone, null, backup, null, unitOfWork); |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return a change set for the specified element. |
| * Direct collections simply store the element itself, since it is immutable. |
| */ |
| @Override |
| public Object buildChangeSet(Object element, ObjectChangeSet owner, AbstractSession session) { |
| return element; |
| } |
| |
| /** |
| * 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) { |
| Object attributeValue = this.getAttributeValueFromObject(original); |
| this.setAttributeValueInObject(clone, this.buildClonePart(attributeValue, cacheKey, cloningSession)); |
| } |
| |
| /** |
| * 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 row, JoinedAttributeManager joinManager, Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) { |
| // for direct collection a cloned value is no different from an original value |
| Object cloneAttributeValue = valueFromRow(row, joinManager, sourceQuery, sharedCacheKey, executionSession, true, new Boolean[1]); |
| setAttributeValueInObject(clone, cloneAttributeValue); |
| } |
| |
| /** |
| * Build and return a clone of the specified attribute value. |
| */ |
| protected Object buildClonePart(Object attributeValue, CacheKey parentCacheKey, AbstractSession cloningSession) { |
| if (attributeValue == null) { |
| return this.getContainerPolicy().containerInstance(); |
| } else { |
| if ((getValueConverter() == null) || (!getValueConverter().isMutable())) { |
| return this.getContainerPolicy().cloneFor(attributeValue); |
| } |
| |
| // Clone the values of the collection as well. |
| Object cloneContainer = this.getContainerPolicy().containerInstance(); |
| Object iterator = this.getContainerPolicy().iteratorFor(attributeValue); |
| while (this.getContainerPolicy().hasNext(iterator)) { |
| Object originalValue = this.getContainerPolicy().next(iterator, cloningSession); |
| |
| // Bug 4182377 - there was a typo in the conversion logic |
| Object cloneValue = getValueConverter().convertDataValueToObjectValue(getValueConverter().convertObjectValueToDataValue(originalValue, cloningSession), cloningSession); |
| this.getContainerPolicy().addInto(cloneValue, cloneContainer, cloningSession); |
| } |
| return cloneContainer; |
| } |
| } |
| |
| /** |
| * 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) { |
| Object attributeValue = getAttributeValueFromObject(original); |
| if (attributeValue == null) { |
| attributeValue = getContainerPolicy().containerInstance(); |
| } else { |
| attributeValue = getContainerPolicy().cloneFor(attributeValue); |
| } |
| setAttributeValueInObject(copy, attributeValue); |
| } |
| |
| /** |
| * Build and return a new element based on the change set. |
| * Direct collections simply store the element itself, since it is immutable. |
| */ |
| protected Object buildElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) { |
| return changeSet; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return a new element based on the specified element. |
| * Direct collections simply return the element itself, since it is immutable. |
| */ |
| @Override |
| public Object buildElementFromElement(Object object, MergeManager mergeManager, AbstractSession targetSession) { |
| return object; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return a new element based on the change set. |
| */ |
| @Override |
| public Object buildRemovedElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) { |
| return this.buildElementFromChangeSet(changeSet, mergeManager, targetSession); |
| } |
| |
| /** |
| * 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 by this mapping are not registered as they have |
| // no identity, this is a no-op. |
| } |
| |
| /** |
| * Return the fields handled by the mapping. |
| */ |
| @Override |
| protected Vector collectFields() { |
| Vector fields = new Vector(1); |
| fields.addElement(this.getField()); |
| return fields; |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the non-null elements. Return true if they are alike. |
| * Use #equals() to determine if two elements are the same. |
| */ |
| @Override |
| public boolean compareElements(Object element1, Object element2, AbstractSession session) { |
| return element1.equals(element2); |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the non-null elements and return true if they are alike. |
| */ |
| @Override |
| public boolean compareElementsForChange(Object element1, Object element2, AbstractSession session) { |
| return this.compareElements(element1, element2, session); |
| } |
| |
| protected ChangeRecord convertToChangeRecord(Object cloneCollection, ObjectChangeSet owner, AbstractSession session) { |
| //since a minimal update for composites can't be done, we are only recording |
| //an all-or-none change. Therefore, this can be treated as a simple direct |
| //value. |
| ContainerPolicy cp = this.getContainerPolicy(); |
| Object container = cp.containerInstance(); |
| |
| Object iter = cp.iteratorFor(cloneCollection); |
| while (cp.hasNext(iter)) { |
| cp.addInto(cp.next(iter, session), container, session); |
| } |
| DirectToFieldChangeRecord changeRecord = new DirectToFieldChangeRecord(owner); |
| changeRecord.setAttribute(getAttributeName()); |
| changeRecord.setMapping(this); |
| changeRecord.setNewValue(container); |
| return changeRecord; |
| } |
| |
| /** |
| * 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<Object, ObjectDescriptor> objectDescriptors, Map<Object, Object> processedObjects, ObjectLevelReadQuery query, DistributedSession session) { |
| // Do nothing.... |
| // The nested collection should de-serialize without need for any further manipulation. |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the class each element in the object's |
| * collection should be converted to, before the collection |
| * is inserted into the object. |
| * This is optional - if left null, the elements will be added |
| * to the object's collection unconverted. |
| */ |
| public Class<?> getAttributeElementClass() { |
| if (!(getValueConverter() instanceof TypeConversionConverter)) { |
| return null; |
| } |
| return ((TypeConversionConverter)getValueConverter()).getObjectClass(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the mapping's containerPolicy. |
| */ |
| @Override |
| public ContainerPolicy getContainerPolicy() { |
| return containerPolicy; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the field that holds the nested collection. |
| */ |
| @Override |
| public DatabaseField getField() { |
| return field; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isAbstractCompositeDirectCollectionMapping() { |
| return true; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the class each element in the database row's |
| * collection should be converted to, before the collection |
| * is inserted into the database. |
| * This is optional - if left null, the elements will be added |
| * to the database row's collection unconverted. |
| */ |
| public Class<?> getFieldElementClass() { |
| if (!(getValueConverter() instanceof TypeConversionConverter)) { |
| return null; |
| } |
| return ((TypeConversionConverter)getValueConverter()).getDataClass(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the name of the field that holds the nested collection. |
| */ |
| public String getFieldName() { |
| return this.getField().getName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Convenience method. |
| * Return the value of an attribute, unwrapping value holders if necessary. |
| * If the value is null, build a new container. |
| */ |
| @Override |
| public Object getRealCollectionAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException { |
| Object value = this.getRealAttributeValueFromObject(object, session); |
| if (value == null) { |
| value = this.getContainerPolicy().containerInstance(1); |
| } |
| return value; |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize the mapping. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| super.initialize(session); |
| if (getField() == null) { |
| throw DescriptorException.fieldNameNotSetInMapping(this); |
| } |
| setField(getDescriptor().buildField(getField())); |
| setFields(collectFields()); |
| if (getValueConverter() != null) { |
| getValueConverter().initialize(this, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate on the appropriate attribute value. |
| */ |
| @Override |
| public void iterate(DescriptorIterator iterator) { |
| // PERF: Only iterate when required. |
| if (iterator.shouldIterateOnPrimitives()) { |
| Object attributeValue = this.getAttributeValueFromObject(iterator.getVisitedParent()); |
| if (attributeValue == null) { |
| return; |
| } |
| ContainerPolicy cp = this.getContainerPolicy(); |
| for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter);) { |
| iterator.iteratePrimitiveForMapping(cp.next(iter, iterator.getSession()), this); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the element's user-defined Map key has changed |
| * since it was cloned from the original version. |
| * Direct elements are not allowed to have keys. |
| */ |
| @Override |
| public boolean mapKeyHasChanged(Object element, AbstractSession session) { |
| return false; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class each element in the object's |
| * collection should be converted to, before the collection |
| * is inserted into the object. |
| * This is optional - if left null, the elements will be added |
| * to the object's collection unconverted. |
| */ |
| public void setAttributeElementClass(Class<?> attributeElementClass) { |
| TypeConversionConverter converter; |
| if (getValueConverter() instanceof TypeConversionConverter) { |
| converter = (TypeConversionConverter)getValueConverter(); |
| } else { |
| converter = new TypeConversionConverter(); |
| setValueConverter(converter); |
| } |
| converter.setObjectClass(attributeElementClass); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class each element in the object's |
| * collection should be converted to, before the collection |
| * is inserted into the object. |
| * This is optional - if left null, the elements will be added |
| * to the object's collection unconverted. |
| */ |
| public void setAttributeElementClassName(String attributeElementClass) { |
| TypeConversionConverter converter; |
| if (getValueConverter() instanceof TypeConversionConverter) { |
| converter = (TypeConversionConverter)getValueConverter(); |
| } else { |
| converter = new TypeConversionConverter(); |
| setValueConverter(converter); |
| } |
| converter.setObjectClassName(attributeElementClass); |
| } |
| |
| /** |
| * ADVANCED: |
| * Set the mapping's containerPolicy. |
| */ |
| @Override |
| public void setContainerPolicy(ContainerPolicy containerPolicy) { |
| this.containerPolicy = containerPolicy; |
| } |
| |
| /** |
| * Set the field that holds the nested collection. |
| */ |
| public void setField(DatabaseField field) { |
| this.field = field; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class each element in the database row's |
| * collection should be converted to, before the collection |
| * is inserted into the database. |
| * This is optional - if left null, the elements will be added |
| * to the database row's collection unconverted. |
| */ |
| public void setFieldElementClass(Class<?> fieldElementClass) { |
| TypeConversionConverter converter; |
| if (getValueConverter() instanceof TypeConversionConverter) { |
| converter = (TypeConversionConverter)getValueConverter(); |
| } else { |
| converter = new TypeConversionConverter(); |
| setValueConverter(converter); |
| } |
| converter.setDataClass(fieldElementClass); |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the mapping to use an instance of the specified container class |
| * to hold the nested objects. |
| * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface. |
| * <p>jdk1.1.x: The container class must be a subclass of Vector. |
| */ |
| @Override |
| public void useCollectionClass(Class<?> concreteClass) { |
| this.setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteClass)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to set the collection class by name. |
| * This is required when building from metadata to allow the correct class loader to be used. |
| */ |
| @Override |
| public void useCollectionClassName(String concreteClassName) { |
| setContainerPolicy(new CollectionContainerPolicy(concreteClassName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to set the collection class by name. |
| * This is required when building from metadata to allow the correct class loader to be used. |
| */ |
| @Override |
| public void useListClassName(String concreteClassName) { |
| setContainerPolicy(new ListContainerPolicy(concreteClassName)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Mapping does not support Map containers. |
| * It supports only Collection containers. |
| */ |
| @Override |
| public void useMapClass(Class<?> concreteClass, String methodName) { |
| throw new UnsupportedOperationException(this.getClass().getName() + ".useMapClass(Class, String)"); |
| } |
| |
| @Override |
| public void useMapClassName(String concreteContainerClassName, String methodName) { |
| throw new UnsupportedOperationException(this.getClass().getName() + ".useMapClass(String, String)"); |
| } |
| |
| /** |
| * PUBLIC: |
| * Sets whether the mapping uses a single node. |
| * @param usesSingleNode true if the items in the collection are in a single node or false if each of the items in the collection is in its own node |
| */ |
| public void setUsesSingleNode(boolean usesSingleNode) { |
| if (getField() instanceof Field) { |
| ((Field)getField()).setUsesSingleNode(usesSingleNode); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Checks whether the mapping uses a single node. |
| * |
| * @return True if the items in the collection are in a single node or false if each of the items in the collection is in its own node. |
| */ |
| public boolean usesSingleNode() { |
| if (getField() instanceof Field) { |
| return ((Field)getField()).usesSingleNode(); |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build the nested collection from the database row. |
| */ |
| @Override |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession executionSession, 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; |
| } |
| Object attributeValue = this.getAttributeValueFromObject(cached); |
| return buildClonePart(attributeValue, cacheKey, executionSession); |
| } |
| return result; |
| |
| }else if (!this.isCacheable && !isTargetProtected && cacheKey != null){ |
| return null; |
| } |
| } |
| if (row.hasSopObject()) { |
| return getAttributeValueFromObject(row.getSopObject()); |
| } |
| ContainerPolicy cp = this.getContainerPolicy(); |
| |
| Object fieldValue = row.getValues(this.getField()); |
| if (fieldValue == null) { |
| return cp.containerInstance(); |
| } |
| |
| Vector fieldValues = this.getDescriptor().buildDirectValuesFromFieldValue(fieldValue); |
| if (fieldValues == null) { |
| return cp.containerInstance(); |
| } |
| |
| Object result = cp.containerInstance(fieldValues.size()); |
| for (Enumeration stream = fieldValues.elements(); stream.hasMoreElements();) { |
| Object element = stream.nextElement(); |
| if (this.getValueConverter() != null) { |
| element = getValueConverter().convertDataValueToObjectValue(element, executionSession); |
| } |
| cp.addInto(element, result, sourceQuery.getSession()); |
| } |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the attribute value from the object and |
| * store it in the appropriate field of the row. |
| */ |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| if (this.isReadOnly()) { |
| return; |
| } |
| |
| Object attributeValue = this.getAttributeValueFromObject(object); |
| if (attributeValue == null) { |
| row.put(this.getField(), null); |
| return; |
| } |
| |
| ContainerPolicy cp = this.getContainerPolicy(); |
| |
| Vector elements = new Vector(cp.sizeFor(attributeValue)); |
| for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter);) { |
| Object element = cp.next(iter, session); |
| if (this.getValueConverter() != null) { |
| element = getValueConverter().convertObjectValueToDataValue(element, session); |
| } |
| if (element != null) { |
| elements.addElement(element); |
| } |
| } |
| |
| Object fieldValue = null; |
| if (!elements.isEmpty()) { |
| fieldValue = this.getDescriptor().buildFieldValueFromDirectValues(elements, elementDataTypeName, session); |
| } |
| row.put(this.getField(), fieldValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * If any part of the nested collection has changed, the whole thing is written. |
| */ |
| @Override |
| public void writeFromObjectIntoRowForUpdate(WriteObjectQuery writeQuery, AbstractRecord row) throws DescriptorException { |
| AbstractSession session = writeQuery.getSession(); |
| |
| if (session.isUnitOfWork()) { |
| if (this.compareObjects(writeQuery.getObject(), writeQuery.getBackupClone(), session)) { |
| return;// nothing is changed, no work required |
| } |
| } |
| this.writeFromObjectIntoRow(writeQuery.getObject(), row, session, WriteType.UPDATE); |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the appropriate attribute value from the object |
| * and put it in the appropriate field of the database row. |
| * Loop through the reference objects and extract the |
| * primary keys and put them in the vector of "nested" rows. |
| */ |
| @Override |
| public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| Object object = ((ObjectChangeSet)changeRecord.getOwner()).getUnitOfWorkClone(); |
| this.writeFromObjectIntoRow(object, row, session, writeType); |
| } |
| |
| /** |
| * INTERNAL: |
| * Write the fields needed for insert into the template with null values. |
| */ |
| @Override |
| public void writeInsertFieldsIntoRow(AbstractRecord row, AbstractSession session) { |
| if (this.isReadOnly()) { |
| return; |
| } |
| row.put(this.getField(), null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the classifiction for the field contained in the mapping. |
| * This is used to convert the row value to a consistent java value. |
| * By default this is unknown. |
| */ |
| @Override |
| public Class<?> getFieldClassification(DatabaseField fieldToClassify) { |
| return getAttributeElementClass(); |
| } |
| |
| @Override |
| public boolean isCollectionMapping() { |
| return true; |
| } |
| |
| @Override |
| public void convertClassNamesToClasses(ClassLoader classLoader){ |
| super.convertClassNamesToClasses(classLoader); |
| |
| this.containerPolicy.convertClassNamesToClasses(classLoader); |
| |
| // Convert and any Converter class names. |
| convertConverterClassNamesToClasses(valueConverter, classLoader); |
| } |
| |
| /** |
| * INTERNAL: |
| * Build and return the change record that results |
| * from comparing the two direct collection attributes. |
| */ |
| @Override |
| public ChangeRecord compareForChange(Object clone, Object backup, ObjectChangeSet owner, AbstractSession session) { |
| return (new ArrayCollectionMappingHelper(this)).compareForChange(clone, backup, owner, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| public boolean compareObjects(Object object1, Object object2, AbstractSession session) { |
| return (new ArrayCollectionMappingHelper(this)).compareObjects(object1, object2, session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| */ |
| @Override |
| public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| (new ArrayCollectionMappingHelper(this)).mergeChangesIntoObject(target, changeRecord, source, mergeManager, targetSession); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| * Simply replace the entire target collection. |
| */ |
| @Override |
| public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| (new ArrayCollectionMappingHelper(this)).mergeIntoObject(target, isTargetUnInitialized, source, mergeManager, targetSession); |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object add to a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleAddToCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) { |
| (new ArrayCollectionMappingHelper(this)).simpleAddToCollectionChangeRecord(referenceKey, changeSetToAdd, changeSet, session); |
| } |
| |
| /** |
| * ADVANCED: |
| * This method is used to have an object removed from a collection once the changeSet is applied |
| * The referenceKey parameter should only be used for direct Maps. |
| */ |
| @Override |
| public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) { |
| (new ArrayCollectionMappingHelper(this)).simpleRemoveFromCollectionChangeRecord(referenceKey, changeSetToRemove, changeSet, session); |
| } |
| |
| /** |
| * INTERNAL |
| * Called when a DatabaseMapping is used to map the key in a collection. Returns the key. |
| */ |
| public Object createMapComponentFromRow(AbstractRecord dbRow, ObjectBuildingQuery query, CacheKey parentCacheKey, AbstractSession session, boolean isTargetProtected){ |
| Object key = dbRow.get(getField()); |
| if (getValueConverter() != null){ |
| key = getValueConverter().convertDataValueToObjectValue(key, session); |
| } |
| return key; |
| } |
| |
| } |