| /* |
| * 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 java.util.StringTokenizer; |
| import javax.xml.namespace.QName; |
| |
| 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.oxm.mappings.DirectCollectionMapping; |
| 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.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType; |
| import org.xml.sax.Attributes; |
| |
| /** |
| * INTERNAL: |
| * <p><b>Purpose</b>: This is how the XML Composite Direct Collection Mapping is |
| * handled when used with the TreeObjectBuilder.</p> |
| */ |
| |
| public class XMLCompositeDirectCollectionMappingNodeValue extends MappingNodeValue implements ContainerValue { |
| private static final String SPACE = " "; |
| private DirectCollectionMapping xmlCompositeDirectCollectionMapping; |
| private int index = -1; |
| |
| public XMLCompositeDirectCollectionMappingNodeValue(DirectCollectionMapping xmlCompositeDirectCollectionMapping) { |
| super(); |
| this.xmlCompositeDirectCollectionMapping = xmlCompositeDirectCollectionMapping; |
| } |
| |
| @Override |
| public boolean isOwningNode(XPathFragment xPathFragment) { |
| XPathFragment nextFragment = xPathFragment.getNextFragment(); |
| if (nextFragment == null || xmlCompositeDirectCollectionMapping.usesSingleNode()) { |
| return xPathFragment.isAttribute() || xPathFragment.nameIsText(); |
| } else { |
| return (nextFragment != null) && (nextFragment.nameIsText() || nextFragment.isAttribute()); |
| } |
| } |
| |
| /** |
| * Override the method in XPathNode such that the marshaller can be set on the |
| * marshalRecord - this is required for XMLConverter usage. |
| */ |
| @Override |
| public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver) { |
| if (xmlCompositeDirectCollectionMapping.isReadOnly()) { |
| return false; |
| } |
| |
| CoreContainerPolicy cp = getContainerPolicy(); |
| Object collection = xmlCompositeDirectCollectionMapping.getAttributeAccessor().getAttributeValueFromObject(object); |
| if (null == collection) { |
| AbstractNullPolicy wrapperNP = xmlCompositeDirectCollectionMapping.getWrapperNullPolicy(); |
| if (wrapperNP != null && wrapperNP.getMarshalNullRepresentation() == XMLNullRepresentationType.XSI_NIL) { |
| marshalRecord.nilSimple(namespaceResolver); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| Object iterator = cp.iteratorFor(collection); |
| Field xmlField = (Field) xmlCompositeDirectCollectionMapping.getField(); |
| if (null != iterator && cp.hasNext(iterator)) { |
| XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver); |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| } else { |
| if (xmlField.usesSingleNode() && !xmlCompositeDirectCollectionMapping.isDefaultEmptyContainer()) { |
| XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver); |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| } else { |
| return marshalRecord.emptyCollection(xPathFragment, namespaceResolver, xmlCompositeDirectCollectionMapping.getWrapperNullPolicy() != null); |
| } |
| } |
| |
| Object objectValue; |
| if (xmlField.usesSingleNode()) { |
| StringBuilder stringValueStringBuilder = new StringBuilder(); |
| String newValue; |
| QName schemaType = null; |
| if (xPathFragment != null && !xPathFragment.isAttribute() && !xPathFragment.nameIsText) { |
| marshalRecord.openStartElement(xPathFragment, namespaceResolver); |
| } |
| |
| while (cp.hasNext(iterator)) { |
| objectValue = cp.next(iterator, session); |
| objectValue = xmlCompositeDirectCollectionMapping.convertObjectValueToDataValue(objectValue, session, marshalRecord.getMarshaller()); |
| schemaType = xmlField.getSchemaTypeForValue(objectValue, session); |
| |
| newValue = marshalRecord.getValueToWrite(schemaType, objectValue, (ConversionManager) session.getDatasourcePlatform().getConversionManager()); |
| if (null != newValue) { |
| stringValueStringBuilder.append(newValue); |
| if (cp.hasNext(iterator)) { |
| stringValueStringBuilder.append(SPACE); |
| } |
| } |
| } |
| |
| XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver); |
| if (xPathFragment != null && xPathFragment.isAttribute()) { |
| marshalRecord.attribute(xPathFragment, namespaceResolver, stringValueStringBuilder.toString()); |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| } else { |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| if (xmlCompositeDirectCollectionMapping.isCDATA()) { |
| marshalRecord.cdata(stringValueStringBuilder.toString()); |
| } else { |
| marshalRecord.characters(stringValueStringBuilder.toString()); |
| if (xPathFragment != null && !xPathFragment.isAttribute() && !xPathFragment.nameIsText) { |
| marshalRecord.endElement(xPathFragment, namespaceResolver); |
| } |
| } |
| } |
| } else { |
| int valueSize = cp.sizeFor(collection); |
| if(marshalRecord.getMarshaller().isApplicationJSON() && (valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) { |
| marshalRecord.startCollection(); |
| } |
| |
| while (cp.hasNext(iterator)) { |
| objectValue = cp.next(iterator, session); |
| marshalSingleValue(xPathFragment, marshalRecord, object, objectValue, session, namespaceResolver, ObjectMarshalContext.getInstance()); |
| } |
| if(marshalRecord.getMarshaller().isApplicationJSON() && (valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) { |
| marshalRecord.endCollection(); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public void attribute(UnmarshalRecord unmarshalRecord, String namespaceURI, String localName, String value) { |
| Object collection = unmarshalRecord.getContainerInstance(this); |
| if (xmlCompositeDirectCollectionMapping.usesSingleNode()) { |
| StringTokenizer stringTokenizer = new StringTokenizer(value); |
| while (stringTokenizer.hasMoreTokens()) { |
| addUnmarshalValue(unmarshalRecord, stringTokenizer.nextToken(), collection); |
| } |
| } else { |
| addUnmarshalValue(unmarshalRecord, value, collection); |
| } |
| } |
| |
| @Override |
| public boolean startElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Attributes atts) { |
| Field xmlField = (Field) xmlCompositeDirectCollectionMapping.getField(); |
| XPathFragment lastXPathFragment = xmlField.getLastXPathFragment(); |
| if (lastXPathFragment.nameIsText()) { |
| String type = atts.getValue(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_TYPE_ATTRIBUTE); |
| if (null != type) { |
| String namespaceURI = null; |
| int colonIndex = type.indexOf(Constants.COLON); |
| if (colonIndex > -1) { |
| String prefix = type.substring(0, colonIndex); |
| namespaceURI = unmarshalRecord.resolveNamespacePrefix(prefix); |
| type = type.substring(colonIndex + 1); |
| } |
| unmarshalRecord.setTypeQName(new QName(namespaceURI, type)); |
| } |
| } else if (lastXPathFragment.isAttribute()) { |
| if (!xmlField.usesSingleNode()) { |
| String namespaceURI = lastXPathFragment.getNamespaceURI(); |
| if (namespaceURI == null) { |
| namespaceURI = Constants.EMPTY_STRING; |
| } |
| String value = atts.getValue(namespaceURI, lastXPathFragment.getLocalName()); |
| Object collection = unmarshalRecord.getContainerInstance(this); |
| addUnmarshalValue(unmarshalRecord, value, collection); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord) { |
| Field xmlField = (Field) xmlCompositeDirectCollectionMapping.getField(); |
| |
| Object value = unmarshalRecord.getCharacters().toString(); |
| if(((String)value).length() == 0 && !xmlField.usesSingleNode()){ |
| if( xmlCompositeDirectCollectionMapping.getNullValue() != null){ |
| value = xmlCompositeDirectCollectionMapping.getNullValue(); |
| } |
| } |
| unmarshalRecord.resetStringBuffer(); |
| XPathFragment lastXPathFragment = xmlField.getLastXPathFragment(); |
| if (!lastXPathFragment.nameIsText()) { |
| return; |
| } |
| |
| if (xmlField.usesSingleNode() ) { |
| |
| Object collection = unmarshalRecord.getContainerInstance(this); |
| StringTokenizer stringTokenizer = new StringTokenizer((String)value); |
| while (stringTokenizer.hasMoreTokens()) { |
| addUnmarshalValue(unmarshalRecord, stringTokenizer.nextToken(), collection); |
| } |
| } else { |
| if(!unmarshalRecord.getXMLReader().isInCollection() && unmarshalRecord.isNil() ){ |
| unmarshalRecord.setAttributeValueNull(this); |
| }else{ |
| Object collection = unmarshalRecord.getContainerInstance(this); |
| addUnmarshalValue(unmarshalRecord, value, collection); |
| } |
| } |
| } |
| |
| @Override |
| public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Object collection) { |
| Field xmlField = (Field) xmlCompositeDirectCollectionMapping.getField(); |
| |
| Object value = unmarshalRecord.getCharacters().toString(); |
| if(((String)value).length() == 0 && !xmlField.usesSingleNode()){ |
| if( xmlCompositeDirectCollectionMapping.getNullValue() != null){ |
| value = xmlCompositeDirectCollectionMapping.getNullValue(); |
| } |
| } |
| unmarshalRecord.resetStringBuffer(); |
| |
| if (xmlField.usesSingleNode() && value instanceof String) { |
| StringTokenizer stringTokenizer = new StringTokenizer((String)value); |
| while (stringTokenizer.hasMoreTokens()) { |
| addUnmarshalValue(unmarshalRecord, stringTokenizer.nextToken(), collection); |
| } |
| } else { |
| if (xmlField.getLastXPathFragment().nameIsText()) { |
| if(!unmarshalRecord.getXMLReader().isInCollection() && unmarshalRecord.isNil() ){ |
| unmarshalRecord.setAttributeValueNull(this); |
| } else{ |
| addUnmarshalValue(unmarshalRecord, value, collection); |
| } |
| } |
| } |
| } |
| |
| private void addUnmarshalValue(UnmarshalRecord unmarshalRecord, Object value, Object collection) { |
| |
| if (unmarshalRecord.isNil() && unmarshalRecord.getXMLReader().isNullRepresentedByXsiNil(xmlCompositeDirectCollectionMapping.getNullPolicy())){ |
| value = null; |
| } else if (!isWhitespaceAware() && Constants.EMPTY_STRING.equals(value)) { |
| value = null; |
| } |
| |
| Field xmlField = (Field) xmlCompositeDirectCollectionMapping.getField(); |
| |
| 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); |
| } |
| |
| value = xmlCompositeDirectCollectionMapping.convertDataValueToObjectValue(value, unmarshalRecord.getSession(), unmarshalRecord.getUnmarshaller()); |
| |
| if (value != null && value.getClass() == CoreClassConstants.STRING) { |
| if (xmlCompositeDirectCollectionMapping.isCollapsingStringValues()) { |
| value = conversionManager.collapseStringValue((String)value); |
| } else if (xmlCompositeDirectCollectionMapping.isNormalizingStringValues()) { |
| value = conversionManager.normalizeStringValue((String)value); |
| } |
| } |
| unmarshalRecord.addAttributeValue(this, value, collection); |
| } |
| |
| @Override |
| public Object getContainerInstance() { |
| return getContainerPolicy().containerInstance(); |
| } |
| |
| @Override |
| public void setContainerInstance(Object object, Object containerInstance) { |
| xmlCompositeDirectCollectionMapping.setAttributeValueInObject(object, containerInstance); |
| } |
| |
| @Override |
| public CoreContainerPolicy getContainerPolicy() { |
| return xmlCompositeDirectCollectionMapping.getContainerPolicy(); |
| } |
| |
| @Override |
| public boolean isContainerValue() { |
| return true; |
| } |
| |
| @Override |
| public boolean marshalSingleValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object value, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) { |
| value = xmlCompositeDirectCollectionMapping.convertObjectValueToDataValue(value, session, marshalRecord.getMarshaller()); |
| |
| if (null != value) { |
| Field xmlField = (Field) xmlCompositeDirectCollectionMapping.getField(); |
| QName schemaType = xmlField.getSchemaTypeForValue(value, session); |
| boolean isElementOpen = false; |
| if (Constants.QNAME_QNAME.equals(schemaType)) { |
| QName fieldValue = (QName) value; |
| if ((fieldValue.getNamespaceURI() == null || fieldValue.getNamespaceURI().equals("")) && marshalRecord.getNamespaceResolver().getDefaultNamespaceURI() != null) { |
| // In this case, an extra xmlns="" declaration is going to be added. This may |
| // require adjusting the namespace of the current fragment. |
| String defaultNamespaceURI = namespaceResolver.getDefaultNamespaceURI(); |
| if (defaultNamespaceURI.equals(xPathFragment.getNamespaceURI()) && xPathFragment.getPrefix() == null) { |
| String prefix = namespaceResolver.generatePrefix(); |
| String xPath = prefix + Constants.COLON + xPathFragment.getShortName(); |
| XPathFragment newFragment = new XPathFragment(xPath); |
| newFragment.setNamespaceURI(defaultNamespaceURI); |
| newFragment.setNextFragment(xPathFragment.getNextFragment()); |
| marshalRecord.openStartElement(newFragment, namespaceResolver); |
| isElementOpen = true; |
| marshalRecord.namespaceDeclaration(prefix, defaultNamespaceURI); |
| marshalRecord.predicateAttribute(xPathFragment, namespaceResolver); |
| xPathFragment = newFragment; |
| } |
| } |
| } |
| |
| if (!isElementOpen) { |
| marshalRecord.openStartElement(xPathFragment, namespaceResolver); |
| } |
| |
| XPathFragment nextFragment = xPathFragment.getNextFragment(); |
| if (nextFragment != null && nextFragment.isAttribute()) { |
| marshalRecord.predicateAttribute(xPathFragment, namespaceResolver); |
| marshalRecord.attribute(nextFragment, namespaceResolver, value,schemaType); |
| marshalRecord.closeStartElement(); |
| } else { |
| if (xmlField.isTypedTextField()) { |
| updateNamespaces(schemaType, marshalRecord, xmlField); |
| } |
| marshalRecord.closeStartElement(); |
| marshalRecord.predicateAttribute(xPathFragment, namespaceResolver); |
| marshalRecord.characters(schemaType, value, null, xmlCompositeDirectCollectionMapping.isCDATA()); |
| } |
| marshalRecord.endElement(xPathFragment, namespaceResolver); |
| return true; |
| } else { |
| AbstractNullPolicy nullPolicy = xmlCompositeDirectCollectionMapping.getNullPolicy(); |
| if (nullPolicy.getMarshalNullRepresentation() != XMLNullRepresentationType.ABSENT_NODE) { |
| marshalRecord.openStartElement(xPathFragment, namespaceResolver); |
| XPathFragment nextFragment = xPathFragment.getNextFragment(); |
| nullPolicy.directMarshal(nextFragment, marshalRecord, object, session, namespaceResolver); |
| marshalRecord.endElement(xPathFragment, namespaceResolver); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public DirectCollectionMapping getMapping() { |
| return xmlCompositeDirectCollectionMapping; |
| } |
| |
| @Override |
| public boolean isWhitespaceAware() { |
| return !xmlCompositeDirectCollectionMapping.getNullPolicy().isNullRepresentedByEmptyNode(); |
| } |
| |
| @Override |
| public boolean getReuseContainer() { |
| return getMapping().getReuseContainer(); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| @Override |
| public boolean isWrapperAllowedAsCollectionName() { |
| return true; |
| } |
| |
| } |