| /* |
| * 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.oxm.mappings; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.XMLMarshalException; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.oxm.XMLObjectBuilder; |
| import org.eclipse.persistence.internal.oxm.XPathEngine; |
| import org.eclipse.persistence.internal.oxm.XPathFragment; |
| import org.eclipse.persistence.internal.oxm.mappings.AnyCollectionMapping; |
| import org.eclipse.persistence.internal.oxm.mappings.XMLContainerMapping; |
| import org.eclipse.persistence.internal.queries.CollectionContainerPolicy; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.queries.ListContainerPolicy; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.ChangeRecord; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.mappings.AttributeAccessor; |
| import org.eclipse.persistence.mappings.ContainerMapping; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.oxm.XMLConstants; |
| import org.eclipse.persistence.oxm.XMLContext; |
| import org.eclipse.persistence.oxm.XMLDescriptor; |
| import org.eclipse.persistence.oxm.XMLField; |
| import org.eclipse.persistence.oxm.XMLMarshaller; |
| import org.eclipse.persistence.oxm.XMLRoot; |
| import org.eclipse.persistence.oxm.XMLUnmarshaller; |
| import org.eclipse.persistence.oxm.mappings.converters.XMLConverter; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; |
| import org.eclipse.persistence.oxm.record.DOMRecord; |
| import org.eclipse.persistence.oxm.record.XMLRecord; |
| import org.eclipse.persistence.platform.xml.XMLPlatform; |
| import org.eclipse.persistence.platform.xml.XMLPlatformFactory; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.sessions.Session; |
| import org.eclipse.persistence.sessions.remote.DistributedSession; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.Text; |
| |
| /** |
| * <p>Any collection XML mappings map an attribute that contains a heterogenous collection of |
| * objects to multiple XML elements. Unlike composite collection XML mappings, the referenced |
| * objects may be of different types (including String), and do not need to be related to each |
| * other through inheritance or a common interface. The corresponding object attribute should |
| * be generic enough for all possible application values. Note that each of the referenced objects |
| * (except String) must specify a default root element on their descriptor. |
| * |
| * <p><b>Any collection mappings are useful with the following XML schema constructs</b>:<ul> |
| * <li> any </li> |
| * <li> choice </li> |
| * <li> substitution groups </li> |
| * </ul> |
| * |
| * <p><b>Setting the XPath</b>: TopLink XML mappings make use of XPath statements to find the relevant |
| * data in an XML document. The XPath statement is relative to the context node specified in the descriptor. |
| * The XPath may contain node type, path, and positional information. The XPath is specified on the |
| * mapping using the <code>setXPath</code> method. Note that for XML Any Collection Mappings the XPath |
| * is optional. |
| * |
| * <p>The following XPath statements may be used to specify the location of XML data relating to an object's |
| * name attribute: |
| * |
| * <table border="1"> |
| * <caption>XPath statements</caption> |
| * <tr> |
| * <th id="c1">XPath</th> |
| * <th id="c2">Description</th> |
| * </tr> |
| * <tr> |
| * <td headers="c1">contact-methods</td> |
| * <td headers="c2">The name information is stored in the contact-methods element.</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1" style="nowrap">contact-methods/info</td> |
| * <td headers="c2">The XPath statement may be used to specify any valid path.</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1">contact-methods[2]</td> |
| * <td headers="c2">The XPath statement may contain positional information. In this case the contact |
| * information is stored in the second occurrence of the contact-methods element.</td> |
| * </tr> |
| * </table> |
| * |
| * <p><b>Mapping an element of type xs:anyType as an Any Collection Mapping</b>: |
| * <!-- |
| * <?xml version="1.0" encoding="UTF-8"?> |
| * <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
| * <xsd:element name="customer" type="customer-type"/> |
| * <xsd:complexType name="customer-type"> |
| * <xsd:sequence> |
| * <xsd:element name="contact-methods" type="xsd:anyType"/> |
| * </xsd:sequence> |
| * </xsd:complexType> |
| * <xsd:element name="address"> |
| * <xsd:complexType> |
| * <xsd:sequence> |
| * <xsd:element name="street" type="xsd:string"/> |
| * <xsd:element name="city" type="xsd:string"/> |
| * </xsd:sequence> |
| * </xsd:complexType> |
| * </xsd:element> |
| * <xsd:element name="phone-number" type="xsd:string"/> |
| * </xsd:schema> |
| * --> |
| * |
| * <p><em>XML Schema</em><br> |
| * <code> |
| * <?xml version="1.0" encoding="UTF-8"?><br> |
| * <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"><br> |
| * <xsd:element name="customer" type="customer-type"/><br> |
| * <xsd:complexType name="customer-type"><br> |
| * <xsd:sequence><br> |
| * <xsd:element name="contact-methods" type="xsd:anyType"/><br> |
| * </xsd:sequence><br> |
| * </xsd:complexType><br> |
| * <xsd:element name="address"><br> |
| * <xsd:complexType><br> |
| * <xsd:sequence><br> |
| * <xsd:element name="street" type="xsd:string"/><br> |
| * <xsd:element name="city" type="xsd:string"/><br> |
| * </xsd:sequence><br> |
| * </xsd:complexType><br> |
| * </xsd:element><br> |
| * <xsd:element name="phone-number" type="xsd:string"/><br> |
| * </xsd:schema><br> |
| * </code> |
| * |
| * <p><em>Code Sample</em><br> |
| * <code> |
| * XMLAnyCollectionMapping contactMethodsMapping = new XMLAnyCollectionMapping();<br> |
| * contactMethodsMapping.setAttributeName("contactMethods");<br> |
| * contactMethodsMapping.setXPath("contact-methods");<br> |
| * </code> |
| * |
| * <p><b>More Information</b>: For more information about using the XML Any Collection Mapping, see the |
| * "Understanding XML Mappings" chapter of the Oracle TopLink Developer's Guide. |
| * |
| * @since Oracle TopLink 10<i>g</i> Release 2 (10.1.3) |
| */ |
| public class XMLAnyCollectionMapping extends XMLAbstractAnyMapping implements AnyCollectionMapping<AbstractSession, AttributeAccessor, ContainerPolicy, XMLConverter, ClassDescriptor, DatabaseField, XMLMarshaller, Session, UnmarshalKeepAsElementPolicy, XMLUnmarshaller, XMLRecord>, XMLMapping, ContainerMapping { |
| private XMLField field; |
| private ContainerPolicy containerPolicy; |
| private boolean defaultEmptyContainer = XMLContainerMapping.EMPTY_CONTAINER_DEFAULT; |
| private boolean useXMLRoot; |
| private boolean mixedContent = true; |
| private boolean isWhitespacePreservedForMixedContent = false; |
| private boolean areOtherMappingInThisContext = true; |
| private XMLConverter valueConverter; |
| private boolean reuseContainer; |
| private AbstractNullPolicy wrapperNullPolicy; |
| |
| public XMLAnyCollectionMapping() { |
| this.containerPolicy = ContainerPolicy.buildDefaultPolicy(); |
| useXMLRoot = false; |
| setKeepAsElementPolicy(UnmarshalKeepAsElementPolicy.KEEP_NONE_AS_ELEMENT); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the attribute from the clone and assign it to the backup. |
| */ |
| @Override |
| public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) { |
| throw DescriptorException.invalidMappingOperation(this, "buildBackupClone"); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clone the attribute from the original and assign it to the clone. |
| */ |
| @Override |
| public void buildClone(Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession) { |
| throw DescriptorException.invalidMappingOperation(this, "buildClone"); |
| } |
| |
| @Override |
| public void buildCloneFromRow(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object clone, CacheKey sharedCacheKey, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) { |
| throw DescriptorException.invalidMappingOperation(this, "buildCloneFromRow"); |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade perform delete through mappings that require the cascade |
| */ |
| @Override |
| public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| //objects referenced by this mapping are not registered as they have |
| // no identity, this is a no-op. |
| } |
| |
| /** |
| * INTERNAL: |
| * Cascade registerNew for Create through mappings that require the cascade |
| */ |
| @Override |
| public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, Map visitedObjects) { |
| //Our current XML support does not make use of the UNitOfWork. |
| } |
| |
| @Override |
| public Object clone() { |
| // Bug 3037701 - clone the AttributeAccessor |
| XMLAnyCollectionMapping mapping = null; |
| mapping = (XMLAnyCollectionMapping) super.clone(); |
| mapping.setContainerPolicy(this.getContainerPolicy()); |
| mapping.setField(this.getField()); |
| return mapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method was created in VisualAge. |
| * @return prototype.changeset.ChangeRecord |
| */ |
| @Override |
| public ChangeRecord compareForChange(Object clone, Object backup, ObjectChangeSet owner, AbstractSession session) { |
| throw DescriptorException.invalidMappingOperation(this, "compareForChange"); |
| } |
| |
| /** |
| * INTERNAL: |
| * Compare the attributes belonging to this mapping for the objects. |
| */ |
| @Override |
| public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) { |
| throw DescriptorException.invalidMappingOperation(this, "compareObjects"); |
| } |
| |
| /** |
| * INTERNAL: |
| * An object has been serialized from the server to the client. |
| * Replace the transient attributes of the remote value holders |
| * with client-side objects. |
| */ |
| @Override |
| public void fixObjectReferences(Object object, Map objectDescriptors, Map processedObjects, ObjectLevelReadQuery query, DistributedSession session) { |
| throw DescriptorException.invalidMappingOperation(this, "fixObjectReferences"); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the mapping's containerPolicy. |
| */ |
| @Override |
| public ContainerPolicy getContainerPolicy() { |
| return containerPolicy; |
| } |
| |
| @Override |
| public DatabaseField getField() { |
| return field; |
| } |
| |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| if (getField() != null) { |
| setField(getDescriptor().buildField(getField())); |
| } |
| if(valueConverter != null) { |
| valueConverter.initialize(this, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate on the appropriate attribute value. |
| */ |
| @Override |
| public void iterate(DescriptorIterator iterator) { |
| throw DescriptorException.invalidMappingOperation(this, "iterate"); |
| } |
| |
| public void setXPath(String xpath) { |
| this.field = new XMLField(xpath); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| */ |
| @Override |
| public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| throw DescriptorException.invalidMappingOperation(this, "mergeChangesIntoObject"); |
| } |
| |
| /** |
| * INTERNAL: |
| * Merge changes from the source to the target object. |
| */ |
| @Override |
| public void mergeIntoObject(Object target, boolean isTargetUninitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) { |
| throw DescriptorException.invalidMappingOperation(this, "mergeIntoObject"); |
| } |
| |
| @Override |
| public void setContainerPolicy(ContainerPolicy cp) { |
| this.containerPolicy = cp; |
| } |
| |
| @Override |
| public void setField(DatabaseField field) { |
| this.field = (XMLField) field; |
| } |
| |
| @Override |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession executionSession, boolean isTargetProtected, Boolean[] wasCacheUsed) throws DatabaseException { |
| XMLRecord record = (XMLRecord) row; |
| |
| if (getField() != null) { |
| //Get the nested row represented by this field to build the collection from |
| Object nested = record.get(getField()); |
| if (nested instanceof Vector) { |
| nested = ((Vector) nested).firstElement(); |
| } |
| if (!(nested instanceof XMLRecord)) { |
| return null; |
| } |
| record = (XMLRecord) nested; |
| } |
| return buildObjectValuesFromDOMRecord((DOMRecord) record, executionSession, sourceQuery, joinManager); |
| } |
| |
| private Object buildObjectValuesFromDOMRecord(DOMRecord record, AbstractSession session, ObjectBuildingQuery query, JoinedAttributeManager joinManager) { |
| //This DOMRecord represents the root node of the AnyType instance |
| //Grab ALL children to populate the collection. |
| Node root = record.getDOM(); |
| NodeList children = root.getChildNodes(); |
| |
| ContainerPolicy cp = getContainerPolicy(); |
| Object container = null; |
| if (reuseContainer) { |
| Object currentObject = record.getCurrentObject(); |
| Object value = getAttributeAccessor().getAttributeValueFromObject(currentObject); |
| container = value != null ? value : cp.containerInstance(); |
| } else { |
| container = cp.containerInstance(); |
| } |
| |
| int length = children.getLength(); |
| Node next = null; |
| if(length > 0) { |
| next = record.getDOM().getFirstChild(); |
| } |
| while(next != null) { |
| Object objectValue = null; |
| if (isUnmappedContent(next)) { |
| if ((next.getNodeType() == Node.TEXT_NODE) && this.isMixedContent()) { |
| if (next.getNodeValue().trim().length() > 0) { |
| objectValue = next.getNodeValue(); |
| objectValue = convertDataValueToObjectValue(objectValue, session, record.getUnmarshaller()); |
| cp.addInto(objectValue, container, session); |
| } |
| } else if (next.getNodeType() == Node.ELEMENT_NODE) { |
| XMLDescriptor referenceDescriptor = null; |
| |
| //In this case it must be an element so we need to dig up the descriptor |
| //make a nested record and build an object from it. |
| DOMRecord nestedRecord = (DOMRecord) record.buildNestedRow((Element) next); |
| |
| if (!useXMLRoot) { |
| referenceDescriptor = getDescriptor(nestedRecord, session, null); |
| objectValue = buildObjectForNonXMLRoot(referenceDescriptor, getConverter(), query, record, nestedRecord, joinManager, session, next, container, cp); |
| } else { |
| String schemaType = ((Element) next).getAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XMLConstants.SCHEMA_TYPE_ATTRIBUTE); |
| QName schemaTypeQName = null; |
| XPathFragment frag = new XPathFragment(); |
| if ((null != schemaType) && (schemaType.length() > 0)) { |
| frag.setXPath(schemaType); |
| if (frag.hasNamespace()) { |
| String prefix = frag.getPrefix(); |
| XMLPlatform xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform(); |
| String url = xmlPlatform.resolveNamespacePrefix(next, prefix); |
| frag.setNamespaceURI(url); |
| schemaTypeQName = new QName(url, frag.getLocalName()); |
| } |
| XMLContext xmlContext = nestedRecord.getUnmarshaller().getXMLContext(); |
| referenceDescriptor = xmlContext.getDescriptorByGlobalType(frag); |
| } |
| if (referenceDescriptor == null) { |
| try { |
| QName qname = new QName(nestedRecord.getNamespaceURI(), nestedRecord.getLocalName()); |
| referenceDescriptor = getDescriptor(nestedRecord, session, qname); |
| } catch (XMLMarshalException e) { |
| referenceDescriptor = null; |
| } |
| // Check if descriptor is for a wrapper, if it is null it out and let continue |
| XMLDescriptor xmlReferenceDescriptor = referenceDescriptor; |
| if (referenceDescriptor != null && xmlReferenceDescriptor.isWrapper()) { |
| referenceDescriptor = null; |
| } |
| } |
| // if KEEP_ALL_AS_ELEMENT is set, or we don't have a descriptor and KEEP_UNKNOWN_AS_ELEMENT |
| // is set, then we want to return either an Element or an XMLRoot wrapping an Element |
| if (getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT || (referenceDescriptor == null && getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT)) { |
| Object objVal = buildObjectNoReferenceDescriptor(record, getConverter(), session, next, null, null); |
| // wrap the object in an XMLRoot |
| // if we know the descriptor use it to wrap the Element in an XMLRoot (if necessary) |
| if (referenceDescriptor != null) { |
| objVal = referenceDescriptor.wrapObjectInXMLRoot(objVal, next.getNamespaceURI(), next.getLocalName(), next.getPrefix(), false, record.isNamespaceAware(), record.getUnmarshaller()); |
| cp.addInto(objVal, container, session); |
| } else { |
| // no descriptor, so manually build the XMLRoot |
| cp.addInto(buildXMLRoot(next, objVal), container, session); |
| } |
| } |
| // if we have a descriptor use it to build the object and wrap it in an XMLRoot |
| else if (referenceDescriptor != null) { |
| buildObjectAndWrapInXMLRoot(referenceDescriptor, getConverter(), query, record, nestedRecord, joinManager, session, next, container, cp); |
| } else { |
| // no descriptor, but could be TEXT to wrap and return |
| XMLRoot rootValue; |
| if ((rootValue = buildXMLRootForText(next, schemaTypeQName, getConverter(), session, record)) != null) { |
| cp.addInto(rootValue, container, session); |
| } |
| } |
| } |
| } |
| } |
| next = next.getNextSibling(); |
| } |
| return container; |
| } |
| |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) throws DescriptorException { |
| if (this.isReadOnly()) { |
| return; |
| } |
| DOMRecord record = (DOMRecord) row; |
| Object attributeValue = this.getAttributeValueFromObject(object); |
| Node root = record.getDOM(); |
| org.w3c.dom.Document doc = record.getDocument(); |
| XMLField xmlRootField = null; |
| if (attributeValue == null) { |
| return; |
| } |
| if (field != null) { |
| root = XPathEngine.getInstance().create((XMLField) getField(), record.getDOM(), session); |
| DOMRecord rootRecord = new DOMRecord(root); |
| rootRecord.setSession(session); |
| rootRecord.setDocPresPolicy(record.getDocPresPolicy()); |
| rootRecord.setNamespaceResolver(record.getNamespaceResolver()); |
| rootRecord.setMarshaller(record.getMarshaller()); |
| record = rootRecord; |
| } |
| |
| ContainerPolicy cp = this.getContainerPolicy(); |
| ArrayList childNodes = getUnmappedChildNodes(record.getDOM().getChildNodes()); |
| Object iter = cp.iteratorFor(attributeValue); |
| int childNodeCount = 0; |
| boolean wasXMLRoot = false; |
| if(null != iter) { |
| while (cp.hasNext(iter) && (childNodeCount < childNodes.size())) { |
| Object element = cp.next(iter, session); |
| element = convertObjectValueToDataValue(element, session, record.getMarshaller()); |
| Object originalObject = element; |
| Node nextChild = null; |
| while (childNodeCount < childNodes.size()) { |
| Node nextPossible = (Node) childNodes.get(childNodeCount); |
| if ((nextPossible.getNodeType() == Node.ELEMENT_NODE) || (nextPossible.getNodeType() == Node.TEXT_NODE) || (nextPossible.getNodeType() == Node.CDATA_SECTION_NODE)) { |
| nextChild = nextPossible; |
| childNodeCount++; |
| break; |
| } |
| childNodeCount++; |
| } |
| if (nextChild == null) { |
| break; |
| } |
| if (usesXMLRoot() && (element instanceof XMLRoot)) { |
| xmlRootField = new XMLField(); |
| wasXMLRoot = true; |
| XPathFragment frag = new XPathFragment(); |
| |
| if ((((XMLRoot) element)).getNamespaceURI() != null) { |
| frag.setNamespaceURI(((XMLRoot) element).getNamespaceURI()); |
| } |
| frag.setXPath(((XMLRoot) element).getLocalName()); |
| xmlRootField.setXPathFragment(frag); |
| |
| xmlRootField.setNamespaceResolver(record.getNamespaceResolver()); |
| element = ((XMLRoot) element).getObject(); |
| } |
| if (element instanceof String) { |
| if (wasXMLRoot) { |
| if (((XMLRoot) originalObject).getNamespaceURI() != null) { |
| String prefix = record.getNamespaceResolver().resolveNamespaceURI(((XMLRoot) originalObject).getNamespaceURI()); |
| if ((prefix == null) || prefix.length() == 0) { |
| xmlRootField.getXPathFragment().setGeneratedPrefix(true); |
| prefix = record.getNamespaceResolver().generatePrefix(); |
| } |
| xmlRootField.getXPathFragment().setXPath(prefix + XMLConstants.COLON + ((XMLRoot) originalObject).getLocalName()); |
| } |
| } |
| |
| if (xmlRootField != null) { |
| XPathEngine.getInstance().create(xmlRootField, root, element, session); |
| } else { |
| Text textNode = doc.createTextNode((String) element); |
| root.replaceChild(textNode, nextChild); |
| } |
| } else if (element instanceof org.w3c.dom.Node) { |
| Node importedCopy = doc.importNode((Node) element, true); |
| root.replaceChild(importedCopy, nextChild); |
| } else { |
| XMLDescriptor referenceDescriptor = (XMLDescriptor) session.getDescriptor(element.getClass()); |
| |
| if (wasXMLRoot) { |
| if (((XMLRoot) originalObject).getNamespaceURI() != null) { |
| String prefix = referenceDescriptor.getNonNullNamespaceResolver().resolveNamespaceURI(((XMLRoot) originalObject).getNamespaceURI()); |
| if ((prefix == null) || prefix.length() == 0) { |
| prefix = record.getNamespaceResolver().resolveNamespaceURI(((XMLRoot) originalObject).getNamespaceURI()); |
| } |
| if ((prefix == null) || prefix.length() == 0) { |
| xmlRootField.getXPathFragment().setGeneratedPrefix(true); |
| prefix = record.getNamespaceResolver().generatePrefix(); |
| } |
| xmlRootField.getXPathFragment().setXPath(prefix + XMLConstants.COLON + ((XMLRoot) originalObject).getLocalName()); |
| } |
| } |
| |
| DOMRecord nestedRecord = (DOMRecord) buildCompositeRow(element, session, referenceDescriptor, row, xmlRootField, element, wasXMLRoot); |
| if (nestedRecord != null) { |
| if (nestedRecord.getDOM() != nextChild) { |
| root.replaceChild(nestedRecord.getDOM(), nextChild); |
| } |
| } |
| } |
| } |
| } |
| if (childNodeCount < childNodes.size()) { |
| //Remove whatever's left |
| for (int i = childNodeCount; i < childNodes.size(); i++) { |
| Node child = (Node) childNodes.get(i); |
| if ((child.getNodeType() == Node.ELEMENT_NODE) || (child.getNodeType() == Node.TEXT_NODE) || (child.getNodeType() == Node.CDATA_SECTION_NODE)) { |
| root.removeChild(child); |
| } |
| } |
| } |
| if (null != iter && cp.hasNext(iter)) { |
| while (cp.hasNext(iter)) { |
| //add in extras |
| Object element = cp.next(iter, session); |
| writeSingleValue(element, object, record, session); |
| } |
| } |
| } |
| |
| @Override |
| public void writeSingleValue(Object element, Object parent, XMLRecord row, AbstractSession session) { |
| XMLField xmlRootField = null; |
| Object originalObject = element; |
| DOMRecord record = (DOMRecord) row; |
| Node root = record.getDOM(); |
| org.w3c.dom.Document doc = row.getDocument(); |
| boolean wasXMLRoot = false; |
| |
| if (usesXMLRoot() && (element instanceof XMLRoot)) { |
| wasXMLRoot = true; |
| xmlRootField = new XMLField(); |
| XPathFragment frag = new XPathFragment(); |
| if ((((XMLRoot) element)).getNamespaceURI() != null) { |
| frag.setNamespaceURI(((XMLRoot) element).getNamespaceURI()); |
| } |
| frag.setLocalName(((XMLRoot) element).getLocalName()); |
| |
| xmlRootField.setXPathFragment(frag); |
| |
| xmlRootField.setNamespaceResolver(row.getNamespaceResolver()); |
| element = ((XMLRoot) element).getObject(); |
| } |
| if (element instanceof String) { |
| writeSimpleValue(xmlRootField, element, originalObject, record, doc, root, wasXMLRoot, session); |
| } else if (element instanceof org.w3c.dom.Node) { |
| Node importedCopy = doc.importNode((Node) element, true); |
| root.appendChild(importedCopy); |
| } else { |
| XMLDescriptor referenceDescriptor = (XMLDescriptor) session.getDescriptor(element.getClass()); |
| if (referenceDescriptor == null) { |
| writeSimpleValue(xmlRootField, element, originalObject, record, doc, root, wasXMLRoot, session); |
| return; |
| } |
| if (wasXMLRoot) { |
| if (((XMLRoot) originalObject).getNamespaceURI() != null) { |
| String prefix = referenceDescriptor.getNonNullNamespaceResolver().resolveNamespaceURI(((XMLRoot) originalObject).getNamespaceURI()); |
| if ((prefix == null) || prefix.length() == 0) { |
| prefix = record.getNamespaceResolver().resolveNamespaceURI(((XMLRoot) originalObject).getNamespaceURI()); |
| } |
| if ((prefix == null) || prefix.length() == 0) { |
| xmlRootField.getXPathFragment().setGeneratedPrefix(true); |
| prefix = record.getNamespaceResolver().generatePrefix(); |
| } |
| xmlRootField.getXPathFragment().setXPath(prefix + XMLConstants.COLON + ((XMLRoot) originalObject).getLocalName()); |
| } |
| } |
| |
| DOMRecord nestedRecord = (DOMRecord) buildCompositeRow(element, session, referenceDescriptor, row, xmlRootField, originalObject, wasXMLRoot); |
| if (nestedRecord != null) { |
| root.appendChild(nestedRecord.getDOM()); |
| } |
| } |
| } |
| |
| private void writeSimpleValue(XMLField xmlRootField, Object element, Object originalObject, DOMRecord record, org.w3c.dom.Document doc, Node root, boolean wasXMLRoot, AbstractSession session) { |
| if (wasXMLRoot) { |
| if (((XMLRoot) originalObject).getNamespaceURI() != null) { |
| String prefix = record.getNamespaceResolver().resolveNamespaceURI(((XMLRoot) originalObject).getNamespaceURI()); |
| if ((prefix == null) || prefix.length() == 0) { |
| xmlRootField.getXPathFragment().setGeneratedPrefix(true); |
| prefix = record.getNamespaceResolver().generatePrefix(); |
| } |
| xmlRootField.getXPathFragment().setXPath(prefix + XMLConstants.COLON + ((XMLRoot) originalObject).getLocalName()); |
| } |
| } |
| if (xmlRootField != null) { |
| xmlRootField.setNamespaceResolver(record.getNamespaceResolver()); |
| QName qname = ((XMLRoot) originalObject).getSchemaType(); |
| if(qname != null){ |
| if(!qname.equals(XMLConstants.STRING_QNAME)){ |
| xmlRootField.setSchemaType(qname); |
| xmlRootField.setIsTypedTextField(true); |
| xmlRootField.addJavaConversion(((XMLRoot) originalObject).getObject().getClass(), qname); |
| } |
| } |
| Node newNode = XPathEngine.getInstance().create(xmlRootField, root, element, session); |
| } else { |
| Text textNode = doc.createTextNode((String) element); |
| root.appendChild(textNode); |
| } |
| } |
| |
| protected AbstractRecord buildCompositeRow(Object attributeValue, AbstractSession session, XMLDescriptor referenceDescriptor, AbstractRecord parentRow, DatabaseField field, Object originalObject, boolean wasXMLRoot) { |
| String defaultRootElementString = null; |
| if(referenceDescriptor != null){ |
| defaultRootElementString = referenceDescriptor.getDefaultRootElement(); |
| if (!wasXMLRoot && defaultRootElementString == null) { |
| throw XMLMarshalException.defaultRootElementNotSpecified((XMLDescriptor) descriptor); |
| } |
| } |
| |
| if ((field == null) && (referenceDescriptor != null) && (defaultRootElementString != null)) { |
| field = referenceDescriptor.buildField(defaultRootElementString); |
| } |
| |
| if ((field != null) && (referenceDescriptor != null)) { |
| ((XMLRecord) parentRow).setLeafElementType(referenceDescriptor.getDefaultRootElementType()); |
| XMLObjectBuilder objectBuilder = (XMLObjectBuilder) referenceDescriptor.getObjectBuilder(); |
| |
| XMLRecord child = (XMLRecord) objectBuilder.createRecordFor(attributeValue, (XMLField) field, (XMLRecord) parentRow, this); |
| child.setNamespaceResolver(((XMLRecord) parentRow).getNamespaceResolver()); |
| objectBuilder.buildIntoNestedRow(child, originalObject, attributeValue, session, referenceDescriptor, (XMLField) field, wasXMLRoot); |
| return child; |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean isXMLMapping() { |
| return true; |
| } |
| |
| @Override |
| public Vector getFields() { |
| return this.collectFields(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the mapping to use an instance of the specified container class |
| * to hold the target objects. |
| * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface. |
| * <p>jdk1.1.x: The container class must be a subclass of Vector. |
| */ |
| @Override |
| public void useCollectionClass(Class concreteContainerClass) { |
| this.setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteContainerClass)); |
| } |
| |
| @Override |
| public void useMapClass(Class concreteContainerClass, String methodName) { |
| throw DescriptorException.invalidMappingOperation(this, "useMapClass"); |
| } |
| |
| @Override |
| public void useMapClassName(String concreteContainerClass, String methodName) { |
| throw DescriptorException.invalidMappingOperation(this, "useMapClass"); |
| } |
| |
| @Override |
| public void useCollectionClassName(String concreteContainerClassName) { |
| this.setContainerPolicy(new CollectionContainerPolicy(concreteContainerClassName)); |
| } |
| |
| @Override |
| public void useListClassName(String concreteContainerClassName) { |
| this.setContainerPolicy(new ListContainerPolicy(concreteContainerClassName)); |
| } |
| |
| @Override |
| public void setUseXMLRoot(boolean useXMLRoot) { |
| this.useXMLRoot = useXMLRoot; |
| } |
| |
| @Override |
| public boolean usesXMLRoot() { |
| return useXMLRoot; |
| } |
| |
| @Override |
| public boolean isMixedContent() { |
| return mixedContent; |
| } |
| |
| @Override |
| public void setMixedContent(boolean mixed) { |
| mixedContent = mixed; |
| } |
| |
| |
| private boolean isUnmappedContent(Node node) { |
| if (!areOtherMappingInThisContext) { |
| return true; |
| } |
| XMLDescriptor parentDesc = (XMLDescriptor) this.getDescriptor(); |
| XMLField field = (XMLField) this.getField(); |
| Iterator<DatabaseMapping> mappings = parentDesc.getMappings().iterator(); |
| int mappingsInContext = 0; |
| while (mappings.hasNext()) { |
| DatabaseMapping next = mappings.next(); |
| if (!(next == this)) { |
| XMLField nextField = (XMLField) next.getField(); |
| XPathFragment frag = getFragmentToCompare(nextField, field); |
| if (frag != null) { |
| mappingsInContext++; |
| if (((node.getNodeType() == Node.TEXT_NODE) || (node.getNodeType() == Node.CDATA_SECTION_NODE)) && frag.nameIsText()) { |
| return false; |
| } |
| if (node.getNodeType() == Node.ELEMENT_NODE) { |
| String nodeNS = node.getNamespaceURI(); |
| String fragNS = frag.getNamespaceURI(); |
| String nodeLocalName = node.getLocalName(); |
| String fragLocalName = frag.getLocalName(); |
| if ((nodeNS == fragNS) || ((nodeNS != null) && (fragNS != null) && nodeNS.equals(fragNS))) { |
| if ((nodeLocalName == fragLocalName) || ((nodeLocalName != null) && (fragLocalName != null) && nodeLocalName.equals(fragLocalName))) { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| if (mappingsInContext == 0) { |
| this.areOtherMappingInThisContext = false; |
| } |
| } |
| return true; |
| } |
| |
| private XPathFragment getFragmentToCompare(XMLField field, XMLField context) { |
| if (field == null) { |
| return null; |
| } |
| if (context == null) { |
| return field.getXPathFragment(); |
| } |
| XPathFragment fieldFrag = field.getXPathFragment(); |
| XPathFragment contextFrag = context.getXPathFragment(); |
| |
| while ((fieldFrag != null) && (contextFrag != null)) { |
| if (fieldFrag.equals(contextFrag)) { |
| if (contextFrag.getNextFragment() == null) { |
| return fieldFrag.getNextFragment(); |
| } else { |
| contextFrag = contextFrag.getNextFragment(); |
| fieldFrag = fieldFrag.getNextFragment(); |
| } |
| } else { |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| private ArrayList getUnmappedChildNodes(NodeList nodes) { |
| ArrayList unmappedNodes = new ArrayList(); |
| int length = nodes.getLength(); |
| for (int i = 0; i < length; i++) { |
| Node next = nodes.item(i); |
| if (isUnmappedContent(next)) { |
| unmappedNodes.add(next); |
| } |
| } |
| return unmappedNodes; |
| } |
| |
| @Override |
| public boolean isCollectionMapping() { |
| return true; |
| } |
| |
| @Override |
| public void setConverter(XMLConverter conv) { |
| this.valueConverter = conv; |
| } |
| |
| public XMLConverter getConverter() { |
| return this.valueConverter; |
| } |
| |
| /** |
| * Setting this to true indicates that text nodes containing *only* whitespaces should still be |
| * added to the collection as strings for mixed content. |
| * |
| * If mixedContent is false, this setting has no effect. |
| */ |
| @Override |
| public boolean isWhitespacePreservedForMixedContent() { |
| return this.isWhitespacePreservedForMixedContent; |
| } |
| |
| @Override |
| public void setPreserveWhitespaceForMixedContent(boolean preserveWhitespace) { |
| this.isWhitespacePreservedForMixedContent = preserveWhitespace; |
| } |
| |
| /** |
| * Return true if the original container on the object should be used if |
| * present. If it is not present then the container policy will be used to |
| * create the container. |
| */ |
| @Override |
| public boolean getReuseContainer() { |
| return reuseContainer; |
| } |
| |
| /** |
| * Specify whether the original container on the object should be used if |
| * present. If it is not present then the container policy will be used to |
| * create the container. |
| */ |
| @Override |
| public void setReuseContainer(boolean reuseContainer) { |
| this.reuseContainer = reuseContainer; |
| } |
| |
| /** |
| * 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 defaultEmptyContainer; |
| } |
| |
| /** |
| * INTERNAL |
| * Indicate whether by default an empty container should be set on the |
| * field/property if the collection is not present in the XML document. |
| * @since EclipseLink 2.3.3 |
| */ |
| @Override |
| public void setDefaultEmptyContainer(boolean defaultEmptyContainer) { |
| this.defaultEmptyContainer = defaultEmptyContainer; |
| } |
| |
| @Override |
| public AbstractNullPolicy getWrapperNullPolicy() { |
| return this.wrapperNullPolicy; |
| } |
| |
| @Override |
| public void setWrapperNullPolicy(AbstractNullPolicy policy) { |
| this.wrapperNullPolicy = policy; |
| } |
| |
| /** |
| * INTERNAL |
| * @since EclipseLink 2.5.0 |
| */ |
| @Override |
| public Object convertObjectValueToDataValue(Object value, Session session, XMLMarshaller marshaller) { |
| if (null != valueConverter) { |
| return valueConverter.convertObjectValueToDataValue(value, session, marshaller); |
| } |
| return value; |
| } |
| |
| /** |
| * INTERNAL |
| * @since EclipseLink 2.5.0 |
| */ |
| @Override |
| public Object convertDataValueToObjectValue(Object fieldValue, Session session, XMLUnmarshaller unmarshaller) { |
| if (null != valueConverter) { |
| return valueConverter.convertDataValueToObjectValue(fieldValue, session, unmarshaller); |
| } |
| return fieldValue; |
| } |
| |
| } |