| /* |
| * Copyright (c) 1998, 2019 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.List; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.eclipse.persistence.core.queries.CoreAttributeGroup; |
| import org.eclipse.persistence.core.queries.CoreAttributeItem; |
| import org.eclipse.persistence.exceptions.XMLMarshalException; |
| import org.eclipse.persistence.internal.core.queries.CoreContainerPolicy; |
| import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession; |
| import org.eclipse.persistence.internal.oxm.mappings.CompositeCollectionMapping; |
| import org.eclipse.persistence.internal.oxm.mappings.Descriptor; |
| import org.eclipse.persistence.internal.oxm.mappings.Field; |
| import org.eclipse.persistence.internal.oxm.mappings.InverseReferenceMapping; |
| import org.eclipse.persistence.internal.oxm.mappings.UnmarshalKeepAsElementPolicy; |
| 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.internal.oxm.record.XMLReader; |
| import org.eclipse.persistence.internal.oxm.record.XMLRecord; |
| import org.eclipse.persistence.internal.oxm.record.deferred.CompositeCollectionMappingContentHandler; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * INTERNAL: |
| * <p><b>Purpose</b>: This is how the XML Composite Collection Mapping is handled when used with the TreeObjectBuilder. |
| * </p> |
| */ |
| public class XMLCompositeCollectionMappingNodeValue extends XMLRelationshipMappingNodeValue implements ContainerValue { |
| private CompositeCollectionMapping xmlCompositeCollectionMapping; |
| private int index = -1; |
| private boolean isInverseReference; |
| |
| public XMLCompositeCollectionMappingNodeValue(CompositeCollectionMapping xmlCompositeCollectionMapping) { |
| super(); |
| this.xmlCompositeCollectionMapping = xmlCompositeCollectionMapping; |
| } |
| |
| public XMLCompositeCollectionMappingNodeValue(CompositeCollectionMapping xmlCompositeCollectionMapping, boolean isInverse) { |
| this(xmlCompositeCollectionMapping); |
| this.isInverseReference = isInverse; |
| } |
| |
| @Override |
| public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver) { |
| if (xmlCompositeCollectionMapping.isReadOnly()) { |
| return false; |
| } |
| |
| Object collection = xmlCompositeCollectionMapping.getAttributeAccessor().getAttributeValueFromObject(object); |
| if (null == collection) { |
| AbstractNullPolicy wrapperNP = xmlCompositeCollectionMapping.getWrapperNullPolicy(); |
| if (wrapperNP != null && wrapperNP.getMarshalNullRepresentation() == XMLNullRepresentationType.XSI_NIL) { |
| marshalRecord.nilSimple(namespaceResolver); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| CoreContainerPolicy cp = getContainerPolicy(); |
| int size = marshalRecord.getCycleDetectionStack().size(); |
| // when writing the collection need to see if any of the objects we are |
| // writing are in the parent collection inverse ref |
| if ((isInverseReference || xmlCompositeCollectionMapping.getInverseReferenceMapping() != null) && size >= 2) { |
| Object owner = marshalRecord.getCycleDetectionStack().get(size - 2); |
| try { |
| if (cp.contains(owner, collection, session)) { |
| return false; |
| } |
| } catch (ClassCastException e) { |
| // For Bug #416875 |
| } |
| } |
| Object iterator = cp.iteratorFor(collection); |
| if (null != iterator && cp.hasNext(iterator)) { |
| XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver); |
| marshalRecord.closeStartGroupingElements(groupingFragment); |
| int valueSize = cp.sizeFor(collection); |
| if(marshalRecord.getMarshaller().isApplicationJSON() && (valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) { |
| marshalRecord.startCollection(); |
| } |
| do { |
| Object objectValue = cp.next(iterator, session); |
| marshalSingleValue(xPathFragment, marshalRecord, object, objectValue, session, namespaceResolver, ObjectMarshalContext.getInstance()); |
| } while (cp.hasNext(iterator)); |
| if(marshalRecord.getMarshaller().isApplicationJSON() && (valueSize > 1 || !marshalRecord.getMarshaller().isReduceAnyArrays())) { |
| marshalRecord.endCollection(); |
| } |
| return true; |
| } |
| return marshalRecord.emptyCollection(xPathFragment, namespaceResolver, xmlCompositeCollectionMapping.getWrapperNullPolicy() != null); |
| } |
| |
| @Override |
| public boolean startElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Attributes atts) { |
| try { |
| Descriptor xmlDescriptor = (Descriptor) xmlCompositeCollectionMapping.getReferenceDescriptor(); |
| if (xmlDescriptor == null) { |
| xmlDescriptor = findReferenceDescriptor(xPathFragment, unmarshalRecord, atts, xmlCompositeCollectionMapping, xmlCompositeCollectionMapping.getKeepAsElementPolicy()); |
| |
| if (xmlDescriptor == null) { |
| if (unmarshalRecord.getXMLReader().isNullRepresentedByXsiNil(xmlCompositeCollectionMapping.getNullPolicy())) { |
| if (unmarshalRecord.isNil()) { |
| return true; |
| } |
| } else if (xmlCompositeCollectionMapping.getNullPolicy().valueIsNull(atts)) { |
| getContainerPolicy().addInto(null, unmarshalRecord.getContainerInstance(this), unmarshalRecord.getSession()); |
| return true; |
| } |
| if (xmlCompositeCollectionMapping.getField() != null) { |
| //try leaf element type |
| QName leafType = ((Field) xmlCompositeCollectionMapping.getField()).getLastXPathFragment().getLeafElementType(); |
| if (leafType != null) { |
| XPathFragment frag = new XPathFragment(); |
| frag.setNamespaceAware(unmarshalRecord.isNamespaceAware()); |
| |
| String xpath = leafType.getLocalPart(); |
| String uri = leafType.getNamespaceURI(); |
| if (uri != null && uri.length() > 0) { |
| frag.setNamespaceURI(uri); |
| String prefix = ((Descriptor) xmlCompositeCollectionMapping.getDescriptor()).getNonNullNamespaceResolver().resolveNamespaceURI(uri); |
| if (prefix != null && prefix.length() > 0) { |
| xpath = prefix + Constants.COLON + xpath; |
| } |
| } |
| frag.setXPath(xpath); |
| Context xmlContext = unmarshalRecord.getUnmarshaller().getContext(); |
| xmlDescriptor = xmlContext.getDescriptorByGlobalType(frag); |
| } |
| } |
| } |
| |
| UnmarshalKeepAsElementPolicy policy = xmlCompositeCollectionMapping.getKeepAsElementPolicy(); |
| if (policy != null && ((xmlDescriptor == null && policy.isKeepUnknownAsElement()) || policy.isKeepAllAsElement())) { |
| if (unmarshalRecord.getTypeQName() != null) { |
| Class theClass = unmarshalRecord.getConversionManager().javaType(unmarshalRecord.getTypeQName()); |
| if (theClass == null) { |
| setupHandlerForKeepAsElementPolicy(unmarshalRecord, xPathFragment, atts); |
| return true; |
| } |
| } else { |
| setupHandlerForKeepAsElementPolicy(unmarshalRecord, xPathFragment, atts); |
| return true; |
| } |
| } |
| } |
| |
| AbstractNullPolicy nullPolicy = xmlCompositeCollectionMapping.getNullPolicy(); |
| if (nullPolicy.isNullRepresentedByEmptyNode()) { |
| String qnameString = xPathFragment.getLocalName(); |
| if (xPathFragment.getPrefix() != null) { |
| qnameString = xPathFragment.getPrefix() + Constants.COLON + qnameString; |
| } |
| if (null != xmlDescriptor) { |
| // Process null capable value |
| CompositeCollectionMappingContentHandler aHandler = new CompositeCollectionMappingContentHandler(// |
| unmarshalRecord, this, xmlCompositeCollectionMapping, atts, xPathFragment, xmlDescriptor); |
| // Send control to the handler |
| aHandler.startElement(xPathFragment.getNamespaceURI(), xPathFragment.getLocalName(), qnameString, atts); |
| XMLReader xmlReader = unmarshalRecord.getXMLReader(); |
| xmlReader.setContentHandler(aHandler); |
| xmlReader.setLexicalHandler(aHandler); |
| } |
| } else if (!(unmarshalRecord.getXMLReader().isNullRecord(nullPolicy, atts, unmarshalRecord))) { |
| Field xmlFld = (Field) this.xmlCompositeCollectionMapping.getField(); |
| if (xmlFld.hasLastXPathFragment()) { |
| unmarshalRecord.setLeafElementType(xmlFld.getLastXPathFragment().getLeafElementType()); |
| } |
| processChild(xPathFragment, unmarshalRecord, atts, xmlDescriptor, xmlCompositeCollectionMapping); |
| } |
| } catch (SAXException e) { |
| throw XMLMarshalException.unmarshalException(e); |
| } |
| return true; |
| } |
| |
| @Override |
| public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord) { |
| Object collection = unmarshalRecord.getContainerInstance(this); |
| endElement(xPathFragment, unmarshalRecord, collection); |
| } |
| |
| @Override |
| public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Object collection) { |
| if(unmarshalRecord.isNil() && unmarshalRecord.getXMLReader().isNullRepresentedByXsiNil(xmlCompositeCollectionMapping.getNullPolicy()) && |
| (unmarshalRecord.getChildRecord() == null)){ |
| if(unmarshalRecord.getXMLReader().isInCollection()){ |
| unmarshalRecord.addAttributeValue(this, null); |
| }else{ |
| unmarshalRecord.setAttributeValueNull(this); |
| } |
| unmarshalRecord.resetStringBuffer(); |
| return; |
| } |
| if (null == unmarshalRecord.getChildRecord()) { |
| SAXFragmentBuilder builder = unmarshalRecord.getFragmentBuilder(); |
| UnmarshalKeepAsElementPolicy keepAsElementPolicy = xmlCompositeCollectionMapping.getKeepAsElementPolicy(); |
| |
| if (null != keepAsElementPolicy && (keepAsElementPolicy.isKeepUnknownAsElement() || keepAsElementPolicy.isKeepAllAsElement()) && builder.getNodes().size() > 1) { |
| if(unmarshalRecord.getTypeQName() != null){ |
| Class theClass = unmarshalRecord.getConversionManager().javaType(unmarshalRecord.getTypeQName()); |
| if(theClass != null){ |
| //handle simple text |
| endElementProcessText(unmarshalRecord, xmlCompositeCollectionMapping, xPathFragment, collection); |
| return; |
| } |
| } |
| if(builder.getNodes().size() > 1) { |
| setOrAddAttributeValueForKeepAsElement(builder, xmlCompositeCollectionMapping, xmlCompositeCollectionMapping, unmarshalRecord, true, collection); |
| return; |
| } |
| }else{ |
| //handle simple text |
| endElementProcessText(unmarshalRecord, xmlCompositeCollectionMapping, xPathFragment, collection); |
| return; |
| } |
| |
| return; |
| } |
| Object objectValue = unmarshalRecord.getChildRecord().getCurrentObject(); |
| |
| InverseReferenceMapping inverseReferenceMapping = xmlCompositeCollectionMapping.getInverseReferenceMapping(); |
| if(null != inverseReferenceMapping) { |
| if(inverseReferenceMapping.getContainerPolicy() == null) { |
| Object currentValue = inverseReferenceMapping.getAttributeAccessor().getAttributeValueFromObject(objectValue); |
| if( !isInverseReference || (currentValue == null && isInverseReference)) { |
| inverseReferenceMapping.getAttributeAccessor().setAttributeValueInObject(objectValue, unmarshalRecord.getCurrentObject()); |
| } |
| } else { |
| Object backpointerContainer = inverseReferenceMapping.getAttributeAccessor().getAttributeValueFromObject(objectValue); |
| if(backpointerContainer == null) { |
| backpointerContainer = inverseReferenceMapping.getContainerPolicy().containerInstance(); |
| inverseReferenceMapping.getAttributeAccessor().setAttributeValueInObject(objectValue, backpointerContainer); |
| } |
| inverseReferenceMapping.getContainerPolicy().addInto(unmarshalRecord.getCurrentObject(), backpointerContainer, unmarshalRecord.getSession()); |
| } |
| } |
| |
| // convert the value - if necessary |
| objectValue = xmlCompositeCollectionMapping.convertDataValueToObjectValue(objectValue, unmarshalRecord.getSession(), unmarshalRecord.getUnmarshaller()); |
| unmarshalRecord.addAttributeValue(this, objectValue, collection); |
| |
| unmarshalRecord.setChildRecord(null); |
| |
| } |
| |
| @Override |
| public Object getContainerInstance() { |
| return getContainerPolicy().containerInstance(); |
| } |
| |
| @Override |
| public void setContainerInstance(Object object, Object containerInstance) { |
| xmlCompositeCollectionMapping.setAttributeValueInObject(object, containerInstance); |
| } |
| |
| @Override |
| public CoreContainerPolicy getContainerPolicy() { |
| return xmlCompositeCollectionMapping.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) { |
| |
| Marshaller marshaller = marshalRecord.getMarshaller(); |
| // convert the value - if necessary |
| boolean isNil = false; |
| if (value instanceof Root) { |
| isNil = ((Root) value).nil; |
| value = ((Root) value).getObject(); |
| } |
| |
| value = xmlCompositeCollectionMapping.convertObjectValueToDataValue(value, session, marshaller); |
| if (null == value) { |
| return xmlCompositeCollectionMapping.getNullPolicy().compositeObjectMarshal(xPathFragment, marshalRecord, object, session, namespaceResolver); |
| } |
| Descriptor descriptor = (Descriptor)xmlCompositeCollectionMapping.getReferenceDescriptor(); |
| if(descriptor == null){ |
| descriptor = (Descriptor) session.getDescriptor(value.getClass()); |
| }else if(descriptor.hasInheritance()){ |
| Class objectValueClass = value.getClass(); |
| if(!(objectValueClass == descriptor.getJavaClass())){ |
| descriptor = (Descriptor) session.getDescriptor(objectValueClass); |
| } |
| } |
| |
| UnmarshalKeepAsElementPolicy keepAsElementPolicy = xmlCompositeCollectionMapping.getKeepAsElementPolicy(); |
| if (null != keepAsElementPolicy && (keepAsElementPolicy.isKeepUnknownAsElement() || keepAsElementPolicy.isKeepAllAsElement()) && value instanceof org.w3c.dom.Node) { |
| marshalRecord.node((org.w3c.dom.Node) value, marshalRecord.getNamespaceResolver()); |
| return true; |
| } |
| |
| if(descriptor != null){ |
| marshalRecord.beforeContainmentMarshal(value); |
| |
| ObjectBuilder objectBuilder = (ObjectBuilder)descriptor.getObjectBuilder(); |
| |
| CoreAttributeGroup group = marshalRecord.getCurrentAttributeGroup(); |
| CoreAttributeGroup nestedGroup = XMLRecord.DEFAULT_ATTRIBUTE_GROUP; |
| CoreAttributeItem item = group.getItem(getMapping().getAttributeName()); |
| if(item != null) { |
| if(item.getGroups() != null) { |
| nestedGroup = item.getGroup(descriptor.getJavaClass()); |
| } |
| if(nestedGroup == null) { |
| nestedGroup = item.getGroup() == null?XMLRecord.DEFAULT_ATTRIBUTE_GROUP:item.getGroup(); |
| } |
| } |
| marshalRecord.pushAttributeGroup(nestedGroup); |
| |
| xPathNode.startElement(marshalRecord, xPathFragment, object, session, namespaceResolver, objectBuilder, value); |
| if (isNil) { |
| marshalRecord.nilSimple(namespaceResolver); |
| } |
| |
| List extraNamespaces = objectBuilder.addExtraNamespacesToNamespaceResolver(descriptor, marshalRecord, session,true, false); |
| writeExtraNamespaces(extraNamespaces, marshalRecord, session); |
| |
| marshalRecord.addXsiTypeAndClassIndicatorIfRequired(descriptor, (Descriptor) xmlCompositeCollectionMapping.getReferenceDescriptor(), (Field)xmlCompositeCollectionMapping.getField(), false); |
| |
| objectBuilder.buildRow(marshalRecord, value, session, marshaller, xPathFragment); |
| marshalRecord.afterContainmentMarshal(object, value); |
| marshalRecord.popAttributeGroup(); |
| marshalRecord.endElement(xPathFragment, namespaceResolver); |
| marshalRecord.removeExtraNamespacesFromNamespaceResolver(extraNamespaces, session); |
| } else { |
| if(Constants.UNKNOWN_OR_TRANSIENT_CLASS.equals(xmlCompositeCollectionMapping.getReferenceClassName())){ |
| throw XMLMarshalException.descriptorNotFoundInProject(value.getClass().getName()); |
| } |
| xPathNode.startElement(marshalRecord, xPathFragment, object, session, namespaceResolver, null, value); |
| |
| QName schemaType = ((Field) xmlCompositeCollectionMapping.getField()).getSchemaTypeForValue(value, session); |
| updateNamespaces(schemaType, marshalRecord,((Field)xmlCompositeCollectionMapping.getField())); |
| marshalRecord.characters(schemaType, value, null, false); |
| marshalRecord.endElement(xPathFragment, namespaceResolver); |
| } |
| return true; |
| } |
| |
| @Override |
| public CompositeCollectionMapping getMapping() { |
| return xmlCompositeCollectionMapping; |
| } |
| |
| @Override |
| protected void setOrAddAttributeValue(UnmarshalRecord unmarshalRecord, Object value, XPathFragment xPathFragment, Object collection){ |
| unmarshalRecord.addAttributeValue(this, value, collection); |
| } |
| |
| @Override |
| public boolean getReuseContainer() { |
| return xmlCompositeCollectionMapping.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 xmlCompositeCollectionMapping.isDefaultEmptyContainer(); |
| } |
| |
| @Override |
| public boolean isWrapperAllowedAsCollectionName() { |
| return true; |
| } |
| |
| } |