blob: a5fd95d328fab216a62676b6586a325465e432c8 [file] [log] [blame]
/*
* 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;
}
}