| /* |
| * 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.lang.reflect.Modifier; |
| |
| 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.ObjectBuilder; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.oxm.ConversionManager; |
| 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.XPathQName; |
| import org.eclipse.persistence.internal.oxm.mappings.CompositeObjectMapping; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.AttributeAccessor; |
| import org.eclipse.persistence.mappings.converters.Converter; |
| import org.eclipse.persistence.mappings.foundation.AbstractCompositeObjectMapping; |
| 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.XMLUnmarshaller; |
| import org.eclipse.persistence.oxm.mappings.converters.XMLConverter; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.NullPolicy; |
| import org.eclipse.persistence.oxm.record.DOMRecord; |
| import org.eclipse.persistence.oxm.record.XMLRecord; |
| import org.eclipse.persistence.platform.xml.XMLPlatformFactory; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.sessions.Session; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| /** |
| * <p>Composite object XML mappings represent a relationship between two classes. In XML, the "owned" |
| * class may be nested with the element tag representing the "owning" class. This mapping is, by |
| * definition, privately owned. |
| * |
| * <p><b>Composite object XML mappings can be used in the following scenarios</b>:<ul> |
| * <li> Mapping into the Parent Record </li> |
| * <li> Mapping to an Element </li> |
| * <li> Mapping to Different Elements by Element Name </li> |
| * <li> Mapping to Different Elements by Element Position </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 path and positional information; the last node in the XPath forms the local |
| * root node for the composite object. The XPath is specified on the mapping using the <code>setXPath</code> |
| * method. |
| * |
| * <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">.</td> |
| * <td headers="c2">Indicates "self".</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1">phone-number</td> |
| * <td headers="c2">The phone-number information is stored in the phone-number element.</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1" style="nowrap">contact-info/phone-number</td> |
| * <td headers="c2">The XPath statement may be used to specify any valid path.</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1">phone-number[2]</td> |
| * <td headers="c2">The XPath statement may contain positional information. In this case the phone-number |
| * information is stored in the second occurrence of the phone-number element.</td> |
| * </tr> |
| * </table> |
| * |
| * <p><b>Mapping into the Parent Record</b>: The composite object may be mapped into the parent |
| * record in a corresponding XML document. |
| * |
| * <!-- |
| * <?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="first-name" type="xsd:string"/> |
| * <xsd:element name="last-name" type="xsd:string"/> |
| * <xsd:element name="street" type="xsd:string"/> |
| * <xsd:element name="city" type="xsd:string"/> |
| * </xsd:sequence> |
| * </xsd:complexType> |
| * </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="first-name" type="xsd:string"/><br> |
| * <xsd:element name="last-name" type="xsd:string"/><br> |
| * <xsd:element name="street" type="xsd:string"/><br> |
| * <xsd:element name="city" type="xsd:string"/><br> |
| * </xsd:sequence><br> |
| * </xsd:complexType><br> |
| * </xsd:schema> |
| * </code> |
| * |
| * <p><em>Code Sample</em><br> |
| * <code> |
| * XMLCompositeObjectMapping addressMapping = new XMLCompositeObjectMapping();<br> |
| * addressMapping.setAttributeName("address");<br> |
| * addressMapping.setXPath(".");<br> |
| * addressMapping.setReferenceClass(Address.class);<br> |
| * </code> |
| * |
| * <p><b>Mapping to an Element</b>: The composite object may be mapped to an element in a corresponding |
| * XML document. |
| * |
| * <!-- |
| * <?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="first-name" type="xsd:string"/> |
| * <xsd:element name="last-name" type="xsd:string"/> |
| * <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:sequence> |
| * </xsd:complexType> |
| * </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="first-name" type="xsd:string"/><br> |
| * <xsd:element name="last-name" type="xsd:string"/><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:sequence><br> |
| * </xsd:complexType><br> |
| * </xsd:schema><br> |
| * </code> |
| * |
| * <p><em>Code Sample</em><br> |
| * <code> |
| * XMLCompositeObjectMapping addressMapping = new XMLCompositeObjectMapping();<br> |
| * addressMapping.setAttributeName("address");<br> |
| * addressMapping.setXPath("address");<br> |
| * addressMapping.setReferenceClass(Address.class);<br> |
| * </code> |
| * |
| * <p><b>More Information</b>: For more information about using the XML Composite Object 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 XMLCompositeObjectMapping extends AbstractCompositeObjectMapping implements XMLMapping, CompositeObjectMapping<AbstractSession, AttributeAccessor, ContainerPolicy, Converter, ClassDescriptor, DatabaseField, XMLMarshaller, Session, UnmarshalKeepAsElementPolicy, XMLUnmarshaller, XMLRecord>, XMLNillableMapping { |
| AbstractNullPolicy nullPolicy; |
| private XMLInverseReferenceMapping inverseReferenceMapping; |
| private UnmarshalKeepAsElementPolicy keepAsElementPolicy; |
| private boolean isWriteOnly; |
| |
| public XMLCompositeObjectMapping() { |
| super(); |
| // The default policy is NullPolicy |
| nullPolicy = new NullPolicy(); |
| } |
| |
| /** |
| * Gets the AttributeAccessor that is used to get and set the value of the |
| * container on the target object. |
| * @deprecated Replaced by getInverseReferenceMapping().getAttributeAccessor() |
| */ |
| @Deprecated |
| public AttributeAccessor getContainerAccessor() { |
| if (this.inverseReferenceMapping == null) { |
| return null; |
| } |
| return this.inverseReferenceMapping.getAttributeAccessor(); |
| } |
| |
| /** |
| * Sets the AttributeAccessor that is used to get and set the value of the |
| * container on the target object. |
| * |
| * @param anAttributeAccessor - the accessor to be used. |
| * @deprecated Replaced by getInverseReferenceMapping().setAttributeAccessor() |
| */ |
| @Deprecated |
| public void setContainerAccessor(AttributeAccessor anAttributeAccessor) { |
| if (this.inverseReferenceMapping == null) { |
| return; |
| } |
| this.inverseReferenceMapping.setAttributeAccessor(anAttributeAccessor); |
| } |
| |
| /** |
| * Sets the name of the backpointer attribute on the target object. Used to |
| * populate the backpointer. If the specified attribute doesn't exist on |
| * the reference class of this mapping, a DescriptorException will be thrown |
| * during initialize. |
| * |
| * @param attributeName - the name of the backpointer attribute to be populated |
| * @deprecated Replaced by getInverseReferenceMapping().setAttributeName() |
| */ |
| @Deprecated |
| public void setContainerAttributeName(String attributeName) { |
| if (this.inverseReferenceMapping == null) { |
| return; |
| } |
| this.inverseReferenceMapping.setAttributeName(attributeName); |
| } |
| |
| /** |
| * Gets the name of the backpointer attribute on the target object. Used to |
| * populate the backpointer. |
| * @deprecated Replaced by getInverseReferenceMapping().getAttributeName() |
| */ |
| @Deprecated |
| public String getContainerAttributeName() { |
| if (this.inverseReferenceMapping == null) { |
| return null; |
| } |
| return this.inverseReferenceMapping.getAttributeName(); |
| } |
| |
| /** |
| * Sets the method name to be used when accessing the value of the back pointer |
| * on the target object of this mapping. If the specified method doesn't exist |
| * on the reference class of this mapping, a DescriptorException will be thrown |
| * during initialize. |
| * |
| * @param methodName - the getter method to be used. |
| * @deprecated Replaced by getInverseReferenceMapping().setGetMethodName() |
| */ |
| @Deprecated |
| public void setContainerGetMethodName(String methodName) { |
| if (this.inverseReferenceMapping == null) { |
| return; |
| } |
| this.inverseReferenceMapping.setGetMethodName(methodName); |
| } |
| |
| /** |
| * Sets the name of the method to be used when setting the value of the back pointer |
| * on the target object of this mapping. If the specified method doesn't exist |
| * on the reference class of this mapping, a DescriptorException will be thrown |
| * during initialize. |
| * |
| * @param methodName - the setter method to be used. |
| * @deprecated Replaced by getInverseReferenceMapping().setSetMethodName() |
| */ |
| @Deprecated |
| public void setContainerSetMethodName(String methodName) { |
| if (this.inverseReferenceMapping == null) { |
| return; |
| } |
| this.inverseReferenceMapping.setSetMethodName(methodName); |
| } |
| |
| /** |
| * Gets the name of the method to be used when accessing the value of the |
| * back pointer on the target object of this mapping. |
| * @deprecated Replaced by getInverseReferenceMapping().getGetMethodName() |
| */ |
| @Deprecated |
| public String getContainerGetMethodName() { |
| if (this.inverseReferenceMapping == null) { |
| return null; |
| } |
| return this.inverseReferenceMapping.getGetMethodName(); |
| } |
| |
| /** |
| * Gets the name of the method to be used when setting the value of the |
| * back pointer on the target object of this mapping. |
| * @deprecated Replaced by getInverseReferenceMapping().getSetMethodName() |
| */ |
| @Deprecated |
| public String getContainerSetMethodName() { |
| if (this.inverseReferenceMapping == null) { |
| return null; |
| } |
| return this.inverseReferenceMapping.getSetMethodName(); |
| } |
| |
| |
| @Override |
| public void convertClassNamesToClasses(ClassLoader classLoader){ |
| if(XMLConstants.UNKNOWN_OR_TRANSIENT_CLASS.equals(referenceClassName)){ |
| return; |
| } |
| super.convertClassNamesToClasses(classLoader); |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * The mapping is initialized with the given session. This mapping is fully initialized |
| * after this. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| //modified so that reference class on composite mappings is no longer mandatory |
| String referenceClassName = getReferenceClassName(); |
| if ((this.referenceClass == null) && (referenceClassName != null)) { |
| if (!referenceClassName.equals(XMLConstants.UNKNOWN_OR_TRANSIENT_CLASS)) { |
| setReferenceClass(session.getDatasourcePlatform().getConversionManager().convertClassNameToClass(referenceClassName)); |
| } |
| } |
| initializeReferenceDescriptorAndField(session); |
| |
| if(null != getContainerAccessor()) { |
| getContainerAccessor().initializeAttributes(this.referenceClass); |
| } |
| |
| } |
| |
| protected void initializeReferenceDescriptorAndField(AbstractSession session){ |
| if (this.referenceClass != null) { |
| super.initialize(session); |
| } else { |
| //below should be the same as AbstractCompositeObjectMapping.initialize |
| if (this.field == null) { |
| throw DescriptorException.fieldNameNotSetInMapping(this); |
| } |
| |
| setField(getDescriptor().buildField(this.field)); |
| setFields(collectFields()); |
| // initialize the converter - if necessary |
| if (hasConverter()) { |
| getConverter().initialize(this, session); |
| } |
| } |
| } |
| /** |
| * Set the AbstractNullPolicy on the mapping<br> |
| * The default policy is NullPolicy.<br> |
| * |
| */ |
| @Override |
| public void setNullPolicy(AbstractNullPolicy aNullPolicy) { |
| nullPolicy = aNullPolicy; |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the AbstractNullPolicy from the Mapping.<br> |
| * The default policy is NullPolicy.<br> |
| */ |
| @Override |
| public AbstractNullPolicy getNullPolicy() { |
| return nullPolicy; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isXMLMapping() { |
| return true; |
| } |
| |
| /** |
| * Get the XPath String |
| * @return String the XPath String associated with this Mapping |
| */ |
| public String getXPath() { |
| return getField().getName(); |
| } |
| |
| /** |
| * Set the Mapping field name attribute to the given XPath String |
| * @param xpathString String |
| */ |
| @Override |
| public void setXPath(String xpathString) { |
| this.setField(new XMLField(xpathString)); |
| } |
| |
| @Override |
| protected Object buildCompositeRow(Object attributeValue, AbstractSession session, AbstractRecord databaseRow, WriteType writeType) { |
| XMLDescriptor xmlReferenceDescriptor = null; |
| try{ |
| xmlReferenceDescriptor = (XMLDescriptor) getReferenceDescriptor(attributeValue, session); |
| }catch(Exception e){ |
| //do nothing |
| } |
| |
| XMLField xmlFld = (XMLField) getField(); |
| if (xmlFld.hasLastXPathFragment() && xmlFld.getLastXPathFragment().hasLeafElementType()) { |
| XMLRecord xmlRec = (XMLRecord) databaseRow; |
| xmlRec.setLeafElementType(xmlFld.getLastXPathFragment().getLeafElementType()); |
| } |
| XMLRecord parent = (XMLRecord) databaseRow; |
| |
| if (xmlReferenceDescriptor != null) { |
| return buildCompositeRowForDescriptor(xmlReferenceDescriptor, attributeValue, session, parent, writeType); |
| |
| } else { |
| if (attributeValue instanceof Element && getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) { |
| return new DOMRecord((Element) attributeValue); |
| }else{ |
| Node newNode = XPathEngine.getInstance().create((XMLField)getField(), parent.getDOM(), attributeValue, session); |
| DOMRecord newRow = new DOMRecord(newNode); |
| return newRow; |
| } |
| } |
| } |
| |
| protected AbstractRecord buildCompositeRowForDescriptor(ClassDescriptor classDesc, Object attributeValue, AbstractSession session, XMLRecord parentRow, WriteType writeType) { |
| XMLObjectBuilder objectBuilder = (XMLObjectBuilder) classDesc.getObjectBuilder(); |
| |
| XMLRecord child = (XMLRecord) objectBuilder.createRecordFor(attributeValue, (XMLField) getField(), parentRow, this); |
| child.setNamespaceResolver(parentRow.getNamespaceResolver()); |
| child.setSession(session); |
| objectBuilder.buildIntoNestedRow(child, attributeValue, session, (XMLDescriptor)getReferenceDescriptor(), (XMLField) getField()); |
| return child; |
| } |
| |
| @Override |
| protected Object buildCompositeObject(ObjectBuilder objectBuilder, AbstractRecord nestedRow, ObjectBuildingQuery query, CacheKey parentCacheKey, JoinedAttributeManager joinManager, AbstractSession targetSession) { |
| return objectBuilder.buildObject(query, nestedRow, joinManager); |
| } |
| |
| @Override |
| public Object readFromRowIntoObject(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object targetObject, CacheKey parentCacheKey, ObjectBuildingQuery sourceQuery, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException { |
| |
| Object fieldValue = databaseRow.getIndicatingNoEntry(getField()); |
| // 20071002: noEntry ineffective as a check for an absent node, empty nodes are DOMRecords, absent nodes are null) |
| // if(fieldValue == AbstractRecord.noEntry && !getNullPolicy().getIsSetPerformedForAbsentNode()) { |
| // Do not perform a set for an absent node |
| // return null; |
| // } else { |
| // Check for absent nodes based on policy flag |
| if ((null == fieldValue) || fieldValue instanceof String) { |
| if (getNullPolicy().getIsSetPerformedForAbsentNode()) { |
| setAttributeValueInObject(targetObject, null); |
| } else { |
| return null; |
| } |
| return null; |
| } |
| // } |
| |
| // Empty or xsi:nil nodes (non-absent) will arrive here along with populated nodes |
| XMLRecord nestedRow = (XMLRecord) this.getDescriptor().buildNestedRowFromFieldValue(fieldValue); |
| // Check the policy to see if this DOM empty/xsi:nil or filled record represents null |
| if (getNullPolicy().valueIsNull((Element) nestedRow.getDOM())) { |
| setAttributeValueInObject(targetObject, null); |
| return null; |
| } |
| Object attributeValue = valueFromRow(fieldValue, nestedRow, joinManager, sourceQuery, executionSession, isTargetProtected); |
| setAttributeValueInObject(targetObject, attributeValue); |
| if(null != getContainerAccessor()) { |
| if(this.inverseReferenceMapping.getContainerPolicy() == null) { |
| getContainerAccessor().setAttributeValueInObject(attributeValue, targetObject); |
| } else { |
| Object backpointerContainer = getContainerAccessor().getAttributeValueFromObject(attributeValue); |
| if(backpointerContainer == null) { |
| backpointerContainer = this.inverseReferenceMapping.getContainerPolicy().containerInstance(); |
| getContainerAccessor().setAttributeValueInObject(attributeValue, backpointerContainer); |
| } |
| this.inverseReferenceMapping.getContainerPolicy().addInto(targetObject, backpointerContainer, executionSession); |
| } |
| } |
| return attributeValue; |
| } |
| |
| public Object valueFromRow(Object fieldValue, XMLRecord nestedRow, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, AbstractSession executionSession, boolean isTargetProtected) throws DatabaseException { |
| // pretty sure we can ignore inheritance here: |
| Object toReturn = null; |
| // Use local descriptor - not the instance variable on DatabaseMapping |
| ClassDescriptor aDescriptor = getReferenceDescriptor((DOMRecord) nestedRow); |
| |
| if (aDescriptor == null) { |
| if ((getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) || (getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT)) { |
| XMLPlatformFactory.getInstance().getXMLPlatform().namespaceQualifyFragment((Element) nestedRow.getDOM()); |
| toReturn = nestedRow.getDOM(); |
| toReturn = convertDataValueToObjectValue(toReturn, executionSession, nestedRow.getUnmarshaller()); |
| |
| //try simple case |
| toReturn = convertToSimpleTypeIfPresent(toReturn, nestedRow, executionSession); |
| return toReturn; |
| } else { |
| NodeList children = nestedRow.getDOM().getChildNodes(); |
| for(int i=0, childrenLength=children.getLength(); i<childrenLength ; i++){ |
| Node nextNode = children.item(i); |
| if(nextNode.getNodeType() == Node.ELEMENT_NODE){ |
| //complex child |
| String type = ((Element) nestedRow.getDOM()).getAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XMLConstants.SCHEMA_TYPE_ATTRIBUTE); |
| if(type != null && type.length() > 0) { |
| throw XMLMarshalException.unknownXsiTypeValue(type, (CompositeObjectMapping) this); |
| } else { |
| throw XMLMarshalException.noDescriptorFound((CompositeObjectMapping) this); |
| } |
| } |
| } |
| |
| //simple case |
| toReturn = convertToSimpleTypeIfPresent(toReturn, nestedRow, executionSession); |
| } |
| } else { |
| if (aDescriptor.hasInheritance()) { |
| Class classValue = aDescriptor.getInheritancePolicy().classFromRow(nestedRow, executionSession); |
| if (classValue == null) { |
| // no xsi:type attribute - look for type indicator on the field |
| QName leafElementType = ((XMLField) getField()).getLeafElementType(); |
| if (leafElementType != null) { |
| XPathQName leafElementXPathQName = new XPathQName(leafElementType, nestedRow.isNamespaceAware()); |
| Object indicator = aDescriptor.getInheritancePolicy().getClassIndicatorMapping().get(leafElementXPathQName); |
| if(indicator != null) { |
| classValue = (Class) indicator; |
| } |
| } |
| } |
| if (classValue != null) { |
| aDescriptor = this.getReferenceDescriptor(classValue, executionSession); |
| } else { |
| // since there is no xsi:type attribute or leaf element type set, |
| // use the reference descriptor - make sure it is non-abstract |
| if (Modifier.isAbstract(aDescriptor.getJavaClass().getModifiers())) { |
| // throw an exception |
| throw DescriptorException.missingClassIndicatorField((org.eclipse.persistence.internal.oxm.record.XMLRecord) nestedRow, aDescriptor.getInheritancePolicy().getDescriptor()); |
| } |
| } |
| } |
| ObjectBuilder objectBuilder = aDescriptor.getObjectBuilder(); |
| toReturn = buildCompositeObject(objectBuilder, nestedRow, sourceQuery, null, joinManager, executionSession); |
| } |
| |
| if (getConverter() != null) { |
| if (getConverter() instanceof XMLConverter) { |
| toReturn = ((XMLConverter) getConverter()).convertDataValueToObjectValue(toReturn, executionSession, nestedRow.getUnmarshaller()); |
| } else { |
| toReturn = getConverter().convertDataValueToObjectValue(toReturn, executionSession); |
| } |
| } |
| return toReturn; |
| } |
| |
| private Object convertToSimpleTypeIfPresent(Object toReturn, XMLRecord nestedRow, AbstractSession executionSession){ |
| Node textchild = nestedRow.getDOM().getFirstChild(); |
| String stringValue = null; |
| if ((textchild != null) && (textchild.getNodeType() == Node.TEXT_NODE)) { |
| stringValue = textchild.getNodeValue(); |
| if(stringValue != null && getKeepAsElementPolicy() != UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT && getKeepAsElementPolicy()!=UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT){ |
| toReturn = stringValue; |
| } |
| } |
| if ((stringValue == null) || stringValue.length() == 0) { |
| return toReturn; |
| } |
| |
| String type = ((Element) nestedRow.getDOM()).getAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XMLConstants.SCHEMA_TYPE_ATTRIBUTE); |
| |
| if ((null != type) && type.length() > 0) { |
| XPathFragment typeFragment = new XPathFragment(type); |
| String namespaceURI = nestedRow.resolveNamespacePrefix(typeFragment.getPrefix()); |
| |
| QName schemaTypeQName = new QName(namespaceURI, typeFragment.getLocalName()); |
| ConversionManager conversionManager = (ConversionManager) executionSession.getDatasourcePlatform().getConversionManager(); |
| Class<Object> theClass = conversionManager.javaType(schemaTypeQName); |
| if (theClass != null) { |
| toReturn = conversionManager.convertObject(stringValue, theClass, schemaTypeQName); |
| } |
| } |
| return toReturn; |
| } |
| |
| @Override |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession executionSession, boolean isTargetProtected, Boolean[] wasCacheUsed) throws DatabaseException { |
| Object fieldValue = row.get(this.getField()); |
| // BUG#2667762 there could be whitespace in the row instead of null |
| if ((fieldValue == null) || (fieldValue instanceof String)) { |
| return null; |
| } |
| |
| XMLRecord nestedRow = (XMLRecord) this.getDescriptor().buildNestedRowFromFieldValue(fieldValue); |
| // Check the policy to see if this DOM record represents null |
| if (getNullPolicy().valueIsNull((Element) nestedRow.getDOM())) { |
| return null; |
| } |
| return valueFromRow(fieldValue, nestedRow, joinManager, sourceQuery, executionSession, isTargetProtected); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord databaseRow, AbstractSession session, WriteType writeType) throws DescriptorException { |
| if (this.isReadOnly()) { |
| return; |
| } |
| Object attributeValue = this.getAttributeValueFromObject(object); |
| writeSingleValue(attributeValue, object, (XMLRecord) databaseRow, session); |
| } |
| |
| @Override |
| public void writeSingleValue(Object value, Object parent, XMLRecord record, AbstractSession session) { |
| Object attributeValue = convertObjectValueToDataValue(value, session, record.getMarshaller()); |
| // handle "self" xpath |
| if (((XMLField) getField()).isSelfField()) { |
| if (((keepAsElementPolicy == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) || (keepAsElementPolicy == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT)) && attributeValue instanceof org.w3c.dom.Node) { |
| //write out node |
| org.w3c.dom.Document doc = record.getDocument(); |
| Node root = record.getDOM(); |
| NodeList children = ((Node) attributeValue).getChildNodes(); |
| for(int i=0,childrenLength=children.getLength();i<childrenLength; i++){ |
| Node importedCopy = doc.importNode(children.item(i), true); |
| root.appendChild(importedCopy); |
| } |
| }else{ |
| ClassDescriptor desc; |
| if(null == attributeValue) { |
| desc = this.getReferenceDescriptor(); |
| } else { |
| desc = this.getReferenceDescriptor(attributeValue.getClass(), session); |
| } |
| if(desc != null){ |
| XMLObjectBuilder objectBuilder = (XMLObjectBuilder)desc.getObjectBuilder(); |
| objectBuilder.buildIntoNestedRow(record, attributeValue, session, (XMLDescriptor)getReferenceDescriptor(), (XMLField) getField()); |
| }else{ |
| //simple case |
| record.put(this.getField(), attributeValue); |
| } |
| } |
| } else { |
| Object fieldValue = null; |
| if (attributeValue != null) { |
| fieldValue = buildCompositeRow(attributeValue, session, record, WriteType.UNDEFINED); |
| } else if (getNullPolicy().compositeObjectMarshal(record, parent, (XMLField) getField(), session)) { |
| // If the null policy marshal method returns true (i.e. marshalled something) |
| // don't add/put null in the record |
| return; |
| } |
| // handle document preservation |
| record.put(this.getField(), fieldValue); |
| } |
| } |
| |
| public void configureNestedRow(AbstractRecord parent, AbstractRecord child) { |
| XMLRecord parentRecord = (XMLRecord) parent; |
| XMLRecord childRecord = (XMLRecord) child; |
| |
| childRecord.setUnmarshaller(parentRecord.getUnmarshaller()); |
| childRecord.setOwningObject(parentRecord.getCurrentObject()); |
| } |
| |
| public ClassDescriptor getReferenceDescriptor(DOMRecord xmlRecord) { |
| ClassDescriptor returnDescriptor = referenceDescriptor; |
| |
| if (returnDescriptor == null) { |
| // Try to find a descriptor based on the schema type |
| String type = ((Element) xmlRecord.getDOM()).getAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XMLConstants.SCHEMA_TYPE_ATTRIBUTE); |
| |
| if ((null != type) && type.length() > 0) { |
| XPathFragment typeFragment = new XPathFragment(type); |
| String namespaceURI = xmlRecord.resolveNamespacePrefix(typeFragment.getPrefix()); |
| typeFragment.setNamespaceURI(namespaceURI); |
| |
| returnDescriptor = xmlRecord.getUnmarshaller().getXMLContext().getDescriptorByGlobalType(typeFragment); |
| |
| } else { |
| //try leaf element type |
| QName leafType = ((XMLField) getField()).getLastXPathFragment().getLeafElementType(); |
| if (leafType != null) { |
| XPathFragment frag = new XPathFragment(); |
| String xpath = leafType.getLocalPart(); |
| String uri = leafType.getNamespaceURI(); |
| if ((uri != null) && uri.length() > 0) { |
| frag.setNamespaceURI(uri); |
| String prefix = ((XMLDescriptor) getDescriptor()).getNonNullNamespaceResolver().resolveNamespaceURI(uri); |
| if ((prefix != null) && prefix.length() > 0) { |
| xpath = prefix + XMLConstants.COLON + xpath; |
| } |
| } |
| frag.setXPath(xpath); |
| |
| returnDescriptor = xmlRecord.getUnmarshaller().getXMLContext().getDescriptorByGlobalType(frag); |
| } |
| } |
| } |
| return returnDescriptor; |
| |
| } |
| |
| @Override |
| protected ClassDescriptor getReferenceDescriptor(Class theClass, AbstractSession session) { |
| if ((getReferenceDescriptor() != null) && getReferenceDescriptor().getJavaClass().equals(theClass)) { |
| return getReferenceDescriptor(); |
| } |
| |
| ClassDescriptor subDescriptor = session.getDescriptor(theClass); |
| if (subDescriptor == null && getKeepAsElementPolicy() != UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) { |
| throw DescriptorException.noSubClassMatch(theClass, this); |
| } else { |
| return subDescriptor; |
| } |
| } |
| |
| @Override |
| public UnmarshalKeepAsElementPolicy getKeepAsElementPolicy() { |
| return keepAsElementPolicy; |
| } |
| |
| @Override |
| public void setKeepAsElementPolicy(UnmarshalKeepAsElementPolicy keepAsElementPolicy) { |
| this.keepAsElementPolicy = keepAsElementPolicy; |
| } |
| |
| protected XMLDescriptor getDescriptor(XMLRecord xmlRecord, AbstractSession session, QName rootQName) throws XMLMarshalException { |
| if (rootQName == null) { |
| rootQName = new QName(xmlRecord.getNamespaceURI(), xmlRecord.getLocalName()); |
| } |
| XMLContext xmlContext = xmlRecord.getUnmarshaller().getXMLContext(); |
| XMLDescriptor xmlDescriptor = xmlContext.getDescriptor(rootQName); |
| if (null == xmlDescriptor) { |
| if (!((getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) || (getKeepAsElementPolicy() == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT))) { |
| throw XMLMarshalException.noDescriptorWithMatchingRootElement(xmlRecord.getLocalName()); |
| } |
| } |
| return xmlDescriptor; |
| } |
| |
| @Override |
| public void setIsWriteOnly(boolean b) { |
| isWriteOnly = b; |
| } |
| |
| @Override |
| public boolean isWriteOnly() { |
| return isWriteOnly; |
| } |
| |
| @Override |
| public void preInitialize(AbstractSession session) throws DescriptorException { |
| getAttributeAccessor().setIsWriteOnly(this.isWriteOnly()); |
| getAttributeAccessor().setIsReadOnly(this.isReadOnly()); |
| super.preInitialize(session); |
| } |
| |
| @Override |
| public void setAttributeValueInObject(Object object, Object value) throws DescriptorException { |
| if(isWriteOnly()) { |
| return; |
| } |
| super.setAttributeValueInObject(object, value); |
| } |
| |
| @Override |
| public XMLInverseReferenceMapping getInverseReferenceMapping() { |
| return inverseReferenceMapping; |
| } |
| |
| void setInverseReferenceMapping(XMLInverseReferenceMapping inverseReferenceMapping) { |
| this.inverseReferenceMapping = inverseReferenceMapping; |
| } |
| |
| /** |
| * INTERNAL |
| * @since EclipseLink 2.5.0 |
| */ |
| @Override |
| public Object convertObjectValueToDataValue(Object value, Session session, XMLMarshaller marshaller) { |
| if (hasConverter()) { |
| if (converter instanceof XMLConverter) { |
| return ((XMLConverter)converter).convertObjectValueToDataValue(value, session, marshaller); |
| } else { |
| return converter.convertObjectValueToDataValue(value, session); |
| } |
| } |
| return value; |
| } |
| |
| /** |
| * INTERNAL |
| * @since EclipseLink 2.5.0 |
| */ |
| @Override |
| public Object convertDataValueToObjectValue(Object fieldValue, Session session, XMLUnmarshaller unmarshaller) { |
| if (hasConverter()) { |
| if (converter instanceof XMLConverter) { |
| return ((XMLConverter)converter).convertDataValueToObjectValue(fieldValue, session, unmarshaller); |
| } else { |
| return converter.convertDataValueToObjectValue(fieldValue, session); |
| } |
| } |
| return fieldValue; |
| } |
| |
| } |