org.eclipse.persistence.oxm.record.JSONWriterRecord Maven / Gradle / Ivy
/*
* Copyright (c) 2011, 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:
// Blaise Doughan - 2.4 - initial implementation
package org.eclipse.persistence.oxm.record;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.List;
import javax.xml.namespace.QName;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.core.helper.CoreConversionManager;
import org.eclipse.persistence.internal.oxm.CharacterEscapeHandler;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.ObjectBuilder;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.XMLBinaryDataHelper;
import org.eclipse.persistence.internal.oxm.XMLMarshaller;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.record.ExtendedContentHandler;
import org.eclipse.persistence.internal.oxm.record.XMLFragmentReader;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
/**
* Use this type of MarshalRecord when the marshal target is a Writer and the
* JSON should not be formatted with carriage returns or indenting.
*
* XMLContext xmlContext = new XMLContext("session-name");
* XMLMarshaller xmlMarshaller = xmlContext.createMarshaller();
* JSONRecord jsonWriterRecord = new JSONWriterRecord();
* jsonWriterRecord.setWriter(myWriter);
* xmlMarshaller.marshal(myObject, jsonWriterRecord);
*
* If the marshal(Writer) and setMediaType(MediaType.APPLICATION_JSON) and
* setFormattedOutput(false) method is called on XMLMarshaller, then the Writer
* is automatically wrapped in a JSONWriterRecord.
*
* XMLContext xmlContext = new XMLContext("session-name");
* XMLMarshaller xmlMarshaller = xmlContext.createMarshaller();
* xmlMarshaller.setMediaType(MediaType.APPLICATION_JSON);
* xmlMarshaller xmlMarshaller.setFormattedOutput(false);
* xmlMarshaller.marshal(myObject, myWriter);
*
* @see org.eclipse.persistence.oxm.XMLMarshaller
*/
public class JSONWriterRecord extends MarshalRecord {
protected boolean isProcessingCData = false;
protected static final String NULL="null";
protected String attributePrefix;
protected boolean charactersAllowed = false;
protected CharsetEncoder encoder;
protected CharacterEscapeHandler characterEscapeHandler;
protected String callbackName;
protected Output writer;
protected Level level;
public JSONWriterRecord(){
super();
}
public JSONWriterRecord(OutputStream outputStream) {
this();
writer = new OutputStreamOutput(outputStream);
}
public JSONWriterRecord(OutputStream outputStream, String callbackName){
this(outputStream);
setCallbackName(callbackName);
}
public JSONWriterRecord(Writer writer){
this();
setWriter(writer);
}
public JSONWriterRecord(Writer writer, String callbackName){
this(writer);
setCallbackName(callbackName);
}
public void setCallbackName(String callbackName){
this.callbackName = callbackName;
}
/**
* INTERNAL:
*/
@Override
public void setMarshaller(XMLMarshaller marshaller) {
super.setMarshaller(marshaller);
attributePrefix = marshaller.getAttributePrefix();
encoder = Charset.forName(marshaller.getEncoding()).newEncoder();
if (marshaller.getValueWrapper() != null) {
textWrapperFragment = new XPathFragment();
textWrapperFragment.setLocalName(marshaller.getValueWrapper());
}
characterEscapeHandler = marshaller.getCharacterEscapeHandler();
writer.setMarshaller(marshaller);
}
/**
* Handle marshal of an empty collection.
* @param openGrouping if grouping elements should be marshalled for empty collections
*/
@Override
public boolean emptyCollection(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, boolean openGrouping) {
if(marshaller.isMarshalEmptyCollections()){
super.emptyCollection(xPathFragment, namespaceResolver, true);
if (null != xPathFragment) {
startCollection();
if (!xPathFragment.isSelfFragment()) {
openStartElement(xPathFragment, namespaceResolver);
if (null != level) {
level.setNeedToCloseComplex(false);
level.setNeedToOpenComplex(false);
}
endElement(xPathFragment, namespaceResolver);
}
endEmptyCollection();
}
return true;
}else{
return super.emptyCollection(xPathFragment, namespaceResolver, openGrouping);
}
}
@Override
public void forceValueWrapper(){
charactersAllowed = false;
}
/**
* Return the Writer that the object will be marshalled to.
* @return The marshal target.
*/
public Writer getWriter() {
return writer.getWriter();
}
/**
* Set the Writer that the object will be marshalled to.
* @param writer The marshal target.
*/
public void setWriter(Writer writer) {
this.writer = new WriterOutput(writer);
}
@Override
public void namespaceDeclaration(String prefix, String namespaceURI){
}
@Override
public void defaultNamespaceDeclaration(String defaultNamespace){
}
/**
* INTERNAL:
*/
@Override
public void startDocument(String encoding, String version) {
try {
if(null != level) {
if(level.isFirst()) {
level.setFirst(false);
} else {
writeListSeparator();
}
}else if(callbackName != null){
startCallback();
}
level = new Level(true, false, false, level);
writer.write('{');
} catch (IOException e) {
throw XMLMarshalException.marshalException(e);
}
}
protected void writeListSeparator() throws IOException{
writer.write(',');
}
protected void writeSeparator() throws IOException{
writer.write(Constants.COLON);
}
/**
* INTERNAL:
*/
protected void startCallback() throws IOException{
if(callbackName != null){
writer.write(callbackName);
writer.write('(');
}
}
/**
* INTERNAL:
*/
@Override
public void endDocument() {
try {
closeComplex();
if(null != level && null == level.getPreviousLevel()){
endCallback();
}
level = level.getPreviousLevel();
} catch (IOException e) {
throw XMLMarshalException.marshalException(e);
}
}
/**
* INTERNAL:
*/
@Override
public void openStartElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
try {
if(level.isFirst()) {
level.setFirst(false);
} else {
writer.write(',');
}
if(xPathFragment.nameIsText()){
if(level.isCollection() && level.isEmptyCollection()) {
writer.write('[');
level.setEmptyCollection(false);
level.setNeedToOpenComplex(false);
charactersAllowed = true;
level = new Level(true, true, false, level);
return;
}
}
if(level.needToOpenComplex){
if (!level.isNestedArray()) {
writer.write('{');
}
level.needToOpenComplex = false;
level.needToCloseComplex = true;
}
//write the key unless this is a a non-empty collection
if(!(level.isCollection() && !level.isEmptyCollection())){
if (!level.isNestedArray()) {
writeKey(xPathFragment);
}
//if it is the first thing in the collection also add the [
if(level.isCollection() && level.isEmptyCollection()){
writer.write('[');
level.setEmptyCollection(false);
}
}
charactersAllowed = true;
if (xPathFragment.getXMLField() != null && xPathFragment.getXMLField().isNestedArray() && this.marshaller.getJsonTypeConfiguration().isJsonDisableNestedArrayName()) {
level = new Level(true, true, true, level);
} else {
level = new Level(true, true, false, level);
}
} catch (IOException e) {
throw XMLMarshalException.marshalException(e);
}
}
/**
* INTERNAL:
*/
@Override
public void element(XPathFragment frag) {
}
/**
* INTERNAL:
*/
@Override
public void attribute(String namespaceURI, String localName, String qName, String value) {
XPathFragment xPathFragment = new XPathFragment();
xPathFragment.setNamespaceURI(namespaceURI);
xPathFragment.setAttribute(true);
xPathFragment.setLocalName(localName);
openStartElement(xPathFragment, namespaceResolver);
characters(null, value, null, false, true);
endElement(xPathFragment, namespaceResolver);
}
/**
* INTERNAL:
*/
@Override
public void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, String value) {
attribute(xPathFragment, namespaceResolver, value, null);
}
/**
* INTERNAL:
* override so we don't iterate over namespaces when startPrefixMapping doesn't do anything
*/
@Override
public void startPrefixMappings(NamespaceResolver namespaceResolver) {
}
/**
* INTERNAL:
* override so we don't iterate over namespaces when endPrefixMapping doesn't do anything
*/
@Override
public void endPrefixMappings(NamespaceResolver namespaceResolver) {
}
/**
* INTERNAL:
*/
@Override
public void closeStartElement() {}
/**
* INTERNAL:
*/
@Override
public void endElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) {
try{
if(null != level) {
if(level.needToOpenComplex){
writer.write('{');
closeComplex();
} else if(level.needToCloseComplex && !level.nestedArray){
closeComplex();
}
charactersAllowed = false;
level = level.getPreviousLevel();
}
} catch (IOException e) {
throw XMLMarshalException.marshalException(e);
}
}
protected void closeComplex() throws IOException {
writer.write('}');
}
@Override
public void startCollection() {
if(null == level) {
try {
startCallback();
writer.write('[');
level = new Level(true, false, false, level);
} catch(IOException e) {
throw XMLMarshalException.marshalException(e);
}
} else {
level.setCollection(true);
level.setEmptyCollection(true);
charactersAllowed = false;
}
}
protected void endEmptyCollection(){
endCollection();
}
protected void endCallback() throws IOException{
if(callbackName != null){
writer.write(')');
writer.write(';');
}
}
@Override
public void endCollection() {
try {
if(level != null && null == level.getPreviousLevel()) {
writer.write(']');
endCallback();
} else {
if(level != null && level.isCollection() && !level.isEmptyCollection()) {
writer.write(']');
}
}
level.setCollection(false);
} catch (IOException e) {
throw XMLMarshalException.marshalException(e);
}
}
/**
* INTERNAL:
*/
@Override
public void characters(String value) {
characters(value, true, false);
}
/**
* INTERNAL:
*/
public void characters(String value, boolean isString, boolean isAttribute) {
boolean textWrapperOpened = false;
if(!charactersAllowed){
if(textWrapperFragment != null){
openStartElement(textWrapperFragment, namespaceResolver);
textWrapperOpened = true;
}
}
level.setNeedToOpenComplex(false);
try {
if(isString){
writer.write('"');
writeValue(value, isAttribute);
writer.write('"');
}else{
writer.write(value);
}
} catch (IOException e) {
throw XMLMarshalException.marshalException(e);
}
if(textWrapperOpened){
if(textWrapperFragment != null){
endElement(textWrapperFragment, namespaceResolver);
}
}
}
@Override
public void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, Object value, QName schemaType){
if(xPathFragment.getNamespaceURI() != null && xPathFragment.getNamespaceURI() == javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI){
return;
}
xPathFragment.setAttribute(true);
openStartElement(xPathFragment, namespaceResolver);
characters(schemaType, value, null, false, true);
endElement(xPathFragment, namespaceResolver);
}
@Override
public void characters(QName schemaType, Object value, String mimeType, boolean isCDATA){
characters(schemaType, value, mimeType, isCDATA, false);
}
public void characters(QName schemaType, Object value, String mimeType, boolean isCDATA, boolean isAttribute) {
if(mimeType != null) {
if(value instanceof List){
value = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesListForBinaryValues(//
(List)value, marshaller, mimeType);
}else{
value = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesForBinaryValue(//
value, marshaller, mimeType).getData();
}
}
if(schemaType != null && Constants.QNAME_QNAME.equals(schemaType)){
String convertedValue = getStringForQName((QName)value);
characters(convertedValue);
} else if(value.getClass() == String.class){
//if schemaType is set and it's a numeric or boolean type don't treat as a string
if(schemaType != null && isNumericOrBooleanType(schemaType)){
String convertedValue = ((ConversionManager) session.getDatasourcePlatform().getConversionManager()).convertObject(value, CoreClassConstants.STRING, schemaType);
characters(convertedValue, false, isAttribute);
}else if(isCDATA){
cdata((String)value);
}else{
characters((String)value);
}
}else{
ConversionManager conversionManager = getConversionManager();
String convertedValue = conversionManager.convertObject(value, CoreClassConstants.STRING, schemaType);
Class