com.sun.jersey.json.impl.writer.Stax2JacksonWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.jersey.json.impl.writer;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.json.impl.DefaultJaxbXmlDocumentStructure;
import com.sun.jersey.json.impl.JaxbXmlDocumentStructure;
import org.codehaus.jackson.JsonGenerator;
/**
* Implementation of {@link XMLStreamWriter} for JSON streams in natural notation.
*
* @author Jakub Podlesak (jakub.podlesak at oracle.com)
* @author Michal Gajdos (michal.gajdos at oracle.com)
*/
public class Stax2JacksonWriter extends DefaultXmlStreamWriter implements XMLStreamWriter {
private static class ProcessingInfo {
boolean isArray;
Type rawType;
Type individualType;
ProcessingInfo lastUnderlyingPI;
boolean startObjectWritten = false;
boolean afterFN = false;
QName elementName;
public ProcessingInfo(QName elementName, boolean isArray, Type rawType, Type individualType) {
this.elementName = elementName;
this.isArray = isArray;
this.rawType = rawType;
this.individualType = individualType;
}
public ProcessingInfo(ProcessingInfo pi) {
this(pi.elementName, pi.isArray, pi.rawType, pi.individualType);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ProcessingInfo other = (ProcessingInfo) obj;
if (this.isArray != other.isArray) {
return false;
}
if (this.elementName != other.elementName
&& (this.elementName == null || !this.elementName.equals(other.elementName))) {
return false;
}
if (this.rawType != other.rawType && (this.rawType == null || !this.rawType.equals(other.rawType))) {
return false;
}
if (this.individualType != other.individualType && (this.individualType == null || !this.individualType.equals(other.individualType))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 47 * hash + (this.isArray ? 1 : 0);
hash = 47 * hash + (this.elementName != null ?this.elementName.hashCode() : 0);
hash = 47 * hash + (this.rawType != null ? this.rawType.hashCode() : 0);
hash = 47 * hash + (this.individualType != null ? this.individualType.hashCode() : 0);
return hash;
}
}
final private boolean attrsWithPrefix;
final static String XML_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";
JacksonStringMergingGenerator generator;
final List processingStack = new ArrayList();
boolean writingAttr = false;
/**
* Document structure to obtain the expected elements and attributes from.
*/
private JaxbXmlDocumentStructure documentStructure;
static T pop(List stack) {
return stack.remove(stack.size() - 1);
}
static T peek(List stack) {
return (stack.size() > 0) ? stack.get(stack.size() - 1) : null;
}
static T peek2nd(List stack) {
return (stack.size() > 1) ? stack.get(stack.size() - 2) : null;
}
public Stax2JacksonWriter(final JsonGenerator generator, final Class> expectedType, final JAXBContext jaxbContext) {
this(generator, JSONConfiguration.DEFAULT, expectedType, jaxbContext);
}
public Stax2JacksonWriter(final JsonGenerator generator,
final JSONConfiguration config,
final Class> expectedType,
JAXBContext jaxbContext) {
this.attrsWithPrefix = config.isUsingPrefixesAtNaturalAttributes();
this.generator = JacksonStringMergingGenerator.createGenerator(generator);
this.documentStructure = DefaultJaxbXmlDocumentStructure.getXmlDocumentStructure(jaxbContext, expectedType, false);
}
@Override
public void writeStartElement(String localName) throws XMLStreamException {
writeStartElement(null, localName, null);
}
@Override
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
writeStartElement(null, localName, namespaceURI);
}
private void ensureStartObjectBeforeFieldName(ProcessingInfo pi) throws IOException {
if ((pi != null) && pi.afterFN) {
generator.writeStartObject();
peek2nd(processingStack).startObjectWritten = true;
pi.afterFN = false;
}
}
@Override
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
try {
if (!writingAttr) {
pushPropInfo(namespaceURI, localName, null);
}
ProcessingInfo currentPI = peek(processingStack);
ProcessingInfo parentPI = peek2nd(processingStack);
if (!currentPI.isArray) {
if ((parentPI != null) && (parentPI.lastUnderlyingPI != null) && (parentPI.lastUnderlyingPI.isArray)) {
generator.writeEndArray();
parentPI.afterFN = false;
}
ensureStartObjectBeforeFieldName(parentPI);
generator.writeFieldName(localName);
currentPI.afterFN = true;
} else {
if ((parentPI == null) || (!currentPI.equals(parentPI.lastUnderlyingPI))) {
// not the same array, need to close the last array off?
if ((parentPI != null) && (parentPI.lastUnderlyingPI != null) && (parentPI.lastUnderlyingPI.isArray)) {
generator.writeEndArray();
parentPI.afterFN = false;
}
// now start the new array
ensureStartObjectBeforeFieldName(parentPI);
generator.writeFieldName(localName);
generator.writeStartArray();
currentPI.afterFN = true;
} else {
// next array element
currentPI.afterFN = true;
}
}
} catch (IOException ex) {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.SEVERE, null, ex);
throw new XMLStreamException(ex);
}
}
static final Type[] _pt = new Type[]{
byte.class, short.class, int.class, long.class, float.class, double.class, boolean.class, char.class,
Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, Character.class,
String.class
};
static final Type[] _nst = new Type[]{
byte.class, short.class, int.class, long.class, float.class, double.class, boolean.class,
Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class,
};
static final Set primitiveTypes = new HashSet() {
{
addAll(Arrays.asList(_pt));
}
};
static final Set nonStringTypes = new HashSet() {
{
addAll(Arrays.asList(_nst));
}
};
private void pushPropInfo(String namespaceUri, String localName, String value) {
final QName qname = new QName(namespaceUri == null ? XMLConstants.NULL_NS_URI : namespaceUri, localName);
if (writingAttr) {
documentStructure.handleAttribute(qname, value);
} else {
documentStructure.startElement(qname);
}
ProcessingInfo parentPI = peek(processingStack);
// still the same array, no need to dig out runtime property info
final boolean sameArrayCollection = documentStructure.isSameArrayCollection();
if ((localName != null) && (parentPI != null)
&& (parentPI.lastUnderlyingPI != null)
// first is for RI and the second part of the next condition is for MOXy
&& (localName.equals(parentPI.lastUnderlyingPI.elementName.getLocalPart()) || sameArrayCollection)) {
processingStack.add(new ProcessingInfo(parentPI.lastUnderlyingPI));
return;
}
final Type rt = documentStructure.getEntityType(qname, writingAttr);
final Type individualType = documentStructure.getIndividualType();
// rt is null for root elements
if (null == rt) {
processingStack.add(new ProcessingInfo(qname, false, null, null));
return;
}
if (primitiveTypes.contains(rt)) {
processingStack.add(new ProcessingInfo(qname, false, rt, individualType));
return;
}
// TODO: wildcard could still simulate an array by adding several elements of the same name
if (documentStructure.isArrayCollection() && !writingAttr) { // another array
if (!((parentPI != null) && (parentPI.isArray) && sameArrayCollection)) {
// another array
processingStack.add(new ProcessingInfo(qname, true, rt, individualType));
return;
}
}
// something else
processingStack.add(new ProcessingInfo(qname, false, rt, individualType));
}
@Override
public void writeEmptyElement(String localName) throws XMLStreamException {
writeEmptyElement(null, localName, null);
}
@Override
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
writeEmptyElement(null, localName, namespaceURI);
}
@Override
public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
writeStartElement(prefix, localName, namespaceURI);
writeEndElement();
}
private void cleanlyEndObject(ProcessingInfo pi) throws IOException {
if (pi.startObjectWritten) {
generator.writeEndObject();
} else {
if (pi.afterFN && pi.lastUnderlyingPI == null) {
if(documentStructure.isArrayCollection()
|| documentStructure.hasSubElements()) {
generator.writeStartObject();
generator.writeEndObject();
} else {
generator.writeNull();
}
}
}
}
@Override
public void writeEndElement() throws XMLStreamException {
try {
ProcessingInfo removedPI = pop(processingStack);
ProcessingInfo currentPI = peek(processingStack);
if (currentPI != null) {
currentPI.lastUnderlyingPI = removedPI;
}
// need to check first, if there was an array to be closed off
if ((removedPI.lastUnderlyingPI != null) && (removedPI.lastUnderlyingPI.isArray)) {
generator.writeEndArray();
}
cleanlyEndObject(removedPI);
documentStructure.endElement(removedPI.elementName);
} catch (IOException ex) {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.SEVERE, null, ex);
throw new XMLStreamException(ex);
}
}
@Override
public void writeEndDocument() throws XMLStreamException {
try {
generator.writeEndObject();
} catch (IOException ex) {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.SEVERE, null, ex);
throw new XMLStreamException(ex);
}
}
@Override
public void close() throws XMLStreamException {
try {
generator.close();
} catch (IOException ex) {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.SEVERE, null, ex);
throw new XMLStreamException(ex);
}
}
@Override
public void flush() throws XMLStreamException {
try {
generator.flush();
} catch (IOException ex) {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.SEVERE, null, ex);
throw new XMLStreamException(ex);
}
}
@Override
public void writeAttribute(String localName, String value) throws XMLStreamException {
writeAttribute(null, null, localName, value);
}
@Override
public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
writeAttribute(null, namespaceURI, localName, value);
}
@Override
public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException {
writingAttr = true;
pushPropInfo(namespaceURI, localName, value);
writeStartElement(prefix, attrsWithPrefix ? ("@" + localName) : localName, namespaceURI);
writingAttr = false;
// a dirty hack, since jaxb ri is giving us wrong info on the actual attribute type in this case
writeCharacters(value, "type".equals(localName) && XML_SCHEMA_INSTANCE.equals(namespaceURI));
writeEndElement();
}
@Override
public void writeStartDocument() throws XMLStreamException {
writeStartDocument(null, null);
}
@Override
public void writeStartDocument(String version) throws XMLStreamException {
writeStartDocument(null, version);
}
@Override
public void writeStartDocument(String encoding, String version) throws XMLStreamException {
try {
generator.writeStartObject();
} catch (IOException ex) {
//JRA-18973: Log Socket exceptions that can happen quite often when browsers close connections pre-maturely at DEBUG level.
// Also don't need to throw the exception further up but just swallow it here.
if (ex instanceof java.net.SocketTimeoutException || ex instanceof java.net.SocketException) {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.FINE, "Socket excption", ex);
} else {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.SEVERE, "IO exception", ex);
throw new XMLStreamException(ex);
}
}
}
private void writeCharacters(String text, boolean forceString) throws XMLStreamException {
try {
ProcessingInfo currentPI = peek(processingStack);
if (currentPI.startObjectWritten && !currentPI.afterFN) {
generator.writeFieldName("$");
}
currentPI.afterFN = false;
final Type valueType = getValueType(currentPI.rawType, currentPI.individualType);
if (forceString || !nonStringTypes.contains(valueType)) {
if (!currentPI.isArray) {
generator.writeStringToMerge(text);
} else {
generator.writeString(text);
}
} else {
writePrimitiveType(text, valueType);
}
} catch (IOException ex) {
Logger.getLogger(Stax2JacksonWriter.class.getName()).log(Level.SEVERE, null, ex);
throw new XMLStreamException(ex);
}
}
private void writePrimitiveType(final String text, final Type valueType) throws IOException {
if ((boolean.class == valueType) || (Boolean.class == valueType)) {
generator.writeBoolean(Boolean.parseBoolean(text));
} else {
generator.writeNumber(text);
}
}
private Type getValueType(final Type rawType, final Type individualType) {
// Individual type.
if (individualType != null) {
return individualType;
}
// Collections.
if (rawType instanceof ParameterizedType) {
final ParameterizedType parameterizedType = (ParameterizedType) rawType;
final Type parameterizedTypeRawType = parameterizedType.getRawType();
if (parameterizedTypeRawType instanceof Class
&& Collection.class.isAssignableFrom((Class) parameterizedTypeRawType)) {
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments != null && actualTypeArguments.length > 0) {
return actualTypeArguments[0];
}
}
}
return rawType;
}
@Override
public void writeCharacters(String text) throws XMLStreamException {
writeCharacters(text, false);
}
@Override
public void writeCharacters(char[] text, int start, int length) throws XMLStreamException {
writeCharacters(new String(text, start, length));
}
}