| /* |
| * 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.internal.oxm; |
| |
| |
| import javax.xml.namespace.QName; |
| |
| import org.eclipse.persistence.internal.core.queries.CoreContainerPolicy; |
| import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession; |
| import org.eclipse.persistence.internal.oxm.mappings.CollectionReferenceMapping; |
| import org.eclipse.persistence.internal.oxm.mappings.Field; |
| import org.eclipse.persistence.internal.oxm.record.MarshalContext; |
| import org.eclipse.persistence.internal.oxm.record.MarshalRecord; |
| import org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext; |
| import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord; |
| import org.xml.sax.Attributes; |
| |
| /** |
| * INTERNAL: |
| * <p><b>Purpose</b>: Class to handle (un)marshal operations for |
| * XMLCollectionReferenceMappings.</p> |
| * |
| * <p>An instance of this class is required for each XMLField set on the |
| * mapping, that is, for each source field in the source-target key field |
| * association list.</p> |
| * |
| * <p>When unmarshalling, an instance of org.eclipse.persistence.internal.oxm.Reference is |
| * created on a per mapping basis (keyed on source object instance) and sorted |
| * on the associated session's org.eclipse.persistence.internal.oxm.ReferenceResolver |
| * instance. Each target primary key value is stored in the Reference instance |
| * for use during mapping resolution phase after unmarshalling completes.</p> |
| * |
| * <p>When marshalling, the target object's primary key value that is mapped to |
| * this NodeValue's XMLField (in the XMLObjectReferenceMapping's source-target |
| * key field association list) is retrieved and written out.</p> |
| * |
| * @see org.eclipse.persistence.internal.oxm.Reference |
| * @see org.eclipse.persistence.internal.oxm.ReferenceResolver |
| * @see org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping |
| */ |
| |
| public class XMLCollectionReferenceMappingNodeValue extends MappingNodeValue implements ContainerValue { |
| private CollectionReferenceMapping xmlCollectionReferenceMapping; |
| private Field xmlField; |
| private static final String SPACE = " "; |
| private int index = -1; |
| |
| /** |
| * This constructor sets the XMLCollectionReferenceMapping and XMLField members to |
| * the provided values. |
| * |
| */ |
| public XMLCollectionReferenceMappingNodeValue(CollectionReferenceMapping xmlCollectionReferenceMapping, Field xmlField) { |
| super(); |
| this.xmlCollectionReferenceMapping = xmlCollectionReferenceMapping; |
| this.xmlField = xmlField; |
| } |
| |
| /** |
| * Handle attribute operation. Here we will create and populate an |
| * org.eclipse.persistence.internal.oxm.Reference instance to be used during |
| * the mapping resolution stage. In particular, the primary key value |
| * for this element will be added to the Reference object's map of |
| * target primary key values - based on the target key field name. Note |
| * that if a reference already exists for the xmlCollectionReferenceMapping's |
| * source object instance, we will simply add to the target pk value list. |
| * The Reference object is stored on the ReferenceResolver associated with |
| * the UnmarshalRecord's session. |
| */ |
| @Override |
| public void attribute(UnmarshalRecord unmarshalRecord, String namespaceURI, String localName, String value) { |
| if (value != null) { |
| Object realValue = unmarshalRecord.getXMLReader().convertValueBasedOnSchemaType(xmlField, value, (ConversionManager) unmarshalRecord.getSession().getDatasourcePlatform().getConversionManager(), unmarshalRecord); |
| Object container = unmarshalRecord.getContainerInstance(this); |
| // build a reference which will be resolved after unmarshalling is complete |
| xmlCollectionReferenceMapping.buildReference(unmarshalRecord, xmlField, realValue, unmarshalRecord.getSession(), container); |
| } |
| } |
| |
| /** |
| * Handle endElement operation. Here we will create and populate an |
| * org.eclipse.persistence.internal.oxm.Reference instance to be used during |
| * the mapping resolution stage. In particular, the primary key value |
| * for this element will be added to the Reference object's map of |
| * target primary key values - based on the target key field name. Note |
| * that if a reference already exists for the xmlCollectionReferenceMapping's |
| * source object instance, we will simply add to the target pk value list. |
| * The Reference object is stored on the ReferenceResolver associated with |
| * the UnmarshalRecord's session. |
| */ |
| @Override |
| public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord) { |
| if (!xmlField.getLastXPathFragment().nameIsText()) { |
| return; |
| } |
| |
| Object value = unmarshalRecord.getCharacters().toString(); |
| unmarshalRecord.resetStringBuffer(); |
| |
| ConversionManager conversionManager = unmarshalRecord.getConversionManager(); |
| if (unmarshalRecord.getTypeQName() != null) { |
| Class<?> typeClass = xmlField.getJavaClass(unmarshalRecord.getTypeQName(), conversionManager); |
| value = conversionManager.convertObject(value, typeClass, unmarshalRecord.getTypeQName()); |
| } else { |
| value = unmarshalRecord.getXMLReader().convertValueBasedOnSchemaType(xmlField, value, conversionManager, unmarshalRecord); |
| } |
| |
| Object container = unmarshalRecord.getContainerInstance(this); |
| |
| // build a reference which will be resolved after unmarshalling is complete |
| xmlCollectionReferenceMapping.buildReference(unmarshalRecord, xmlField, value, unmarshalRecord.getSession(), container); |
| } |
| |
| @Override |
| public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Object container) { |
| this.endElement(xPathFragment, unmarshalRecord); |
| } |
| |
| /** |
| * Indicate if the next XPathFragment is an attribute or text() node. |
| */ |
| @Override |
| public boolean isOwningNode(XPathFragment xPathFragment) { |
| if(isMarshalNodeValue()) { |
| if (xmlCollectionReferenceMapping.usesSingleNode()) { |
| return xPathFragment.nameIsText() || xPathFragment.isAttribute(); |
| } |
| XPathFragment nextFragment = xPathFragment.getNextFragment(); |
| return (nextFragment != null) && (nextFragment.nameIsText() || nextFragment.isAttribute()); |
| } |
| return super.isOwningNode(xPathFragment); |
| } |
| |
| @Override |
| public boolean isWrapperAllowedAsCollectionName() { |
| return true; |
| } |
| |
| @Override |
| public boolean isContainerValue() { |
| return true; |
| } |
| |
| @Override |
| public Object getContainerInstance() { |
| return getContainerPolicy().containerInstance(); |
| } |
| |
| @Override |
| public void setContainerInstance(Object object, Object containerInstance) { |
| xmlCollectionReferenceMapping.setAttributeValueInObject(object, containerInstance); |
| } |
| |
| @Override |
| public CoreContainerPolicy getContainerPolicy() { |
| return xmlCollectionReferenceMapping.getContainerPolicy(); |
| } |
| |
| /** |
| * Handle the marshal operation for this NodeValue. Each of the target |
| * object's primary key values that are mapped to the collection mapping's fields |
| * (in the XMLCollectionReferenceMapping's source-target key field association list) |
| * are retrieved and written out. |
| */ |
| @Override |
| public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver) { |
| if(this.xmlCollectionReferenceMapping.isReadOnly()) { |
| return false; |
| } |
| CoreContainerPolicy cp = xmlCollectionReferenceMapping.getContainerPolicy(); |
| Object collection = xmlCollectionReferenceMapping.getAttributeAccessor().getAttributeValueFromObject(object); |
| if (collection == null) { |
| return false; |
| } |
| |
| Object iterator = cp.iteratorFor(collection); |
| if (cp.hasNext(iterator)) { |
| XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver); |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| } else { |
| return marshalRecord.emptyCollection(xPathFragment, namespaceResolver, false); |
| } |
| Object objectValue; |
| |
| if (xmlCollectionReferenceMapping.usesSingleNode()) { |
| StringBuilder stringValueStringBuilder = new StringBuilder(); |
| String newValue; |
| QName schemaType; |
| while (cp.hasNext(iterator)) { |
| objectValue = cp.next(iterator, session); |
| Object fieldValue = xmlCollectionReferenceMapping.buildFieldValue(objectValue, xmlField, session); |
| if (fieldValue == null) { |
| if(null != objectValue) { |
| Field fkField = (Field) xmlCollectionReferenceMapping.getSourceToTargetKeyFieldAssociations().get(xmlField); |
| fieldValue = marshalRecord.getMarshaller().getContext().getValueByXPath(objectValue, fkField.getXPath(), fkField.getNamespaceResolver(), Object.class); |
| } |
| if(null == fieldValue) { |
| break; |
| } |
| } |
| schemaType = xmlField.getSchemaTypeForValue(fieldValue, session); |
| newValue = marshalRecord.getValueToWrite(schemaType, fieldValue, (ConversionManager) session.getDatasourcePlatform().getConversionManager()); |
| if (newValue != null) { |
| stringValueStringBuilder.append(newValue); |
| if (cp.hasNext(iterator)) { |
| stringValueStringBuilder.append(SPACE); |
| } |
| } |
| } |
| marshalSingleValue(xPathFragment, marshalRecord, object, stringValueStringBuilder.toString(), session, namespaceResolver, ObjectMarshalContext.getInstance()); |
| } else { |
| marshalRecord.startCollection(); |
| while (cp.hasNext(iterator)) { |
| objectValue = cp.next(iterator, session); |
| marshalSingleValue(xPathFragment, marshalRecord, object, objectValue, session, namespaceResolver, ObjectMarshalContext.getInstance()); |
| } |
| marshalRecord.endCollection(); |
| } |
| return true; |
| } |
| |
| /** |
| */ |
| @Override |
| public boolean startElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Attributes atts) { |
| if (xmlField.getLastXPathFragment().isAttribute()) { |
| if (!this.xmlCollectionReferenceMapping.usesSingleNode()) { |
| String namespaceURI = xmlField.getLastXPathFragment().getNamespaceURI(); |
| String value; |
| if (namespaceURI == null) { |
| value = atts.getValue(xmlField.getLastXPathFragment().getLocalName()); |
| } else { |
| value = atts.getValue(namespaceURI, xmlField.getLastXPathFragment().getLocalName()); |
| } |
| |
| xmlCollectionReferenceMapping.buildReference(unmarshalRecord, xmlField, value, unmarshalRecord.getSession(), unmarshalRecord.getContainerInstance(this)); |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean marshalSingleValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object value, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) { |
| if (xmlCollectionReferenceMapping.usesSingleNode()) { |
| XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver); |
| if (xPathFragment.isAttribute()) { |
| marshalRecord.attribute(xPathFragment, namespaceResolver, value, null); |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| } else { |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| marshalRecord.characters(null, value, null, false); |
| } |
| } else { |
| QName schemaType; |
| Object fieldValue = xmlCollectionReferenceMapping.buildFieldValue(value, xmlField, session); |
| if (fieldValue == null) { |
| return false; |
| } |
| schemaType = xmlField.getSchemaTypeForValue(fieldValue, session); |
| |
| marshalRecord.openStartElement(xPathFragment, namespaceResolver); |
| XPathFragment nextFragment = xPathFragment.getNextFragment(); |
| if (nextFragment.isAttribute()) { |
| marshalRecord.attribute(nextFragment, namespaceResolver, fieldValue, schemaType); |
| marshalRecord.closeStartElement(); |
| } else { |
| marshalRecord.predicateAttribute(xPathFragment, namespaceResolver); |
| marshalRecord.closeStartElement(); |
| marshalRecord.characters(schemaType, fieldValue, null, false); |
| } |
| marshalRecord.endElement(xPathFragment, namespaceResolver); |
| } |
| return true; |
| } |
| |
| @Override |
| public CollectionReferenceMapping getMapping() { |
| return xmlCollectionReferenceMapping; |
| } |
| |
| @Override |
| public boolean getReuseContainer() { |
| return getMapping().getReuseContainer(); |
| } |
| |
| @Override |
| public boolean isMarshalNodeValue() { |
| return xmlCollectionReferenceMapping.getFields().size() == 1 || xmlCollectionReferenceMapping.usesSingleNode(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used to track the index of the corresponding containerInstance in the containerInstances Object[] on UnmarshalRecord |
| */ |
| @Override |
| public void setIndex(int index){ |
| this.index = index; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set to track the index of the corresponding containerInstance in the containerInstances Object[] on UnmarshalRecord |
| * Set during TreeObjectBuilder initialization |
| */ |
| @Override |
| public int getIndex(){ |
| return index; |
| } |
| |
| /** |
| * INTERNAL |
| * Return true if an empty container should be set on the object if there |
| * is no presence of the collection in the XML document. |
| * @since EclipseLink 2.3.3 |
| */ |
| @Override |
| public boolean isDefaultEmptyContainer() { |
| return getMapping().isDefaultEmptyContainer(); |
| } |
| |
| } |