All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.mappings.foundation.AbstractCompositeDirectCollectionMapping Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * 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;

/**
 * AbstractCompositeDirectCollectionMapping consolidates the behavior of mappings that
 * map collections of "native" data objects (e.g. Strings).
 * 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 objectDescriptors, Map 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.
     * 

jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface. *

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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy