| /* |
| * 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; |
| |
| /** |
| * <p>Use this type of MarshalRecord when the marshal target is a Writer and the |
| * JSON should not be formatted with carriage returns or indenting.</p> |
| * <p><code> |
| * XMLContext xmlContext = new XMLContext("session-name");<br> |
| * XMLMarshaller xmlMarshaller = xmlContext.createMarshaller();<br> |
| * JSONRecord jsonWriterRecord = new JSONWriterRecord();<br> |
| * jsonWriterRecord.setWriter(myWriter);<br> |
| * xmlMarshaller.marshal(myObject, jsonWriterRecord);<br> |
| * </code></p> |
| * <p>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.</p> |
| * <p><code> |
| * XMLContext xmlContext = new XMLContext("session-name");<br> |
| * XMLMarshaller xmlMarshaller = xmlContext.createMarshaller();<br> |
| * xmlMarshaller.setMediaType(MediaType.APPLICATION_JSON); |
| * xmlMarshaller xmlMarshaller.setFormattedOutput(false);<br> |
| * xmlMarshaller.marshal(myObject, myWriter);<br> |
| * </code></p> |
| * @see org.eclipse.persistence.oxm.XMLMarshaller |
| */ |
| public class JSONWriterRecord extends MarshalRecord<XMLMarshaller> { |
| |
| 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<Object> theClass = conversionManager.javaType(schemaType); |
| |
| if(schemaType == null || theClass == null){ |
| if(value.getClass() == CoreClassConstants.BOOLEAN || CoreClassConstants.NUMBER.isAssignableFrom(value.getClass())){ |
| characters(convertedValue, false, isAttribute); |
| }else{ |
| characters(convertedValue); |
| |
| } |
| }else if(schemaType != null && !isNumericOrBooleanType(schemaType)){ |
| //if schemaType exists and is not boolean or number do write quotes |
| characters(convertedValue); |
| } else if(isCDATA){ |
| cdata(convertedValue); |
| }else{ |
| characters(convertedValue, false, isAttribute); |
| } |
| } |
| charactersAllowed = false; |
| |
| } |
| |
| |
| private boolean isNumericOrBooleanType(QName schemaType){ |
| if(schemaType == null){ |
| return false; |
| }else if(schemaType.equals(Constants.BOOLEAN_QNAME) |
| || schemaType.equals(Constants.INTEGER_QNAME) |
| || schemaType.equals(Constants.INT_QNAME) |
| || schemaType.equals(Constants.BYTE_QNAME) |
| || schemaType.equals(Constants.DECIMAL_QNAME) |
| || schemaType.equals(Constants.FLOAT_QNAME) |
| || schemaType.equals(Constants.DOUBLE_QNAME) |
| || schemaType.equals(Constants.SHORT_QNAME) |
| || schemaType.equals(Constants.LONG_QNAME) |
| || schemaType.equals(Constants.NEGATIVE_INTEGER_QNAME) |
| || schemaType.equals(Constants.NON_NEGATIVE_INTEGER_QNAME) |
| || schemaType.equals(Constants.NON_POSITIVE_INTEGER_QNAME) |
| || schemaType.equals(Constants.POSITIVE_INTEGER_QNAME) |
| || schemaType.equals(Constants.UNSIGNED_BYTE_QNAME) |
| || schemaType.equals(Constants.UNSIGNED_INT_QNAME) |
| || schemaType.equals(Constants.UNSIGNED_LONG_QNAME) |
| || schemaType.equals(Constants.UNSIGNED_SHORT_QNAME) |
| ){ |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void namespaceDeclarations(NamespaceResolver namespaceResolver) { |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void nilComplex(XPathFragment xPathFragment, NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| closeStartGroupingElements(groupingFragment); |
| openStartElement(xPathFragment, namespaceResolver); |
| characters(NULL, false, false); |
| endElement(xPathFragment, namespaceResolver); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void nilSimple(NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| characters(NULL, false, false); |
| closeStartGroupingElements(groupingFragment); |
| } |
| |
| /** |
| * Used when an empty simple value should be written |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void emptySimple(NamespaceResolver namespaceResolver){ |
| nilSimple(namespaceResolver); |
| } |
| |
| @Override |
| public void emptyAttribute(XPathFragment xPathFragment,NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| openStartElement(xPathFragment, namespaceResolver); |
| characters(NULL, false, false); |
| endElement(xPathFragment, namespaceResolver); |
| closeStartGroupingElements(groupingFragment); |
| } |
| |
| /** |
| * Used when an empty complex item should be written |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void emptyComplex(XPathFragment xPathFragment, NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| closeStartGroupingElements(groupingFragment); |
| openStartElement(xPathFragment, namespaceResolver); |
| endElement(xPathFragment, namespaceResolver); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void marshalWithoutRootElement(ObjectBuilder treeObjectBuilder, Object object, Descriptor descriptor, Root root, boolean isXMLRoot){ |
| if(treeObjectBuilder != null){ |
| addXsiTypeAndClassIndicatorIfRequired(descriptor, null, descriptor.getDefaultRootElementField(), root, object, isXMLRoot, true); |
| treeObjectBuilder.marshalAttributes(this, object, session); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void cdata(String value) { |
| characters(value); |
| } |
| |
| /** |
| * INTERNAL: |
| * The character used to separate the prefix and uri portions when namespaces are present |
| * @since 2.4 |
| */ |
| @Override |
| public char getNamespaceSeparator(){ |
| return marshaller.getNamespaceSeparator(); |
| } |
| |
| /** |
| * INTERNAL: |
| * The optional fragment used to wrap the text() mappings |
| * @since 2.4 |
| */ |
| @Override |
| public XPathFragment getTextWrapperFragment() { |
| return textWrapperFragment; |
| } |
| |
| protected void writeKey(XPathFragment xPathFragment) throws IOException { |
| if (xPathFragment.getLocalName() != null && !xPathFragment.getLocalName().equals(Constants.EMPTY_STRING)) { |
| super.openStartElement(xPathFragment, namespaceResolver); |
| writer.write('"'); |
| if (xPathFragment.isAttribute() && attributePrefix != null) { |
| writer.writeAttributePrefix(); |
| } |
| |
| if (isNamespaceAware()) { |
| if (xPathFragment.getNamespaceURI() != null) { |
| String prefix = null; |
| if (getNamespaceResolver() != null) { |
| prefix = getNamespaceResolver().resolveNamespaceURI(xPathFragment.getNamespaceURI()); |
| } else if (namespaceResolver != null) { |
| prefix = namespaceResolver.resolveNamespaceURI(xPathFragment.getNamespaceURI()); |
| } |
| if (prefix != null && !prefix.equals(Constants.EMPTY_STRING)) { |
| writer.write(prefix); |
| writer.writeNamespaceSeparator(); |
| } |
| } |
| } |
| |
| writer.writeLocalName(xPathFragment); |
| writer.write('"'); |
| |
| writeSeparator(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void writeValue(String value, boolean isAttribute) { |
| try { |
| if (characterEscapeHandler != null) { |
| writer.writeResultFromCharEscapeHandler(value, isAttribute); |
| return; |
| } |
| |
| char[] chars = value.toCharArray(); |
| for (int x = 0, charsSize = chars.length; x < charsSize; x++) { |
| char character = chars[x]; |
| switch (character){ |
| case '"' : { |
| writer.write("\\\""); |
| break; |
| } |
| case '\b': { |
| writer.write("\\b"); |
| break; |
| } |
| case '\f': { |
| writer.write("\\f"); |
| break; |
| } |
| case '\n': { |
| writer.write("\\n"); |
| break; |
| } |
| case '\r': { |
| writer.write("\\r"); |
| break; |
| } |
| case '\t': { |
| writer.write("\\t"); |
| break; |
| } |
| case '\\': { |
| writer.write("\\\\"); |
| break; |
| } |
| default: { |
| if(Character.isISOControl(character) || !encoder.canEncode(character)){ |
| writer.write("\\u"); |
| String hex = Integer.toHexString(character).toUpperCase(); |
| for(int i=hex.length(); i<4; i++){ |
| writer.write("0"); |
| } |
| writer.write(hex); |
| }else{ |
| writer.write(character); |
| } |
| } |
| } |
| } |
| } catch (IOException e) { |
| throw XMLMarshalException.marshalException(e); |
| } |
| } |
| |
| @Override |
| protected String getStringForQName(QName qName){ |
| if(null == qName) { |
| return null; |
| } |
| CoreConversionManager xmlConversionManager = getSession().getDatasourcePlatform().getConversionManager(); |
| |
| return xmlConversionManager.convertObject(qName, String.class); |
| } |
| |
| /** |
| * Receive notification of a node. |
| * @param node The Node to be added to the document |
| * @param namespaceResolver The NamespaceResolver can be used to resolve the |
| * namespace URI/prefix of the node |
| */ |
| @Override |
| public void node(Node node, NamespaceResolver namespaceResolver, String uri, String name) { |
| if (node.getNodeType() == Node.ATTRIBUTE_NODE) { |
| Attr attr = (Attr) node; |
| String resolverPfx = null; |
| if (getNamespaceResolver() != null) { |
| resolverPfx = this.getNamespaceResolver().resolveNamespaceURI(attr.getNamespaceURI()); |
| } |
| String namespaceURI = attr.getNamespaceURI(); |
| // If the namespace resolver contains a prefix for the attribute's URI, |
| // use it instead of what is set on the attribute |
| if (resolverPfx != null) { |
| attribute(attr.getNamespaceURI(), Constants.EMPTY_STRING, resolverPfx+Constants.COLON+attr.getLocalName(), attr.getNodeValue()); |
| } else { |
| attribute(attr.getNamespaceURI(), Constants.EMPTY_STRING, attr.getName(), attr.getNodeValue()); |
| // May need to declare the URI locally |
| if (attr.getNamespaceURI() != null) { |
| attribute(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, Constants.EMPTY_STRING, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON + attr.getPrefix(), attr.getNamespaceURI()); |
| this.getNamespaceResolver().put(attr.getPrefix(), attr.getNamespaceURI()); |
| } |
| } |
| } else if (node.getNodeType() == Node.TEXT_NODE) { |
| characters(node.getNodeValue(), false, false); |
| } else { |
| try { |
| JSONWriterRecordContentHandler wrcHandler = new JSONWriterRecordContentHandler(); |
| |
| XMLFragmentReader xfragReader = new XMLFragmentReader(namespaceResolver); |
| xfragReader.setContentHandler(wrcHandler); |
| xfragReader.setProperty("http://xml.org/sax/properties/lexical-handler", wrcHandler); |
| xfragReader.parse(node, uri, name); |
| } catch (SAXException sex) { |
| throw XMLMarshalException.marshalException(sex); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isWrapperAsCollectionName() { |
| return marshaller.isWrapperAsCollectionName(); |
| } |
| |
| @Override |
| public void flush() { |
| try { |
| writer.flush(); |
| } catch(IOException e) { |
| throw XMLMarshalException.marshalException(e); |
| } |
| } |
| |
| /** |
| * This class will typically be used in conjunction with an XMLFragmentReader. |
| * The XMLFragmentReader will walk a given XMLFragment node and report events |
| * to this class - the event's data is then written to the enclosing class' |
| * writer. |
| * |
| * @see org.eclipse.persistence.internal.oxm.record.XMLFragmentReader |
| */ |
| protected class JSONWriterRecordContentHandler implements ExtendedContentHandler, LexicalHandler { |
| |
| JSONWriterRecordContentHandler() { |
| } |
| |
| // --------------------- CONTENTHANDLER METHODS --------------------- // |
| @Override |
| public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { |
| XPathFragment xPathFragment = new XPathFragment(localName); |
| xPathFragment.setNamespaceURI(namespaceURI); |
| openStartElement(xPathFragment, namespaceResolver); |
| handleAttributes(atts); |
| } |
| |
| @Override |
| public void endElement(String namespaceURI, String localName, String qName) throws SAXException { |
| XPathFragment xPathFragment = new XPathFragment(localName); |
| xPathFragment.setNamespaceURI(namespaceURI); |
| |
| JSONWriterRecord.this.endElement(xPathFragment, namespaceResolver); |
| } |
| |
| @Override |
| public void startPrefixMapping(String prefix, String uri) throws SAXException { |
| } |
| |
| @Override |
| public void characters(char[] ch, int start, int length) throws SAXException { |
| String characters = new String (ch, start, length); |
| characters(characters); |
| } |
| |
| @Override |
| public void characters(CharSequence characters) throws SAXException { |
| JSONWriterRecord.this.characters(characters.toString()); |
| } |
| |
| // --------------------- LEXICALHANDLER METHODS --------------------- // |
| @Override |
| public void comment(char[] ch, int start, int length) throws SAXException { |
| } |
| |
| @Override |
| public void startCDATA() throws SAXException { |
| isProcessingCData = true; |
| } |
| |
| @Override |
| public void endCDATA() throws SAXException { |
| isProcessingCData = false; |
| } |
| |
| // --------------------- CONVENIENCE METHODS --------------------- // |
| protected void handleAttributes(Attributes atts) { |
| for (int i=0, attsLength = atts.getLength(); i<attsLength; i++) { |
| String qName = atts.getQName(i); |
| if((qName != null && (qName.startsWith(javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON) || qName.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)))) { |
| continue; |
| } |
| attribute(atts.getURI(i), atts.getLocalName(i), qName, atts.getValue(i)); |
| } |
| } |
| |
| protected void writeComment(char[] chars, int start, int length) { |
| } |
| |
| protected void writeCharacters(char[] chars, int start, int length) { |
| try { |
| characters(chars, start, length); |
| } catch (SAXException e) { |
| throw XMLMarshalException.marshalException(e); |
| } |
| } |
| // --------------- SATISFY CONTENTHANDLER INTERFACE --------------- // |
| @Override |
| public void endPrefixMapping(String prefix) throws SAXException {} |
| @Override |
| public void processingInstruction(String target, String data) throws SAXException {} |
| @Override |
| public void setDocumentLocator(Locator locator) {} |
| @Override |
| public void startDocument() throws SAXException {} |
| @Override |
| public void endDocument() throws SAXException {} |
| @Override |
| public void skippedEntity(String name) throws SAXException {} |
| @Override |
| public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {} |
| |
| // --------------- SATISFY LEXICALHANDLER INTERFACE --------------- // |
| @Override |
| public void startEntity(String name) throws SAXException {} |
| @Override |
| public void endEntity(String name) throws SAXException {} |
| @Override |
| public void startDTD(String name, String publicId, String systemId) throws SAXException {} |
| @Override |
| public void endDTD() throws SAXException {} |
| @Override |
| public void setNil(boolean isNil) {} |
| |
| } |
| |
| /** |
| * Instances of this class are used to maintain state about the current |
| * level of the JSON message being marshalled. |
| */ |
| protected static class Level { |
| |
| private boolean first; |
| private boolean collection; |
| private boolean emptyCollection; |
| private boolean needToOpenComplex; |
| private boolean needToCloseComplex; |
| private boolean nestedArray; |
| private Level previousLevel; |
| |
| public Level(boolean value, boolean needToOpen, boolean nestedArray) { |
| this.first = value; |
| needToOpenComplex = needToOpen; |
| this.nestedArray = nestedArray; |
| } |
| |
| public Level(boolean value, boolean needToOpen, boolean nestedArray, Level previousLevel) { |
| this(value, needToOpen, nestedArray); |
| this.previousLevel = previousLevel; |
| } |
| |
| public boolean isNeedToOpenComplex() { |
| return needToOpenComplex; |
| } |
| |
| public void setNeedToOpenComplex(boolean needToOpenComplex) { |
| this.needToOpenComplex = needToOpenComplex; |
| } |
| |
| public boolean isNeedToCloseComplex() { |
| return needToCloseComplex; |
| } |
| |
| public void setNeedToCloseComplex(boolean needToCloseComplex) { |
| this.needToCloseComplex = needToCloseComplex; |
| } |
| |
| public boolean isEmptyCollection() { |
| return emptyCollection; |
| } |
| |
| public void setEmptyCollection(boolean emptyCollection) { |
| this.emptyCollection = emptyCollection; |
| } |
| |
| public boolean isFirst() { |
| return first; |
| } |
| |
| public void setFirst(boolean value) { |
| this.first = value; |
| } |
| |
| public boolean isCollection() { |
| return collection; |
| } |
| |
| public void setCollection(boolean collection) { |
| this.collection = collection; |
| } |
| |
| public Level getPreviousLevel() { |
| return previousLevel; |
| } |
| |
| public boolean isNestedArray() { |
| return nestedArray; |
| } |
| |
| public void setNestedArray(boolean nestedArray) { |
| this.nestedArray = nestedArray; |
| } |
| } |
| |
| protected interface Output { |
| |
| void flush() throws IOException; |
| |
| XMLMarshaller getMarshaller(); |
| |
| OutputStream getOutputStream(); |
| |
| Writer getWriter(); |
| |
| void setMarshaller(XMLMarshaller marshaller); |
| |
| void write(char character) throws IOException; |
| |
| void write(String text) throws IOException; |
| |
| void writeAttributePrefix() throws IOException; |
| |
| void writeCR() throws IOException; |
| |
| void writeLocalName(XPathFragment xPathFragment) throws IOException; |
| |
| void writeNamespaceSeparator() throws IOException; |
| |
| void writeResultFromCharEscapeHandler(String value, boolean isAttribute); |
| |
| } |
| |
| protected static class OutputStreamOutput implements Output { |
| |
| private static final int BUFFER_SIZE = 512; |
| |
| private byte[] attributePrefix; |
| private byte[] buffer = new byte[BUFFER_SIZE]; |
| private int bufferIndex = 0; |
| private CharacterEscapeHandler characterEscapeHandler; |
| private byte[] cr = Constants.cr().getBytes(Constants.DEFAULT_CHARSET); |
| private XMLMarshaller marshaller; |
| private char namespaceSeparator; |
| private OutputStream outputStream; |
| |
| protected OutputStreamOutput(OutputStream writer) { |
| this.outputStream = writer; |
| } |
| |
| @Override |
| public void flush() throws IOException { |
| outputStream.write(buffer, 0, bufferIndex); |
| bufferIndex = 0; |
| outputStream.flush(); |
| } |
| |
| @Override |
| public XMLMarshaller getMarshaller() { |
| return marshaller; |
| } |
| |
| @Override |
| public OutputStream getOutputStream() { |
| return outputStream; |
| } |
| |
| @Override |
| public Writer getWriter() { |
| return null; |
| } |
| |
| @Override |
| public void setMarshaller(XMLMarshaller marshaller) { |
| this.marshaller = marshaller; |
| String attributePrefix = marshaller.getAttributePrefix(); |
| if(null != attributePrefix) { |
| this.attributePrefix = attributePrefix.getBytes(Constants.DEFAULT_CHARSET); |
| } |
| this.characterEscapeHandler = marshaller.getCharacterEscapeHandler(); |
| this.namespaceSeparator = marshaller.getNamespaceSeparator(); |
| } |
| |
| private void write(byte[] bytes) { |
| int bytesLength = bytes.length; |
| if(bufferIndex + bytesLength >= BUFFER_SIZE) { |
| try { |
| outputStream.write(buffer, 0, bufferIndex); |
| bufferIndex = 0; |
| if(bytesLength > BUFFER_SIZE) { |
| outputStream.write(bytes); |
| return; |
| } |
| } catch(IOException e) { |
| throw XMLMarshalException.marshalException(e); |
| } |
| } |
| System.arraycopy(bytes, 0, buffer, bufferIndex, bytes.length); |
| bufferIndex += bytesLength; |
| } |
| |
| @Override |
| public void write(char character) throws IOException { |
| if (character > 0x7F) { |
| if(character > 0x7FF) { |
| if((character >= Character.MIN_HIGH_SURROGATE) && (character <= Character.MAX_LOW_SURROGATE)) { |
| int uc = ((character & 0x3ff) << 10); |
| // 11110zzz |
| write((byte)(0xF0 | ((uc >> 18)))); |
| // 10zzyyyy |
| write((byte)(0x80 | ((uc >> 12) & 0x3F))); |
| // 10yyyyxx |
| write((byte)(0x80 | ((uc >> 6) & 0x3F))); |
| // 10xxxxxx |
| write((byte)(0x80 + (uc & 0x3F))); |
| return; |
| } else { |
| // 1110yyyy |
| write((byte)(0xE0 + (character >> 12))); |
| } |
| // 10yyyyxx |
| write((byte)(0x80 + ((character >> 6) & 0x3F))); |
| } else { |
| // 110yyyxx |
| write((byte)(0xC0 + (character >> 6))); |
| } |
| write((byte)(0x80 + (character & 0x3F))); |
| } else { |
| write((byte) character); |
| } |
| } |
| |
| private void write(byte b) { |
| if(bufferIndex == BUFFER_SIZE) { |
| try { |
| outputStream.write(buffer, 0, BUFFER_SIZE); |
| bufferIndex = 0; |
| } catch(IOException e) { |
| throw XMLMarshalException.marshalException(e); |
| } |
| } |
| buffer[bufferIndex++] = b; |
| } |
| |
| @Override |
| public void write(String text) throws IOException { |
| write(text.getBytes(Constants.DEFAULT_CHARSET)); |
| } |
| |
| @Override |
| public void writeAttributePrefix() throws IOException { |
| write(attributePrefix); |
| } |
| |
| @Override |
| public void writeCR() throws IOException { |
| write(cr); |
| } |
| |
| @Override |
| public void writeLocalName(XPathFragment xPathFragment) throws IOException { |
| write(xPathFragment.getLocalNameBytes()); |
| } |
| |
| @Override |
| public void writeNamespaceSeparator() throws IOException { |
| write(namespaceSeparator); |
| } |
| |
| @Override |
| public void writeResultFromCharEscapeHandler(String value, boolean isAttribute) { |
| try { |
| CharArrayWriter out = new CharArrayWriter(); |
| characterEscapeHandler.escape(value.toCharArray(), 0, value.length(), isAttribute, out); |
| byte[] bytes = out.toString().getBytes(); |
| write(bytes); |
| out.close(); |
| } catch (IOException e) { |
| throw XMLMarshalException.marshalException(e); |
| } |
| } |
| |
| } |
| |
| private static class WriterOutput implements Output { |
| |
| private String attributePrefix; |
| private CharacterEscapeHandler characterEscapeHandler; |
| private String cr = Constants.cr(); |
| private XMLMarshaller marshaller; |
| private char namespaceSeparator; |
| private Writer writer; |
| |
| @Override |
| public void flush() throws IOException { |
| writer.flush(); |
| } |
| |
| protected WriterOutput(Writer writer) { |
| this.writer = writer; |
| } |
| |
| @Override |
| public XMLMarshaller getMarshaller() { |
| return marshaller; |
| } |
| |
| @Override |
| public OutputStream getOutputStream() { |
| return null; |
| } |
| |
| @Override |
| public Writer getWriter() { |
| return writer; |
| } |
| |
| @Override |
| public void setMarshaller(XMLMarshaller marshaller) { |
| this.marshaller = marshaller; |
| this.attributePrefix = marshaller.getAttributePrefix(); |
| this.characterEscapeHandler = marshaller.getCharacterEscapeHandler(); |
| this.namespaceSeparator = marshaller.getNamespaceSeparator(); |
| } |
| |
| @Override |
| public void writeAttributePrefix() throws IOException { |
| writer.write(attributePrefix); |
| } |
| |
| @Override |
| public void write(char character) throws IOException { |
| writer.write(character); |
| } |
| |
| @Override |
| public void write(String text) throws IOException { |
| writer.write(text); |
| } |
| |
| @Override |
| public void writeCR() throws IOException { |
| writer.write(cr); |
| } |
| |
| @Override |
| public void writeLocalName(XPathFragment xPathFragment) throws IOException { |
| writer.write(xPathFragment.getLocalName()); |
| } |
| |
| @Override |
| public void writeNamespaceSeparator() throws IOException { |
| writer.write(namespaceSeparator); |
| } |
| |
| @Override |
| public void writeResultFromCharEscapeHandler(String value, boolean isAttribute) { |
| try { |
| characterEscapeHandler.escape(value.toCharArray(), 0, value.length(), isAttribute, writer); |
| } catch (IOException e) { |
| throw XMLMarshalException.marshalException(e); |
| } |
| } |
| |
| } |
| |
| } |