blob: caf8fa309c918cf93c3de6d00c9c6c942af581f2 [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;
import java.util.Collection;
import java.util.Properties;
import javax.xml.transform.Result;
import javax.xml.transform.sax.SAXResult;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.FragmentContentHandler;
import org.eclipse.persistence.internal.oxm.JsonTypeConfiguration;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.TreeObjectBuilder;
import org.eclipse.persistence.internal.oxm.XMLObjectBuilder;
import org.eclipse.persistence.internal.oxm.XPathEngine;
import org.eclipse.persistence.internal.oxm.record.ExtendedResult;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.oxm.documentpreservation.DocumentPreservationPolicy;
import org.eclipse.persistence.oxm.record.MarshalRecord;
import org.eclipse.persistence.oxm.record.NodeRecord;
import org.eclipse.persistence.oxm.record.XMLRecord;
import org.eclipse.persistence.platform.xml.XMLPlatformException;
import org.eclipse.persistence.sessions.DatabaseSession;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* <p>Class used to marshal object to XML.
*
* <p>Create an XMLMarshaller from an XMLContext.<br>
* <em>Code Sample</em><br>
* <code>
* XMLContext context = new XMLContext("mySessionName");<br>
* XMLMarshaller marshaller = context.createMarshaller();<br>
* </code>
*
* <p>Objects can be marshalled to the following outputs:<ul>
* <li>java.io.OutputStream</li>
* <li>java.io.Writer</li>
* <li>javax.xml.transform.Result</li>
* <li>org.w3c.dom.Node</li>
* <li>org.xml.sax.ContentHandler</li>
* </ul>
*
* <p>Objects that can be marshalled are those which are mapped in the
* TopLink project associated with the XMLContext, and which are mapped
* to an XMLDescriptor that has a default root element specified.
*
* @see org.eclipse.persistence.oxm.XMLContext
*/
public class XMLMarshaller extends org.eclipse.persistence.internal.oxm.XMLMarshaller<AbstractSession, CharacterEscapeHandler, XMLContext, XMLDescriptor, XMLMarshalListener, MediaType, NamespacePrefixMapper, TreeObjectBuilder, DatabaseSession> implements Cloneable {
private Object marshalAttributeGroup;
private JsonTypeConfiguration jsonTypeConfiguration;
/**
* Create a new XMLMarshaller based on the specified session
* @param xmlContext the XML content
*/
public XMLMarshaller(XMLContext xmlContext) {
super(xmlContext);
setMediaType(MediaType.APPLICATION_XML);
}
/**
* Copy constructor
*/
protected XMLMarshaller(XMLMarshaller xmlMarshaller) {
super(xmlMarshaller);
}
/**
* Return the instance of XMLContext that was used to create this instance
* of XMLMarshaller.
*/
public XMLContext getXMLContext() {
return getContext();
}
/**
* Set the XMLContext used by this instance of XMLMarshaller.
*/
public void setXMLContext(XMLContext value) {
context = value;
}
/**
* Return a properties object for a given instance of the
* XMLMarshaller.
*
*/
public Properties getProperties() {
if(null == marshalProperties) {
marshalProperties = new Properties();
}
return marshalProperties;
}
public void setXMLMarshalHandler(XMLMarshalListener marshalListener) {
setMarshalListener(marshalListener);
}
/**
* PUBLIC:
* Convert the given object to XML and update the given result with that XML Document
* @param object the object to marshal
* @param result the result to marshal the object to
* @throws XMLMarshalException if an error occurred during marshalling
*/
@Override
public void marshal(Object object, Result result) throws XMLMarshalException {
if ((object == null) || (result == null)) {
throw XMLMarshalException.nullArgumentException();
}
XMLDescriptor xmlDescriptor = null;
AbstractSession session = null;
boolean isXMLRoot = (object instanceof Root);
if(isXMLRoot){
try{
session = context.getSession(((Root)object).getObject());
if(session != null){
xmlDescriptor = getDescriptor(((Root)object).getObject(), session);
}
}catch (XMLMarshalException marshalException) {
if (!isSimpleXMLRoot((Root) object)) {
throw marshalException;
}
}
}else{
Class objectClass = object.getClass();
if(result instanceof ExtendedResult){
MarshalRecord writerRecord = ((ExtendedResult)result).createRecord();
if(object instanceof Collection){
writerRecord.startCollection();
String valueWrapper;
for(Object o : (Collection) object) {
if (o == null) {
valueWrapper = this.getValueWrapper();
if (isApplicationJSON()) {
this.setValueWrapper("");
writerRecord.setMarshaller(this);
}
writerRecord.nilSimple(null);
this.setValueWrapper(valueWrapper);
} else {
if (isApplicationJSON() && o != null && (o.getClass() == CoreClassConstants.STRING || o.getClass() == CoreClassConstants.BOOLEAN || CoreClassConstants.NUMBER.isAssignableFrom(o.getClass()))) {
if (writerRecord.getSession() == null) {
writerRecord.setSession((CoreAbstractSession) this.getContext().getSession());
}
valueWrapper = this.getValueWrapper();
this.setValueWrapper("");
writerRecord.setMarshaller(this);
writerRecord.characters(null, o, null, false);
this.setValueWrapper(valueWrapper);
} else {
marshal(o, writerRecord);
}
}
//Origin marshal(o, writerRecord);
}
writerRecord.endCollection();
return;
}else{
marshal(object, writerRecord);
return;
}
}
if(xmlDescriptor == null){
session = context.getSession(objectClass);
xmlDescriptor = getDescriptor(objectClass, session);
}
}
//if this is a simple xml root, the session and descriptor will be null
if (session == null || !context.getDocumentPreservationPolicy(session).shouldPreserveDocument()) {
super.marshal(object, result);
return;
}
try {
Document document = objectToXML(object, xmlDescriptor, isXMLRoot);
if ((result instanceof SAXResult) && (isFragment())) {
FragmentContentHandler fragmentHandler = new FragmentContentHandler(((SAXResult) result).getHandler());
getTransformer(); // Ensure transformer is initialized
if (isXMLRoot) {
String oldEncoding = transformer.getEncoding();
String oldVersion = transformer.getVersion();
if (((Root) object).getEncoding() != null) {
transformer.setEncoding(((Root) object).getEncoding());
}
if (((Root) object).getXMLVersion() != null) {
transformer.setVersion(((Root) object).getXMLVersion());
}
transformer.transform(document, fragmentHandler);
transformer.setEncoding(oldEncoding);
transformer.setVersion(oldVersion);
} else {
transformer.transform(document, fragmentHandler);
}
} else {
if (result.getClass().equals(staxResultClass)) {
try {
String namespace = null;
String localName = null;
if(isXMLRoot){
Root xmlRootObject = (Root)object;
if(xmlRootObject.getObject() != null && xmlRootObject.getObject() instanceof Node){
namespace = ((Root)object).getNamespaceURI();
localName = ((Root)object).getLocalName();
}
}
Object xmlStreamWriter = PrivilegedAccessHelper.invokeMethod(staxResultGetStreamWriterMethod, result);
if (xmlStreamWriter != null) {
Object domtostax = PrivilegedAccessHelper.newInstanceFromClass(domToStreamWriterClass);
PrivilegedAccessHelper.invokeMethod(writeToStreamMethod, domtostax, new Object[]{document,namespace, localName,xmlStreamWriter});
return;
} else {
Object xmlEventWriter = PrivilegedAccessHelper.invokeMethod(staxResultGetEventWriterMethod, result);
if(xmlEventWriter != null) {
Object domToEventWriter = PrivilegedAccessHelper.newInstanceFromClass(domToEventWriterClass);
PrivilegedAccessHelper.invokeMethod(writeToEventWriterMethod, domToEventWriter, new Object[]{document, namespace, localName, xmlEventWriter});
return;
}
}
} catch(Exception e) {
throw XMLMarshalException.marshalException(e);
}
}
getTransformer(); // Ensure transformer is initialized
if (isXMLRoot) {
String oldEncoding = transformer.getEncoding();
String oldVersion = transformer.getVersion();
if (((Root) object).getEncoding() != null) {
transformer.setEncoding(((Root) object).getEncoding());
}
if (((Root) object).getXMLVersion() != null) {
transformer.setVersion(((Root) object).getXMLVersion());
}
transformer.transform(document, result);
transformer.setEncoding(oldEncoding);
transformer.setVersion(oldVersion);
} else {
transformer.transform(document, result);
}
}
} catch (XMLPlatformException e) {
throw XMLMarshalException.marshalException(e);
}
}
@Override
protected Node getNode(Object object, Node parentNode, AbstractSession session, XMLDescriptor xmlDescriptor, boolean isXMLRoot) {
Node node = super.getNode(object, parentNode, session, xmlDescriptor, isXMLRoot);
if(null != node) {
return node;
}
if(null != session && context.getDocumentPreservationPolicy(session).shouldPreserveDocument()) {
return objectToXMLNode(object, parentNode, session, xmlDescriptor, isXMLRoot);
}
return null;
}
@Override
public XMLMarshalListener getMarshalListener() {
return super.getMarshalListener();
}
@Override
public void setMarshalListener(XMLMarshalListener listener) {
super.setMarshalListener(listener);
}
/**
* Convert the given object to XML and update the given marshal record with
* that XML Document.
* @param object the object to marshal
* @param marshalRecord the marshalRecord to marshal the object to
*/
protected void marshal(Object object, AbstractSession session, MarshalRecord marshalRecord) {
boolean isXMLRoot = (object instanceof Root);
marshal(object, marshalRecord, session, getDescriptor(object, isXMLRoot), isXMLRoot);
}
/**
* INTERNAL:
* Convert the given object to an XML Document
* @param object the object to marshal
* @return the document which the specified object has been marshalled to
* @param descriptor the XMLDescriptor for the object being marshalled
* @throws XMLMarshalException if an error occurred during marshalling
*/
@Override
protected Document objectToXML(Object object, XMLDescriptor descriptor, boolean isXMLRoot) throws XMLMarshalException {
AbstractSession session = context.getSession(descriptor);
DocumentPreservationPolicy docPresPolicy = context.getDocumentPreservationPolicy(session);
if (docPresPolicy != null && docPresPolicy.shouldPreserveDocument()) {
XMLRecord xmlRow = null;
if (!isXMLRoot) {
xmlRow = (XMLRecord) ((XMLObjectBuilder) descriptor.getObjectBuilder()).createRecordFor(object, context.getDocumentPreservationPolicy(session));
xmlRow.setMarshaller(this);
if (this.attachmentMarshaller != null) {
xmlRow.setXOPPackage(this.attachmentMarshaller.isXOPPackage());
}
addDescriptorNamespacesToXMLRecord(descriptor, xmlRow);
}
return objectToXML(object, descriptor, xmlRow, isXMLRoot, docPresPolicy);
}
return super.objectToXML(object, descriptor, isXMLRoot);
}
@Override
protected Node objectToXMLNode(Object object, Node rootNode, AbstractSession session,XMLDescriptor descriptor, boolean isXMLRoot) throws XMLMarshalException {
DocumentPreservationPolicy docPresPolicy = context.getDocumentPreservationPolicy(session);
if (docPresPolicy != null && docPresPolicy.shouldPreserveDocument()) {
XMLRecord xmlRow = null;
if (!isXMLRoot) {
xmlRow = (XMLRecord) ((XMLObjectBuilder) descriptor.getObjectBuilder()).createRecordFor(object, context.getDocumentPreservationPolicy(session));
xmlRow.setMarshaller(this);
if (this.attachmentMarshaller != null) {
xmlRow.setXOPPackage(this.attachmentMarshaller.isXOPPackage());
}
if (xmlRow.getDOM().getNodeType() == Node.ELEMENT_NODE) {
addDescriptorNamespacesToXMLRecord(descriptor, xmlRow);
}
}
Document doc = objectToXML(object, rootNode, descriptor, xmlRow, isXMLRoot, docPresPolicy);
if ((xmlRow != null) && (xmlRow.getDOM().getNodeType() == Node.DOCUMENT_FRAGMENT_NODE)) {
return xmlRow.getDOM();
} else {
return doc;
}
}
return super.objectToXMLNode(object, rootNode, session, descriptor, isXMLRoot);
}
public Document objectToXML(Object object, Node parent, DocumentPreservationPolicy docPresPolicy) {
boolean isXMLRoot = (object instanceof Root);
AbstractSession session = null;
XMLDescriptor descriptor = null;
if(isXMLRoot){
try{
session = context.getSession(((Root)object).getObject());
if(session != null){
descriptor = getDescriptor(((Root)object).getObject(), session);
}
}catch (XMLMarshalException marshalException) {
if (!isSimpleXMLRoot((Root) object)) {
throw marshalException;
}
}
}else{
Class objectClass = object.getClass();
session = context.getSession(objectClass);
descriptor = getDescriptor(objectClass, session);
}
String localRootName = descriptor.getDefaultRootElement();
if (null == localRootName) {
throw XMLMarshalException.defaultRootElementNotSpecified(descriptor);
}
if(docPresPolicy == null) {
docPresPolicy = context.getDocumentPreservationPolicy(session);
}
if (docPresPolicy != null && docPresPolicy.shouldPreserveDocument()) {
XMLRecord xmlRow = (XMLRecord) ((XMLObjectBuilder) descriptor.getObjectBuilder()).createRecord(localRootName, parent, session);
xmlRow.setMarshaller(this);
if (this.attachmentMarshaller != null) {
xmlRow.setXOPPackage(this.attachmentMarshaller.isXOPPackage());
}
return objectToXML(object, descriptor, xmlRow, isXMLRoot, docPresPolicy);
}
MarshalRecord marshalRecord = new NodeRecord(localRootName, parent);
marshalRecord.setMarshaller(this);
marshal(object, marshalRecord, session, descriptor, isXMLRoot);
return marshalRecord.getDocument();
}
/**
* INTERNAL:
* Convert the given object to an XML Document
*/
public Document objectToXML(Object object, XMLDescriptor descriptor, XMLRecord xmlRow, boolean isXMLRoot, DocumentPreservationPolicy docPresPolicy) {
return objectToXML(object, null, descriptor, xmlRow, isXMLRoot, docPresPolicy);
}
public Document objectToXML(Object object, Node rootNode, XMLDescriptor descriptor, XMLRecord xmlRow, boolean isXMLRoot, DocumentPreservationPolicy docPresPolicy) {
if(null != rootNode) {
int rootNodeType = rootNode.getNodeType();
if(rootNodeType != Node.DOCUMENT_NODE && rootNodeType != Node.ELEMENT_NODE && rootNodeType != Node.DOCUMENT_FRAGMENT_NODE ) {
throw XMLMarshalException.marshalException(null);
}
}
Document document = null;
NamespaceResolver resolver = new NamespaceResolver();
resolver.setDOM(rootNode);
this.copyNamespaces(descriptor.getNamespaceResolver(), resolver);
boolean shouldCallSetAttributeNS = false;
boolean isRootDocumentFragment = false;
AbstractSession session = context.getSession(descriptor);
if (xmlRow != null) {
isRootDocumentFragment = (xmlRow.getDOM().getNodeType() == Node.DOCUMENT_FRAGMENT_NODE);
}
Object originalObject = object;
if (isXMLRoot) {
String xmlRootUri = ((Root) object).getNamespaceURI();
String xmlRootPrefix = null;
if (xmlRow == null) {
String recordName = ((Root) object).getLocalName();
if (xmlRootUri != null) {
xmlRootPrefix = resolver.resolveNamespaceURI(xmlRootUri);
if (xmlRootPrefix == null && !(xmlRootUri.equals(resolver.getDefaultNamespaceURI()))) {
xmlRootPrefix = resolver.generatePrefix();
resolver.put(xmlRootPrefix, xmlRootUri);
shouldCallSetAttributeNS = true;
}
if(xmlRootPrefix != null) {
recordName = xmlRootPrefix + XMLConstants.COLON + recordName;
}
}
xmlRow = (XMLRecord) ((XMLObjectBuilder) descriptor.getObjectBuilder()).createRecordFor(((Root) object).getObject(), docPresPolicy, recordName, xmlRootUri);
xmlRow.setMarshaller(this);
if (this.attachmentMarshaller != null) {
xmlRow.setXOPPackage(this.attachmentMarshaller.isXOPPackage());
}
if (!isRootDocumentFragment) {
if (shouldCallSetAttributeNS) {
if (xmlRootPrefix != null) {
((Element) xmlRow.getDOM()).setAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + XMLConstants.COLON + xmlRootPrefix, xmlRootUri);
}
shouldCallSetAttributeNS = false;
}
}
}
copyNamespaces(resolver, xmlRow.getNamespaceResolver());
document = xmlRow.getDocument();
object = ((Root) object).getObject();
}
XMLObjectBuilder bldr = (XMLObjectBuilder) descriptor.getObjectBuilder();
AbstractSession objectSession = context.getSession(object);
xmlRow.setSession(objectSession);
xmlRow.addXsiTypeAndClassIndicatorIfRequired(descriptor, null, null, originalObject, object, isXMLRoot, true);
xmlRow.setMarshaller(this);
if (shouldCallSetAttributeNS && !isRootDocumentFragment) {
((Element) xmlRow.getDOM()).setAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + XMLConstants.COLON + XMLConstants.SCHEMA_INSTANCE_PREFIX, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
}
xmlRow = (XMLRecord) bldr.buildRow(xmlRow, object, objectSession, isXMLRoot);
document = xmlRow.getDocument();
addSchemaLocations(document, session);
return document;
}
private void addSchemaLocations(Document document, AbstractSession session) {
Element docElement = document.getDocumentElement();
NamespaceResolver resolver = new NamespaceResolver();
resolver.put(javax.xml.XMLConstants.XMLNS_ATTRIBUTE, javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
resolver.put(XMLConstants.SCHEMA_INSTANCE_PREFIX, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
if ((getSchemaLocation() != null) || (getNoNamespaceSchemaLocation() != null)) {
XMLField field = new XMLField("@xmlns:xsi");
field.setNamespaceResolver(resolver);
XPathEngine.getInstance().create(field, docElement, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, session);
}
if (getSchemaLocation() != null) {
XMLField field = new XMLField("@xsi:" + XMLConstants.SCHEMA_LOCATION);
field.setNamespaceResolver(resolver);
XPathEngine.getInstance().create(field, docElement, getSchemaLocation(), session);
}
if (getNoNamespaceSchemaLocation() != null) {
XMLField field = new XMLField("@xsi:" + XMLConstants.NO_NS_SCHEMA_LOCATION);
field.setNamespaceResolver(resolver);
XPathEngine.getInstance().create(field, docElement, getNoNamespaceSchemaLocation(), session);
}
}
protected XMLDescriptor getDescriptor(Object object, AbstractSession session, boolean isXMLRoot) {
if (isXMLRoot) {
return getDescriptor((Root) object, session);
} else {
return getDescriptor(object, session);
}
}
@Override
public XMLMarshaller clone() {
return new XMLMarshaller(this);
}
/**
* NamespacePrefixMapper that can be used during marshal (instead of those set in the project meta data)
* @since 2.3.3
*/
@Override
public void setNamespacePrefixMapper(NamespacePrefixMapper mapper) {
super.setNamespacePrefixMapper(mapper);
}
/**
* NamespacePrefixMapper that can be used during marshal (instead of those set in the project meta data)
* @since 2.3.3
*/
@Override
public NamespacePrefixMapper getNamespacePrefixMapper() {
return super.getNamespacePrefixMapper();
}
/**
* Return this Marshaller's CharacterEscapeHandler.
* @since 2.3.3
*/
@Override
public CharacterEscapeHandler getCharacterEscapeHandler() {
return super.getCharacterEscapeHandler();
}
/**
* Set this Marshaller's CharacterEscapeHandler.
* @since 2.3.3
*/
@Override
public void setCharacterEscapeHandler(CharacterEscapeHandler c) {
super.setCharacterEscapeHandler(c);
}
/**
* Set the MediaType for this xmlMarshaller.
* See org.eclipse.persistence.oxm.MediaType for the media types supported by EclipseLink MOXy
* @since EclipseLink 2.4.0
*/
@Override
public void setMediaType(MediaType mediaType) {
super.setMediaType(mediaType);
}
/**
* Get the MediaType for this xmlMarshaller.
* See org.eclipse.persistence.oxm.MediaType for the media types supported by EclipseLink MOXy
* If not set the default is MediaType.APPLICATION_XML
* @return MediaType
* @since EclipseLink 2.4.0
*/
public MediaType getMediaType(){
return mediaType;
}
/**
* Returns json type configuration.
*
* @return json type configuration
* @since 2.6.0
*/
@Override
public JsonTypeConfiguration getJsonTypeConfiguration() {
if (null == jsonTypeConfiguration) {
jsonTypeConfiguration = new JsonTypeConfiguration();
}
return jsonTypeConfiguration;
}
}