blob: da6f7408fdd1249ca6d86b646ffbaab41240235c [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.oxm.mappings.nullpolicy;
import org.eclipse.persistence.core.sessions.CoreSession;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.NullCapableValue;
import org.eclipse.persistence.internal.oxm.XPathEngine;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.XPathNode;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.record.AbstractMarshalRecord;
import org.eclipse.persistence.internal.oxm.record.MarshalRecord;
import org.eclipse.persistence.internal.oxm.record.XMLRecord;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
/**
* PUBLIC: <b>Description</b>: This node null policy allows for the handling of
* various representations of null in XML documents.<br>
* <p>
* <b>Null policies have 2 concrete implementations</b>:
* <ul>
* <li> NullPolicy (default implementation)</li>
* <li> IsSetNullPolicy (keyed off isSet() state of the node)</li>
* </ul>
*
* <table border="1">
* <caption>Unmarshalling</caption>
* <tr>
* <th id="c1">Unmarshal Flag</th>
* <th id="c2">Description</th>
* </tr>
* <tr>
* <td headers="c1"> isSetPerformedForAbsentNode </td>
* <td headers="c2">This umarshal flag represents whether a set is done for
* absent nodes only.</td>
* </tr>
* <tr>
* <td headers="c1"> isNullRepresentedByEmptyNode </td>
* <td headers="c2">If this unmarshal flag is false for empty nodes we set an
* empty Object for composite mappings, otherwise we set to null.</td>
* </tr>
* <tr>
* <td headers="c1" style="nowrap"> isNullRepresentedByXsiNil </td>
* <td headers="c2">If this unmarshal flag is false for xsi:nil nodes we ignore
* the xsi:nil attribute and treat as an empty node.<br>
* Otherwise we set to null.</td>
* </tr>
* </table>
*
* <table border="1">
* <caption>Marshalling</caption>
* <tr>
* <th id="c3">Marshal Enum</th>
* <th id="c4">XMLNullRepresentationType Description</th>
* </tr>
* <tr>
* <td headers="c3"> XSI_NIL </td>
* <td headers="c4">Nillable: Write out an xsi:nil="true" attribute.</td>
* </tr>
* <tr>
* <td headers="c3"> ABSENT_NODE(default) </td>
* <td headers="c4">Optional: Write out no node.</td>
* </tr>
* <tr>
* <td headers="c3" style="nowrap"> EMPTY_NODE </td>
* <td headers="c4">Required: Write out an empty {@literal <node/>} or node="" node.</td>
* </tr>
* </table> &nbsp;<b>Usage</b>:<br>
*
* @see org.eclipse.persistence.internal.oxm.NullCapableValue
* @since Oracle TopLink 11<i>g</i> Release 1 (11.1.1)
*/
public abstract class AbstractNullPolicy {
protected static final String TRUE = "true";
protected static final String COLON_W_SCHEMA_NIL_ATTRIBUTE = Constants.COLON + Constants.SCHEMA_NIL_ATTRIBUTE;
protected static final String XSI_NIL_ATTRIBUTE = Constants.SCHEMA_INSTANCE_PREFIX + COLON_W_SCHEMA_NIL_ATTRIBUTE;
/**
* This state flag determines how we unmarshal absent nodes. true =
* (default) Perform a set(null). false = Do not perform a set(null).
*/
protected boolean isSetPerformedForAbsentNode = true;
/**
* This state flag determines how we unmarshal empty nodes. true = Perform a
* set(null) or primitive type equivalent. false = (default) Perform a
* set(new Object()).
*/
protected boolean isNullRepresentedByEmptyNode = false;
/**
* This state flag determines how we unmarshal xsi:nil nodes. A set is
* performed in both cases. true = Perform a set(null) or primitive type
* equivalent.. false = (default) do nothing and treat as an empty node.
*/
protected boolean isNullRepresentedByXsiNil = false;
/**
* This state flag determines how we unmarshal xsi:nil nodes when there
* are other attributes (other than xsi:nil) present. If false, we ignore
* any attributes and treat the element as nil. If true, we inspect if
* some attributes are present and if yes, we process them.
*/
protected boolean ignoreAttributesForNil = true;
/**
* This enum instance determines what to write out during a marshal
* operation. We are defaulting to ABSENT_NODE
*/
protected XMLNullRepresentationType marshalNullRepresentation = XMLNullRepresentationType.ABSENT_NODE;
/**
* Default constructor.
*/
protected AbstractNullPolicy() {
}
/**
* Get the enum that determines what XML to write when a null value is encountered.
*/
public XMLNullRepresentationType getMarshalNullRepresentation() {
return marshalNullRepresentation;
}
/**
* Set the enum that determines what XML to write when a null value is encountered.
*/
public void setMarshalNullRepresentation(XMLNullRepresentationType anEnumInstance) {
marshalNullRepresentation = anEnumInstance;
}
/**
* INTERNAL:
* When using the SAX or DOM Platform, this method is responsible for
* marshalling null values for the XML Direct Mapping.
*
* @return true if this method caused any nodes to be marshaled, else false.
*/
public boolean directMarshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, //
Object object, CoreSession session, NamespaceResolver namespaceResolver) {
// Handle attributes - XSI_NIL, ABSENT_NODE have the same behavior
if (xPathFragment.isAttribute()) {
// Write out an empty attribute
if (marshalNullRepresentation == XMLNullRepresentationType.EMPTY_NODE) {
marshalRecord.emptyAttribute(xPathFragment, namespaceResolver);
return true;
} else {
// XSI_NIL attributes are invalid - and are ignored
// ABSENT_NODE - Write out nothing
return false;
}
} else {
// Nillable: write out xsi:nil="true" attribute in empty element
if (marshalNullRepresentation == XMLNullRepresentationType.XSI_NIL) {
marshalRecord.nilSimple(namespaceResolver);
return true;
} else {
// EMPTY_NODE - Write out empty element
if (marshalNullRepresentation == XMLNullRepresentationType.EMPTY_NODE) {
marshalRecord.emptySimple(namespaceResolver);
return true;
} else {
// ABSENT_NODE - Write out nothing
return false;
}
}
}
}
/**
* INTERNAL: When using the SAX Platform, this method is responsible for
* marshalling null values for the XML Composite Object Mapping.
*
* @return true if this method caused any nodes to be marshaled, else false.
*/
public boolean compositeObjectMarshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, //
Object object, CoreSession session, NamespaceResolver namespaceResolver) {
if (marshalNullRepresentation == XMLNullRepresentationType.ABSENT_NODE){
return false;
}
// Nillable
else if (marshalNullRepresentation == XMLNullRepresentationType.XSI_NIL) {
marshalRecord.nilComplex(xPathFragment, namespaceResolver);
return true;
} else if (marshalNullRepresentation == XMLNullRepresentationType.EMPTY_NODE) {
// Optional and Required
// This call is really only valid when using DOM - TBD false
// Write out empty element - we need to differentiate between
// object=null and object=new Object() with null fields and 0-numeric primitive values
// EMPTY_NODE - Write out empty element - Required
marshalRecord.emptyComplex(xPathFragment, namespaceResolver);
return true;
}
return false;
}
/**
* INTERNAL: When using the DOM Platform, this method is responsible for
* marshalling null values for the XML Composite Object Mapping.
*
* @return true if this method caused any objects to be marshaled, else false.
*/
public boolean compositeObjectMarshal(AbstractMarshalRecord record, Object object, Field field, CoreAbstractSession session) {
if (marshalNullRepresentation == XMLNullRepresentationType.XSI_NIL) {
record.put(field, XMLRecord.NIL);
return true;
} else {
// EMPTY_NODE - Write out empty element - Required
if (marshalNullRepresentation == XMLNullRepresentationType.EMPTY_NODE) {
Node element = XPathEngine.getInstance().createUnownedElement(record.getDOM(), field);
record.put(field, element);
return true;
} else {
// ABSENT_NODE - Write out nothing - Optional
return false;
}
}
}
/**
* INTERNAL: When using the SAX or DOM Platform during unmarshal operations.
* Use the attributes to determine if the element represents a null value.
*
* @return true if based on the attributes the corresponding element
* represents a null value, else false.
*/
public boolean valueIsNull(Attributes attributes) {
// Nillable
if (isNullRepresentedByXsiNil()) {
// Ignore any other attributes that are in addition to xsi:nil
if(null == attributes) {
return false;
}
return attributes.getValue(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_NIL_ATTRIBUTE) != null;
} else {
// EMPTY_NODE - Required
if (isNullRepresentedByEmptyNode() && (null == attributes || attributes.getLength() == 0)) {
return true;
}
}
return false;
}
/**
* INTERNAL: When using the DOM Platform during unmarshal operations.
* Use the element to determine if the element represents a null value.
*
* @return true if based on the element it represents a null value, else false.
*/
public boolean valueIsNull(Element element) {
// Check Nillable: Ignore any other attributes that are in addition to xsi:nil
if (null == element) {
return true;
} else {
if (isNullRepresentedByXsiNil() && element.hasAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_NIL_ATTRIBUTE)) {
return true;
} else {
// EMPTY_NODE - Required
// Verify no attributes and no child nodes on the DOM element
if (isNullRepresentedByEmptyNode() && !element.hasAttributes() && (element.getChildNodes().getLength() == 0)) {
return true;
} else {
return false;
}
}
}
}
/**
* INTERNAL: When using the SAX Platform this allows a NodeValue to be
* registered to receive events from the TreeObjectBuilder.
*/
public abstract void xPathNode(XPathNode xPathNode, NullCapableValue nullCapableValue);
/**
* @return the isSetPerformedForAbsentNode flag
*/
public boolean getIsSetPerformedForAbsentNode() {
return isSetPerformedForAbsentNode;
}
/**
*
*/
public boolean isNullRepresentedByEmptyNode() {
return isNullRepresentedByEmptyNode;
}
/**
*
*/
public void setNullRepresentedByEmptyNode(boolean bIsNullRepresentedByEmptyNode) {
isNullRepresentedByEmptyNode = bIsNullRepresentedByEmptyNode;
}
/**
*
*/
public boolean isNullRepresentedByXsiNil() {
return isNullRepresentedByXsiNil;
}
/**
*
*/
public void setNullRepresentedByXsiNil(boolean bIsNullRepresentedByXsiNil) {
isNullRepresentedByXsiNil = bIsNullRepresentedByXsiNil;
}
/**
*
*/
public boolean ignoreAttributesForNil() {
return ignoreAttributesForNil;
}
/**
*
*/
public void setIgnoreAttributesForNil(boolean ignoreAttributesForNil) {
this.ignoreAttributesForNil = ignoreAttributesForNil;
}
/**
* INTERNAL:
* Private function to process or create an entry in the NamespaceResolver for the xsi prefix.
* @return xsi prefix
*/
protected String processNamespaceResolverForXSIPrefix(NamespaceResolver namespaceResolver, MarshalRecord marshalRecord) {
String xsiPrefix;
if (null == namespaceResolver) {
// add new xsi entry into the properties map
xsiPrefix = Constants.SCHEMA_INSTANCE_PREFIX;
marshalRecord.namespaceDeclaration(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
} else {
// find an existing xsi entry in the map
xsiPrefix = namespaceResolver.resolveNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
if (null == xsiPrefix) {
xsiPrefix = namespaceResolver.generatePrefix(Constants.SCHEMA_INSTANCE_PREFIX);
marshalRecord.namespaceDeclaration(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
}
}
return xsiPrefix;
}
/**
* INTERNAL
*/
public void directMarshal(Field field, AbstractMarshalRecord record, Object object) {
Object fieldValue = null;
if(marshalNullRepresentation == XMLNullRepresentationType.EMPTY_NODE) {
fieldValue = Constants.EMPTY_STRING;
} else {
if(!field.getLastXPathFragment().isAttribute()) {
if(marshalNullRepresentation == XMLNullRepresentationType.XSI_NIL) {
fieldValue = XMLRecord.NIL;
}
}
}
record.put(field, fieldValue);
}
}