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

org.eclipse.persistence.internal.oxm.ReferenceResolver 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
//     Marcel Valovy - major speed up, major refurbishing.
package org.eclipse.persistence.internal.oxm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.Callable;

import org.eclipse.persistence.core.descriptors.CoreDescriptor;
import org.eclipse.persistence.core.descriptors.CoreInheritancePolicy;
import org.eclipse.persistence.core.mappings.CoreAttributeAccessor;
import org.eclipse.persistence.core.mappings.CoreMapping;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.core.queries.CoreContainerPolicy;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.oxm.mappings.CollectionReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.InverseReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
import org.eclipse.persistence.internal.oxm.mappings.ObjectReferenceMapping;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * This class is leveraged by reference mappings. It plays 3 roles:
 * 
    *
  • Stores objects with an ID
  • *
  • Stores key based relationships
  • *
  • Resolves key based relationships based on the objects stored by ID
  • *
*/ public final class ReferenceResolver { /** * The default initial capacity of map - must be a power of two. */ private static final int MAP_INITIAL_CAPACITY = 1 << 6; // aka 64 /** * The default initial capacity of list - must be a power of two. */ private static final int LIST_INITIAL_CAPACITY = 1 << 3; // aka 8 /** * This field is in fact used locally only. We're just saving resources by reusing one instance of the object. */ private final ReferenceKey refKey; /** * Stores References. */ private LinkedHashMap referencesMap; /** * Stores References that have been kicked out of the {@link #referencesMap} because a Reference with same key * was added to it. */ private List unluckyReferences; /** * Stores positions of References that have been kicked out of the {@link #referencesMap}. The position is * the same this Reference would have if this code would run on a list instead of map. */ private List unluckyRefPositions; /** * Speed-up cache that was introduced in 2.5 instead of the previous speed-up mechanisms using session cache. */ private Map, Map> cache; /** * The default constructor initializes the list of References. */ public ReferenceResolver() { referencesMap = new LinkedHashMap<>(MAP_INITIAL_CAPACITY); unluckyReferences = new ArrayList<>(MAP_INITIAL_CAPACITY); unluckyRefPositions = new ArrayList<>(MAP_INITIAL_CAPACITY); cache = new HashMap<>(MAP_INITIAL_CAPACITY); refKey = new ReferenceKey(); } /* Shows why the containers are not final. Keep this private method up here. */ /** * Resets the references containers. *
* PERF: * Allocating a new object may be faster than clearing old objects, especially in this case. *
* As 'Stephen C' points out: ,,There are locality and cross-generational issues that could affect performance. When * you repeatedly recycle an ArrayList, the object and its backing array are likely to be tenured. That means that: *
     *   -  The list objects and the objects representing list elements are likely to be in different areas of the
     *      heap, potentially increasing TLB misses and page traffic, especially at GC time.
     *   -  Assignment of (young generation) references into the (tenured) list's backing array are likely to incur
     *     write barrier overheads ... depending on the GC implementation."
     * 
* from Stack Overflow. *
* Taking last size can give approximate prediction of the next size. * Halving provides convergence and increases efficiency. Since the list will be empty after, it's efficient. */ private void resetContainers() { referencesMap = new LinkedHashMap<>(Math.max(referencesMap.size() / 2, MAP_INITIAL_CAPACITY)); unluckyReferences = new ArrayList<>(Math.max(unluckyReferences.size() / 2, LIST_INITIAL_CAPACITY)); unluckyRefPositions = new ArrayList<>(unluckyReferences.size()); cache = new HashMap<>(Math.max(cache.size() / 2, MAP_INITIAL_CAPACITY)); } /** * Add a Reference object to the list - these References will * be resolved after unmarshalling is complete. *

* ############################# * # Strategy - Hash Collision # * ############################# * Suppose that hashing function is h(k) = k % 9; * __________Input 9 entries_________________________ * Key k | 0, 8, 7, 5, 5, 5, 1, 14, 3 | * Position# p | 0, 1, 2, 3, 4, 5, 6, 7, 8 | * Value = p * 3 | 0, 3, 6, 9, 12, 15, 18, 21, 24 | * -------------------------------------------------- * e.g. eighth entry is Entry#7{ key = 14, value = 21 } * * #################################### * # Insert element - O(1) guaranteed # * #################################### * Processing the 9th key: * * 1. Attempt to insert Entry#7 with key '14' into map of references. * > h(14) = 5; * HashMap buckets: * position | 0 1 2 3 4 5 6 7 8 * entry(key) | 0 1 3 5 7 8 * ^ * > Bucket 5 is taken. * * 2. Store the entry in a separate list. * List for unlucky references: * position | 0 1 2 * entry(key) | 5 5 * ^ * * position | 0 1 2 * entry(key) | 5 5 14 * ^ * 3. Store the position # p of this element, i.e. what spot it would have * taken if all entries were stored in a position list, counting from zero. * * List storing position # of unlucky references: * position | 0 1 2 * entry # (p) | 4 5 * ^ * * position | 0 1 2 * entry # (p) | 4 5 7 * ^ * * ##################################################### * # Retrieve element - O(1) expected, O(n) worst case # * ##################################################### * Retrieve entry with key '14' * 1. Attempt to retrieve it from map * > h(14) = 5; * HashMap buckets: * position | 0 1 2 3 4 5 6 7 8 * entry(key) | 0 1 3 5 7 8 * ^ * Hash function points to bucket # 5. Stored key is 5. * > key 5 != 14. * * 2. Iterate through list of unluckyReferences, comparing * key to all keys in the list. * * position | 0 1 2 * entry(key) | 5 5 14 * ^ * > key 5 != 14 * * position | 0 1 2 * entry(key) | 5 5 14 * ^ * * > key 5 != 14 * * position | 0 1 2 * entry(key) | 5 5 14 * ^ * * > key 14 = 14, retrieve entry. * * ################################################## * # Iterate through all elements - O(n) guaranteed # * ################################################## * * 1. Create boolean array of size n that keeps track * of unlucky positions: * > boolean[] a = new boolean[lastPosition + 1]; * * 2. Set a[p] = true for elements that did not fit into * hash map, p = position # of element. * * > for (Integer p : unluckyRefPositions) { * > a[p] = true; * > } * * 3. Iterate through LinkedMap and List as if they were one joined collection * of size s = map.size() + list.size(), ordered by p = position # of element: * > for (p = 0; p < s; p ++) { * > if a[p] = false, take next element from linked map iterator, * > if a[p] = true, take next element from list iterator, * > } * */ public void addReference(final Reference ref) { final ReferenceKey key = new ReferenceKey(ref); /* If an entry with equal key is already present in map, we preserve the original entry and * note the position of the new entry into the list of positions and put the new misfit value * into the list of unluckyReferences. */ final Reference previous = referencesMap.get(key); if (previous != null || ref.getSourceObject() instanceof Collection) { unluckyReferences.add(ref); // The input integer represents the position (starting from 0) of the new element that didn't fit into the map. unluckyRefPositions.add(referencesMap.size() + unluckyReferences.size() - 1); } else { referencesMap.put(key, ref); } } /** * Retrieve the reference for a given mapping instance. If more keys match, returns the first occurrence. */ public Reference getReference(final ObjectReferenceMapping mapping, final Object sourceObject) { refKey.setMapping(mapping); refKey.setSourceObject(sourceObject); final Reference reference = referencesMap.get(refKey); if (reference != null) { return reference; } // Search for unlucky references that were kicked out of hashMap by entries with equal key. for (Reference reference1 : unluckyReferences) { if (reference1.getMapping() == mapping && reference1.getSourceObject() == sourceObject) { return reference1; } } return null; } /** * Return a reference for the given mapping and source object, that doesn't already * contain an entry for the provided field. */ public Reference getReference(final ObjectReferenceMapping mapping, final Object sourceObject, final Field xmlField) { final Field targetField = (Field) mapping.getSourceToTargetKeyFieldAssociations().get(xmlField); String tgtXpath = null; if (!(mapping.getReferenceClass() == null || mapping.getReferenceClass() == Object.class)) { if (targetField != null) { tgtXpath = targetField.getXPath(); } } final ReferenceKey key = new ReferenceKey(sourceObject, mapping); Reference reference = referencesMap.get(key); if (reference != null && reference.getPrimaryKeyMap().get(tgtXpath) == null) { return reference; } // Search for unlucky references that were kicked out of hashMap by entries with equal key. for (Reference reference1 : unluckyReferences) { if (reference1.getMapping() == mapping && reference1.getSourceObject() == sourceObject) { if (reference1.getPrimaryKeyMap().get(tgtXpath) == null) { return reference1; } } } return null; } /** * Store an instance by key based on a mapped class. These values will be * used when it comes time to resolve the references. * * @since EclipseLink 2.5.0 */ public void putValue(final Class clazz, final Object key, final Object object) { Map keyToObject = cache.get(clazz); if (null == keyToObject) { keyToObject = new HashMap<>(); cache.put(clazz, keyToObject); } keyToObject.put(key, object); } /** * INTERNAL: * Iterates through all references. Resolves them. Resets containers. * * @param session typically will be a unit of work * @param userSpecifiedResolver a user-provided subclass of IDResolver, may be null */ public void resolveReferences(final CoreAbstractSession session, final IDResolver userSpecifiedResolver, final ErrorHandler handler) { final Collection luckyReferences = referencesMap.values(); final Iterator mapIterator = luckyReferences.iterator(); final Iterator listIterator = unluckyReferences.iterator(); /** * Speed up array which lowers time complexity by a factor of n. */ boolean[] a = null; /** * Represents position of last Reference that did not fit into hash map. */ Integer lastPosition; if (!unluckyReferences.isEmpty()) { lastPosition = unluckyRefPositions.get(unluckyRefPositions.size() - 1); a = new boolean[lastPosition + 1]; for (Integer position : unluckyRefPositions) { a[position] = true; } } else { lastPosition = -1; // for the condition "i <= lastPosition" } for (int i = 0, totalSize = luckyReferences.size() + unluckyReferences.size(); i < totalSize; i++) { final Reference reference; // Fast check to see if position [i] should have // contained Reference that did not fit in into map. //noinspection ConstantConditions if (i <= lastPosition && a[i]) { reference = listIterator.next(); } else { reference = mapIterator.next(); } perform(session, userSpecifiedResolver, handler, reference); } resetContainers(); } /** * Add java doc if you understand this code. */ private void perform(final CoreAbstractSession session, final IDResolver userSpecifiedResolver, final ErrorHandler handler, final Reference reference) { final Object referenceSourceObject = reference.getSourceObject(); if (reference.getMapping() instanceof CollectionReferenceMapping) { final CollectionReferenceMapping mapping = (CollectionReferenceMapping) reference.getMapping(); final CoreContainerPolicy cPolicy = mapping.getContainerPolicy(); //container should never be null final Object container = reference.getContainer(); // create vectors of primary key values - one vector per reference instance createPKVectorsFromMap(reference, mapping); // if the we could not generate the primary key for the reference, it will not resolve - skip it if (reference.getPrimaryKey() == null) { return; } // loop over each pk vector and get object from cache - then add to collection and set on object Object value = null; if (!mapping.isWriteOnly()) { for (Object o : ((Vector) reference.getPrimaryKey())) { final CacheId primaryKey = (CacheId) o; if (userSpecifiedResolver != null) { final Callable c; try { if (primaryKey.getPrimaryKey().length > 1) { final Map idWrapper = new HashMap<>(); for (int y = 0; y < primaryKey.getPrimaryKey().length; y++) { final ObjectReferenceMapping refMapping = (ObjectReferenceMapping) reference.getMapping(); final String idName = (String) refMapping.getReferenceDescriptor(). getPrimaryKeyFieldNames().get(y); final Object idValue = primaryKey.getPrimaryKey()[y]; idWrapper.put(idName, idValue); } c = userSpecifiedResolver.resolve(idWrapper, reference.getTargetClass()); } else { c = userSpecifiedResolver.resolve(primaryKey.getPrimaryKey()[0], reference.getTargetClass()); } if (c != null) { value = c.call(); } } catch (Exception e) { throw XMLMarshalException.unmarshalException(e); } } else { value = getValue(session, reference, primaryKey, handler); } if (value != null) { cPolicy.addInto(value, container, session); } } } // for each reference, get the source object and add it to the container policy // when finished, set the policy on the mapping mapping.setAttributeValueInObject(referenceSourceObject, container); final InverseReferenceMapping inverseReferenceMapping = mapping.getInverseReferenceMapping(); if (inverseReferenceMapping != null && value != null) { final CoreAttributeAccessor backpointerAccessor = inverseReferenceMapping.getAttributeAccessor(); final CoreContainerPolicy backpointerContainerPolicy = inverseReferenceMapping.getContainerPolicy(); if (backpointerContainerPolicy == null) { backpointerAccessor.setAttributeValueInObject(value, referenceSourceObject); } else { Object backpointerContainer = backpointerAccessor.getAttributeValueFromObject(value); if (backpointerContainer == null) { backpointerContainer = backpointerContainerPolicy.containerInstance(); backpointerAccessor.setAttributeValueInObject(value, backpointerContainer); } backpointerContainerPolicy.addInto(referenceSourceObject, backpointerContainer, session); } } } else if (reference.getMapping() instanceof ObjectReferenceMapping) { final CacheId primaryKey = (CacheId) reference.getPrimaryKey(); Object value = null; if (userSpecifiedResolver != null) { final Callable c; try { if (primaryKey.getPrimaryKey().length > 1) { final Map idWrapper = new HashMap<>(); for (int y = 0; y < primaryKey.getPrimaryKey().length; y++) { final ObjectReferenceMapping refMapping = (ObjectReferenceMapping) reference.getMapping(); final String idName = (String) refMapping.getReferenceDescriptor() .getPrimaryKeyFieldNames().get(y); final Object idValue = primaryKey.getPrimaryKey()[y]; idWrapper.put(idName, idValue); } c = userSpecifiedResolver.resolve(idWrapper, reference.getTargetClass()); } else { c = userSpecifiedResolver.resolve(primaryKey.getPrimaryKey()[0], reference.getTargetClass()); } if (c != null) { value = c.call(); } } catch (Exception e) { throw XMLMarshalException.unmarshalException(e); } } else { value = getValue(session, reference, primaryKey, handler); } ObjectReferenceMapping mapping = (ObjectReferenceMapping) reference.getMapping(); if (value != null) { mapping.setAttributeValueInObject(reference.getSourceObject(), value); } if (null != reference.getSetting()) { reference.getSetting().setValue(value); } InverseReferenceMapping inverseReferenceMapping = mapping.getInverseReferenceMapping(); if (inverseReferenceMapping != null) { CoreAttributeAccessor backpointerAccessor = inverseReferenceMapping.getAttributeAccessor(); CoreContainerPolicy backpointerContainerPolicy = inverseReferenceMapping.getContainerPolicy(); if (backpointerContainerPolicy == null) { backpointerAccessor.setAttributeValueInObject(value, referenceSourceObject); } else { Object backpointerContainer = backpointerAccessor.getAttributeValueFromObject(value); if (backpointerContainer == null) { backpointerContainer = backpointerContainerPolicy.containerInstance(); backpointerAccessor.setAttributeValueInObject(value, backpointerContainer); } backpointerContainerPolicy.addInto(reference.getSourceObject(), backpointerContainer, session); } } } } /** * INTERNAL: * Create primary key values to be used for cache lookup. The map * of primary keys on the reference is keyed on the reference descriptors primary * key field names. Each of these primary keys contains all of the values for a * particular key - in the order that they we read in from the document. For * example, if the key field names are A, B, and C, and there are three reference * object instances, then the hashmap would have the following: * (A=[1,2,3], B=[X,Y,Z], C=[Jim, Joe, Jane]). If the primary key field names on * the reference descriptor contained [B, C, A], then the result of this method call * would be reference.primaryKeys=([X, Jim, 1], [Y, Joe, 2], [Z, Jane, 3]). */ private void createPKVectorsFromMap(final Reference reference, final CollectionReferenceMapping mapping) { final CoreDescriptor referenceDescriptor = mapping.getReferenceDescriptor(); final Vector pks = new Vector<>(); if (null == referenceDescriptor) { final CacheId pkVals = (CacheId) reference.getPrimaryKeyMap().get(null); if (null == pkVals) { return; } for (int x = 0; x < pkVals.getPrimaryKey().length; x++) { final Object[] values = new Object[1]; values[0] = pkVals.getPrimaryKey()[x]; pks.add(new CacheId(values)); } } else { final List pkFields = referenceDescriptor.getPrimaryKeyFieldNames(); if (pkFields.isEmpty()) { return; } boolean init = true; // for each primary key field name for (Object pkField : pkFields) { final CacheId pkVals = (CacheId) reference.getPrimaryKeyMap().get(pkField); if (pkVals == null) { return; } // initialize the list of pk vectors once and only once if (init) { for (int i = 0; i < pkVals.getPrimaryKey().length; i++) { pks.add(new CacheId(new Object[0])); } init = false; } // now add each value for the current target key to it's own vector for (int i = 0; i < pkVals.getPrimaryKey().length; i++) { final Object val = pkVals.getPrimaryKey()[i]; (pks.get(i)).add(val); } } } reference.setPrimaryKey(pks); } /** * Add java doc if you understand this code. */ private Object getValue(final CoreAbstractSession session, final Reference reference, final CacheId primaryKey, final ErrorHandler handler) { final Class referenceTargetClass = reference.getTargetClass(); if (null == referenceTargetClass || referenceTargetClass == CoreClassConstants.OBJECT) { for (Object entry : session.getDescriptors().values()) { Object value = null; final Descriptor targetDescriptor = (Descriptor) entry; final List pkFields = targetDescriptor.getPrimaryKeyFields(); if (null != pkFields && 1 == pkFields.size()) { Field pkField = (Field) pkFields.get(0); pkField = (Field) targetDescriptor.getTypedField(pkField); final Class targetType = pkField.getType(); if (targetType == CoreClassConstants.STRING || targetType == CoreClassConstants.OBJECT) { value = getValue(targetDescriptor.getJavaClass(), primaryKey); } else { try { final Object[] pkValues = primaryKey.getPrimaryKey(); final Object[] convertedPkValues = new Object[pkValues.length]; for (int x = 0; x < pkValues.length; x++) { convertedPkValues[x] = session.getDatasourcePlatform().getConversionManager() .convertObject(pkValues[x], targetType); } value = getValue(targetDescriptor.getJavaClass(), new CacheId(convertedPkValues)); } catch (ConversionException ignored) { } } if (null != value) { return value; } } } if (primaryKey.getPrimaryKey()[0] != null) { final XMLMarshalException e = XMLMarshalException.missingIDForIDRef( Object.class.getName(), primaryKey.getPrimaryKey()); if (handler != null) { final SAXParseException saxParseException = new SAXParseException(e.getLocalizedMessage(), null, e); try { handler.warning(saxParseException); } catch (SAXException saxException) { throw e; } } } return null; } else { Object value = getValue(referenceTargetClass, primaryKey); if (null == value) { final CoreMapping mapping = (CoreMapping) reference.getMapping(); final CoreDescriptor targetDescriptor = mapping.getReferenceDescriptor(); if (targetDescriptor.hasInheritance()) { final CoreInheritancePolicy inheritancePolicy = targetDescriptor.getInheritancePolicy(); final List childDescriptors = inheritancePolicy.getAllChildDescriptors(); for (CoreDescriptor childDescriptor : childDescriptors) { value = getValue(childDescriptor.getJavaClass(), primaryKey); if (null != value) { return value; } } } } if (value == null && (primaryKey.getPrimaryKey()[0] != null)) { final XMLMarshalException e = XMLMarshalException.missingIDForIDRef( referenceTargetClass.getName(), primaryKey.getPrimaryKey()); if (handler != null) { SAXParseException saxParseException = new SAXParseException(e.getLocalizedMessage(), null, e); try { handler.warning(saxParseException); } catch (SAXException saxException) { throw e; } } } return value; } } /** * Retrieves value from {@link #cache}. */ private Object getValue(Class clazz, CacheId primaryKey) { Map keyToObject = cache.get(clazz); if (null != keyToObject) { return keyToObject.get(primaryKey); } return null; } /** * This class serves to represent a key for {@link org.eclipse.persistence.internal.oxm.Reference} when used * in maps. */ private static final class ReferenceKey { private Object sourceObject; private Mapping mapping; public ReferenceKey() { } public ReferenceKey(final Object sourceObject, final Mapping mapping) { this.sourceObject = sourceObject; this.mapping = mapping; } public ReferenceKey(final Reference ref) { this.sourceObject = ref.getSourceObject(); this.mapping = ref.getMapping(); } public void setMapping(final ObjectReferenceMapping mapping) { this.mapping = mapping; } public void setSourceObject(final Object sourceObject) { this.sourceObject = sourceObject; } @Override public int hashCode() { int result = System.identityHashCode(sourceObject); result = 31 * result + System.identityHashCode(mapping); return result; } @Override public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof ReferenceKey)) return false; ReferenceKey that = (ReferenceKey) o; return sourceObject == that.sourceObject && mapping == that.mapping; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy