blob: 87e9e12582b79fb69f2e456475fb202afdf5576e [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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.BinaryDataCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.ChoiceCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.CollectionReferenceMapping;
import org.eclipse.persistence.internal.oxm.mappings.CompositeCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.DirectCollectionMapping;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
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.oxm.mappings.nullpolicy.AbstractNullPolicy;
import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType;
public class XMLChoiceCollectionMappingMarshalNodeValue extends MappingNodeValue implements ContainerValue {
private ChoiceCollectionMapping xmlChoiceCollectionMapping;
private Map<Field, NodeValue> fieldToNodeValues;
private Map<Class, List<FieldNodeValue>> classToNodeValues;
private NodeValue choiceElementNodeValue;
private Field xmlField;
private boolean isMixedNodeValue;
private boolean isAny;
private NodeValue anyNodeValue;
private int index = -1;
/**
* This class is needed to hold field and nodeValue relationship.
* If we have choice with different fields with the same (java class) type (e.g. via XmlAdapter),
* there is need to know to which field we are holding the nodeValue.
*
* It is used in getNodeValueForValue method. If we knew only class relationship to nodeValue,
* there is no way how to say that this nodeValue is related to the first or second field (or any other field with given java class).
*/
private static class FieldNodeValue {
//author Martin Vojtek (martin.vojtek@oracle.com)
//see AdapterWithElementsTestCases
private final Field field;
private final NodeValue nodeValue;
public FieldNodeValue(Field field, NodeValue nodeValue) {
super();
this.field = field;
this.nodeValue = nodeValue;
}
public Field getField() {
return field;
}
public NodeValue getNodeValue() {
return nodeValue;
}
}
public XMLChoiceCollectionMappingMarshalNodeValue(ChoiceCollectionMapping mapping, Field xmlField) {
this.xmlChoiceCollectionMapping = mapping;
this.xmlField = xmlField;
isAny = mapping.isAny();
initializeNodeValue();
}
@Override
public boolean isOwningNode(XPathFragment xPathFragment) {
if(isMixedNodeValue) {
if(xPathFragment.nameIsText()) {
return true;
} else {
return false;
}
}
return choiceElementNodeValue.isOwningNode(xPathFragment);
}
public void setFieldToNodeValues(Map<Field, NodeValue> fieldToNodeValues) {
this.fieldToNodeValues = fieldToNodeValues;
this.classToNodeValues = new HashMap<>();
for(Field nextField:fieldToNodeValues.keySet()) {
Class associatedClass = ((Map<Field, Class>)this.xmlChoiceCollectionMapping.getFieldToClassMappings()).get(nextField);
if (classToNodeValues.containsKey(associatedClass)) {
classToNodeValues.get(associatedClass).add(new FieldNodeValue(nextField, fieldToNodeValues.get(nextField)));
} else {
List<FieldNodeValue> newFieldToNodeValuesList = new ArrayList<>();
newFieldToNodeValuesList.add(new FieldNodeValue(nextField, fieldToNodeValues.get(nextField)));
this.classToNodeValues.put(associatedClass, newFieldToNodeValuesList);
}
}
Collection<Class> classes = this.classToNodeValues.keySet();
for(Class nextClass:((Map<Class, Mapping>)this.xmlChoiceCollectionMapping.getChoiceElementMappingsByClass()).keySet()) {
//Create node values for any classes that aren't already processed
if(!(classes.contains(nextClass))) {
Field field = (Field) xmlChoiceCollectionMapping.getClassToFieldMappings().get(nextClass);
NodeValue nodeValue = new XMLChoiceCollectionMappingUnmarshalNodeValue(xmlChoiceCollectionMapping, xmlField, (Mapping) xmlChoiceCollectionMapping.getChoiceElementMappingsByClass().get(nextClass));
List<FieldNodeValue> newFieldToNodeValuesList = new ArrayList<>();
newFieldToNodeValuesList.add(new FieldNodeValue(field, nodeValue));
this.classToNodeValues.put(nextClass, newFieldToNodeValuesList);
NodeValue nodeValueForField = fieldToNodeValues.get(field);
nodeValue.setXPathNode(nodeValueForField.getXPathNode());
}
}
}
private void initializeNodeValue() {
Mapping xmlMapping = (Mapping) xmlChoiceCollectionMapping.getChoiceElementMappings().get(xmlField);
if(xmlMapping instanceof BinaryDataCollectionMapping) {
choiceElementNodeValue = new XMLBinaryDataCollectionMappingNodeValue((BinaryDataCollectionMapping)xmlMapping);
} else if(xmlMapping instanceof DirectCollectionMapping) {
choiceElementNodeValue = new XMLCompositeDirectCollectionMappingNodeValue((DirectCollectionMapping)xmlMapping);
} else if(xmlMapping instanceof CompositeCollectionMapping) {
choiceElementNodeValue = new XMLCompositeCollectionMappingNodeValue((CompositeCollectionMapping)xmlMapping);
} else {
CollectionReferenceMapping refMapping = ((CollectionReferenceMapping)xmlMapping);
if(refMapping.usesSingleNode() || refMapping.getFields().size() == 1) {
choiceElementNodeValue = new XMLCollectionReferenceMappingNodeValue(refMapping, xmlField);
} else {
choiceElementNodeValue = new XMLCollectionReferenceMappingMarshalNodeValue((CollectionReferenceMapping)xmlMapping);
}
}
if(isAny){
anyNodeValue = new XMLChoiceCollectionMappingUnmarshalNodeValue(xmlChoiceCollectionMapping, null, xmlChoiceCollectionMapping.getAnyMapping());
}
}
@Override
public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver) {
if(xmlChoiceCollectionMapping.isReadOnly()) {
return false;
}
Object value = xmlChoiceCollectionMapping.getAttributeValueFromObject(object);
if(value == null) {
AbstractNullPolicy wrapperNP = xmlChoiceCollectionMapping.getWrapperNullPolicy();
if (wrapperNP != null && wrapperNP.getMarshalNullRepresentation() == XMLNullRepresentationType.XSI_NIL) {
marshalRecord.nilSimple(namespaceResolver);
return true;
} else {
return false;
}
}
CoreContainerPolicy cp = getContainerPolicy();
Object iterator = cp.iteratorFor(value);
if (null != iterator && cp.hasNext(iterator)) {
if(xPathFragment != null) {
XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver);
marshalRecord.closeStartGroupingElements(groupingFragment);
}
} else {
return marshalRecord.emptyCollection(xPathFragment, namespaceResolver, xmlChoiceCollectionMapping.getWrapperNullPolicy() != null);
}
if(marshalRecord.getMarshaller().isApplicationJSON()){
List<NodeValue> nodeValues = new ArrayList();
List<List> values = new ArrayList<>();
NodeValue mixedNodeValue = null;
List mixedValues = null;
//sort the elements. Results will be a list of nodevalues and a corresponding list of
//collections associated with those nodevalues
while(cp.hasNext(iterator)) {
Object nextValue = xmlChoiceCollectionMapping.convertObjectValueToDataValue(cp.next(iterator, session), session, marshalRecord.getMarshaller());
NodeValue nodeValue = getNodeValueForValue(nextValue);
if(nodeValue != null){
if(nodeValue == this){
mixedNodeValue = this;
if(mixedValues == null){
mixedValues = new ArrayList();
}
mixedValues.add(nextValue);
}else{
int index = nodeValues.indexOf(nodeValue);
if(index > -1){
values.get(index).add(nextValue);
}else{
nodeValues.add(nodeValue);
List valuesList = new ArrayList();
valuesList.add(nextValue);
values.add(valuesList);
}
}
}
}
//always write out mixed values last so we can determine if the textWrapper key needs to be written.
if(mixedNodeValue != null){
nodeValues.add(mixedNodeValue);
values.add(mixedValues);
}
for(int i =0;i < nodeValues.size(); i++){
NodeValue associatedNodeValue = nodeValues.get(i);
List listValue = values.get(i);
XPathFragment frag = null;
if(associatedNodeValue == this){
frag = marshalRecord.getTextWrapperFragment();
}else{
frag = associatedNodeValue.getXPathNode().getXPathFragment();
if(frag != null){
frag = getOwningFragment(associatedNodeValue, frag);
associatedNodeValue = ((XMLChoiceCollectionMappingUnmarshalNodeValue)associatedNodeValue).getChoiceElementMarshalNodeValue();
}
}
if(frag != null || associatedNodeValue.isAnyMappingNodeValue()){
int valueSize = listValue.size();
if(valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays()) {
marshalRecord.startCollection();
}
for(int j=0;j<valueSize; j++){
marshalSingleValueWithNodeValue(frag, marshalRecord, object, listValue.get(j), session, namespaceResolver, ObjectMarshalContext.getInstance(), associatedNodeValue);
}
if(valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays()) {
marshalRecord.endCollection();
}
}
}
}
else{
while(cp.hasNext(iterator)) {
Object nextValue = cp.next(iterator, session);
marshalSingleValue(xPathFragment, marshalRecord, object, nextValue, session, namespaceResolver, ObjectMarshalContext.getInstance());
}
}
return true;
}
@Override
public boolean marshalSingleValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object value, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
value = xmlChoiceCollectionMapping.convertObjectValueToDataValue(value, session, marshalRecord.getMarshaller());
if(value !=null && value.getClass() == CoreClassConstants.STRING && this.xmlChoiceCollectionMapping.isMixedContent()) {
marshalMixedContent(marshalRecord, (String)value);
return true;
}
NodeValue associatedNodeValue = getNodeValueForValue(value);
if(associatedNodeValue != null) {
if(associatedNodeValue.isAnyMappingNodeValue()){
//NodeValue unwrappedNodeValue = ((XMLChoiceCollectionMappingUnmarshalNodeValue)associatedNodeValue).getChoiceElementMarshalNodeValue();
return marshalSingleValueWithNodeValue(null, marshalRecord, object, value, session, namespaceResolver, marshalContext, associatedNodeValue);
}
else{
//Find the correct fragment
XPathFragment frag = associatedNodeValue.getXPathNode().getXPathFragment();
if(frag != null){
frag = getOwningFragment(associatedNodeValue, frag);
NodeValue unwrappedNodeValue = ((XMLChoiceCollectionMappingUnmarshalNodeValue)associatedNodeValue).getChoiceElementMarshalNodeValue();
return marshalSingleValueWithNodeValue(frag, marshalRecord, object, value, session, namespaceResolver, marshalContext, unwrappedNodeValue);
}
}
}
return true;
}
private boolean marshalSingleValueWithNodeValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object value, CoreAbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext, NodeValue unwrappedNodeValue) {
if(unwrappedNodeValue != null){
unwrappedNodeValue.marshalSingleValue(xPathFragment, marshalRecord, object, value, session, namespaceResolver, marshalContext);
}
return true;
}
private NodeValue getNodeValueForValue(Object value){
if(value == null){
Iterator<NodeValue> nodeValues= fieldToNodeValues.values().iterator();
while(nodeValues.hasNext()) {
XMLChoiceCollectionMappingUnmarshalNodeValue unmarshalNodeValue = (XMLChoiceCollectionMappingUnmarshalNodeValue)nodeValues.next();
NodeValue nextNodeValue = unmarshalNodeValue.getChoiceElementMarshalNodeValue();
if(nextNodeValue instanceof MappingNodeValue){
Mapping nextMapping = ((MappingNodeValue)nextNodeValue).getMapping();
if(nextMapping.isAbstractCompositeCollectionMapping()){
if(((CompositeCollectionMapping)nextMapping).getNullPolicy().isNullRepresentedByXsiNil()){
return unmarshalNodeValue;
}
}else if(nextMapping.isAbstractCompositeDirectCollectionMapping()){
if(((DirectCollectionMapping)nextMapping).getNullPolicy().isNullRepresentedByXsiNil()){
return unmarshalNodeValue;
}
}else if(nextMapping instanceof BinaryDataCollectionMapping){
if(((BinaryDataCollectionMapping)nextMapping).getNullPolicy().isNullRepresentedByXsiNil()){
return unmarshalNodeValue;
}
}
}
}
return null;
}
Field associatedField = null;
NodeValue nodeValue = null;
if(value instanceof Root) {
Root rootValue = (Root)value;
String localName = rootValue.getLocalName();
String namespaceUri = rootValue.getNamespaceURI();
Object fieldValue = rootValue.getObject();
associatedField = getFieldForName(localName, namespaceUri);
if(associatedField == null) {
if(xmlChoiceCollectionMapping.isAny()) {
return this.anyNodeValue;
}
Class<?> theClass = fieldValue.getClass();
while(associatedField == null) {
associatedField = (Field) xmlChoiceCollectionMapping.getClassToFieldMappings().get(theClass);
if(theClass.getSuperclass() != null) {
theClass = theClass.getSuperclass();
} else {
break;
}
}
}
if(associatedField != null) {
nodeValue = this.fieldToNodeValues.get(associatedField);
}
} else {
Class<?> theClass = value.getClass();
while(associatedField == null) {
associatedField = (Field) xmlChoiceCollectionMapping.getClassToFieldMappings().get(theClass);
List<FieldNodeValue> fieldNodes = classToNodeValues.get(theClass);
nodeValue = null;
if (null != fieldNodes) {
//match also field
if (null != associatedField && fieldNodes.size() > 1) {
for (FieldNodeValue fieldNode : fieldNodes) {
if (fieldNode.getField().equals(associatedField)) {
nodeValue = fieldNode.getNodeValue();
}
}
}
if (null == nodeValue && fieldNodes.size() > 0) {
nodeValue = fieldNodes.get(0).getNodeValue();
}
}
if(theClass.getSuperclass() != null) {
theClass = theClass.getSuperclass();
} else {
break;
}
}
}
if(associatedField == null) {
//check the field associations
List<Field> sourceFields = null;
Class<?> theClass = value.getClass();
while(theClass != null) {
sourceFields = (List<Field>) xmlChoiceCollectionMapping.getClassToSourceFieldsMappings().get(theClass);
if(sourceFields != null) {
break;
}
theClass = theClass.getSuperclass();
}
if(sourceFields != null) {
associatedField = sourceFields.get(0);
nodeValue = fieldToNodeValues.get(associatedField);
}
}
if(nodeValue != null){
return nodeValue;
}
if(associatedField != null) {
return fieldToNodeValues.get(associatedField);
}
if (xmlChoiceCollectionMapping.isMixedContent() && value instanceof String){
//use this as a placeholder for the nodevalue for mixedcontent
return this;
}
if (xmlChoiceCollectionMapping.isAny()){
return anyNodeValue;
}
return null;
}
private XPathFragment getOwningFragment(NodeValue nodeValue, XPathFragment frag){
while(frag != null) {
if(nodeValue.isOwningNode(frag)) {
return frag;
}
frag = frag.getNextFragment();
}
return null;
}
private void marshalMixedContent(MarshalRecord record, String value) {
record.characters(value);
}
private Field getFieldForName(String localName, String namespaceUri) {
Iterator<Field> fields = fieldToNodeValues.keySet().iterator();
while(fields.hasNext()) {
Field nextField = fields.next();
if(nextField != null){
XPathFragment fragment = nextField.getXPathFragment();
while(fragment != null && (!fragment.nameIsText())) {
if(fragment.getNextFragment() == null || fragment.getHasText()) {
if(fragment.getLocalName().equals(localName)) {
String fragUri = fragment.getNamespaceURI();
if((namespaceUri == null && fragUri == null) || (namespaceUri != null && fragUri != null && namespaceUri.equals(fragUri))) {
return nextField;
}
}
}
fragment = fragment.getNextFragment();
}
}
}
return null;
}
public Collection<NodeValue> getAllNodeValues() {
return this.fieldToNodeValues.values();
}
@Override
public boolean isMarshalNodeValue() {
return true;
}
@Override
public boolean isUnmarshalNodeValue() {
return false;
}
@Override
public boolean isWrapperAllowedAsCollectionName() {
return false;
}
@Override
public Object getContainerInstance() {
return getContainerPolicy().containerInstance();
}
@Override
public void setContainerInstance(Object object, Object containerInstance) {
xmlChoiceCollectionMapping.setAttributeValueInObject(object, containerInstance);
}
@Override
public CoreContainerPolicy getContainerPolicy() {
return xmlChoiceCollectionMapping.getContainerPolicy();
}
@Override
public boolean isContainerValue() {
return true;
}
@Override
public ChoiceCollectionMapping getMapping() {
return xmlChoiceCollectionMapping;
}
@Override
public boolean getReuseContainer() {
return getMapping().getReuseContainer();
}
/**
* INTERNAL:
* Indicates that this is the choice mapping node value that represents the mixed content.
*/
public void setIsMixedNodeValue(boolean b) {
this.isMixedNodeValue = b;
}
/**
* INTERNAL:
* Return true if this is the node value representing mixed content.
*/
@Override
public boolean isMixedContentNodeValue() {
return this.isMixedNodeValue;
}
/**
* 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 void setXPathNode(XPathNode xPathNode) {
super.setXPathNode(xPathNode);
if(this.anyNodeValue != null) {
this.anyNodeValue.setXPathNode(xPathNode);
}
}
}