org.eclipse.persistence.oxm.mappings.XMLCollectionReferenceMapping Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 1998, 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
package org.eclipse.persistence.oxm.mappings;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.xml.namespace.QName;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.NonSynchronizedVector;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.internal.oxm.Reference;
import org.eclipse.persistence.internal.oxm.ReferenceResolver;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.internal.oxm.mappings.CollectionReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.XMLContainerMapping;
import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord;
import org.eclipse.persistence.internal.queries.CollectionContainerPolicy;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.queries.JoinedAttributeManager;
import org.eclipse.persistence.internal.queries.ListContainerPolicy;
import org.eclipse.persistence.internal.queries.MapContainerPolicy;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.AttributeAccessor;
import org.eclipse.persistence.mappings.ContainerMapping;
import org.eclipse.persistence.oxm.XMLField;
import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy;
import org.eclipse.persistence.oxm.record.DOMRecord;
import org.eclipse.persistence.oxm.record.XMLRecord;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
/**
* TopLink OXM version of a 1-M mapping. A list of source-target key field
* associations is used to link the source xpaths to their related target
* xpaths, and hence their primary key (unique identifier) values used when
* (un)marshalling.
*
* It is important to note that each target xpath is assumed to be set as a primary
* key field on the target (reference) class descriptor - this is necessary in order
* to locate the correct target object instance in the session cache when resolving
* mapping references.
*
* The usesSingleNode flag should be set to true if the keys are to be written out in space-separated
* lists.
*
* @see XMLObjectReferenceMapping
* @see ContainerMapping
*/
public class XMLCollectionReferenceMapping extends XMLObjectReferenceMapping implements CollectionReferenceMapping, ContainerMapping {
protected ContainerPolicy containerPolicy; // type of container used to hold the aggregate objects
private static final String SPACE = " ";
private DatabaseField field;
private boolean defaultEmptyContainer = XMLContainerMapping.EMPTY_CONTAINER_DEFAULT;
private boolean usesSingleNode;
private boolean reuseContainer;
private AbstractNullPolicy wrapperNullPolicy;
/**
* PUBLIC:
* The default constructor initializes the sourceToTargetKeyFieldAssociations
* and sourceToTargetKeys data structures.
*/
public XMLCollectionReferenceMapping() {
sourceToTargetKeyFieldAssociations = new HashMap<>();
sourceToTargetKeys = new NonSynchronizedVector<>();
this.containerPolicy = ContainerPolicy.buildDefaultPolicy();
this.usesSingleNode = false;
}
@Override
public DatabaseField getField() {
return field;
}
@Override
public void setField(DatabaseField field) {
this.field = field;
}
/**
* Get the XPath String
* @return String the XPath String associated with this Mapping
*/
public String getXPath() {
return getField().getName();
}
/**
* Set the Mapping field name attribute to the given XPath String
* @param xpathString String
*/
public void setXPath(String xpathString) {
this.setField(new XMLField(xpathString));
}
/**
* INTERNAL:
* Retrieve the target object's primary key value that is mapped to a given
* source xpath (in the source-target key field association list).
*
* @param targetObject - the reference class instance that holds the required pk value
* @return null if the target object is null, the reference class is null, or
* a primary key field name does not exist on the reference descriptor that
* matches the target field name - otherwise, return the associated primary
* key value
*/
@Override
public Object buildFieldValue(Object targetObject, XMLField xmlFld, AbstractSession session) {
if (targetObject == null) {
return null;
}
ClassDescriptor descriptor = referenceDescriptor;
if(null == descriptor) {
descriptor = session.getClassDescriptor(targetObject);
}
ObjectBuilder objectBuilder = descriptor.getObjectBuilder();
Object primaryKey = objectBuilder.extractPrimaryKeyFromObject(targetObject, session);
XMLField tgtXMLField = (XMLField) getSourceToTargetKeyFieldAssociations().get(xmlFld);
int idx = 0;
if(!(null == referenceClass || ClassConstants.OBJECT == referenceClass)) {
idx = descriptor.getPrimaryKeyFields().indexOf(tgtXMLField);
if (idx == -1) {
return null;
}
}
if (primaryKey instanceof CacheId) {
return ((CacheId)primaryKey).getPrimaryKey()[idx];
} else {
return primaryKey;
}
}
/**
* INTERNAL:
* Create (if necessary) and populate a reference object that will be used
* during the mapping reference resolution phase after unmarshalling is
* complete.
*/
@Override
public void buildReference(UnmarshalRecord record, XMLField xmlField, Object object, AbstractSession session, Object container) {
buildReference(record.getCurrentObject(), xmlField, object, session, container, record.getReferenceResolver());
}
/**
* INTERNAL:
* Create (if necessary) and populate a reference object that will be used
* during the mapping reference resolution phase after unmarshalling is
* complete.
*/
public void buildReference(Object srcObject, XMLField xmlField, Object object, AbstractSession session, Object container, ReferenceResolver resolver) {
if (resolver == null) {
return;
}
Reference reference = resolver.getReference(this, srcObject, xmlField);
if (reference == null) {
// if reference is null, create a new instance and set it on the resolver
reference = new Reference(this, srcObject, referenceClass, new HashMap(), container);
resolver.addReference(reference);
}
CacheId primaryKeys;
if(null == referenceClass || ClassConstants.OBJECT == referenceClass) {
HashMap primaryKeyMap = reference.getPrimaryKeyMap();
CacheId pks = (CacheId) primaryKeyMap.get(null);
if (pks == null){
Object[] pkValues = new Object[1];
pks = new CacheId(pkValues);
primaryKeyMap.put(null, pks);
}
if(usesSingleNode) {
for (StringTokenizer stok = new StringTokenizer((String) object); stok.hasMoreTokens();) {
pks.add(stok.nextToken());
reference = resolver.getReference(this, srcObject, xmlField);
if (reference == null) {
// if reference is null, create a new instance and set it on the resolver
reference = new Reference(this, srcObject, referenceClass, new HashMap(), container);
resolver.addReference(reference);
}
primaryKeyMap = reference.getPrimaryKeyMap();
pks = (CacheId) primaryKeyMap.get(null);
if (pks == null){
Object[] pkValues = new Object[1];
pks = new CacheId(pkValues);
primaryKeyMap.put(null, pks);
}
}
} else {
pks.add(object);
}
} else {
XMLField tgtFld = (XMLField) getSourceToTargetKeyFieldAssociations().get(xmlField);
String tgtXPath = tgtFld.getQualifiedName();
HashMap primaryKeyMap = reference.getPrimaryKeyMap();
CacheId pks = (CacheId) primaryKeyMap.get(tgtXPath);
ClassDescriptor descriptor = session.getClassDescriptor(referenceClass);
if (pks == null){
pks = new CacheId(new Object[0]);
primaryKeyMap.put(tgtXPath, pks);
}
Class> type = descriptor.getTypedField(tgtFld).getType();
XMLConversionManager xmlConversionManager = (XMLConversionManager) session.getDatasourcePlatform().getConversionManager();
if(usesSingleNode) {
for (StringTokenizer stok = new StringTokenizer((String) object); stok.hasMoreTokens();) {
Object value = xmlConversionManager.convertObject(stok.nextToken(), type);
if (value != null) {
pks.add(value);
}
reference = resolver.getReference(this, srcObject, xmlField);
if (reference == null) {
// if reference is null, create a new instance and set it on the resolver
reference = new Reference(this, srcObject, referenceClass, new HashMap(), container);
resolver.addReference(reference);
}
primaryKeyMap = reference.getPrimaryKeyMap();
pks = (CacheId) primaryKeyMap.get(null);
if (pks == null){
pks = new CacheId(new Object[0]);
primaryKeyMap.put(tgtXPath, pks);
}
}
} else {
Object value = xmlConversionManager.convertObject(object, type);
if (value != null) {
pks.add(value);
}
}
}
}
/**
* INTERNAL:
* Return the mapping's containerPolicy.
*/
@Override
public ContainerPolicy getContainerPolicy() {
return containerPolicy;
}
/**
* INTERNAL:
* The mapping is initialized with the given session. This mapping is fully initialized
* after this.
*/
@Override
public void initialize(AbstractSession session) throws DescriptorException {
super.initialize(session);
if(null != getField()) {
setField(getDescriptor().buildField(getField()));
}
ContainerPolicy cp = getContainerPolicy();
if (cp != null) {
if (cp.getContainerClass() == null) {
Class