com.sun.jersey.json.impl.reader.XmlEventProvider 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) 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.reader;
import java.io.IOException;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import com.sun.jersey.api.json.JSONConfiguration;
import org.codehaus.jackson.JsonLocation;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
/**
* Abstract provider for creating {@code JsonXmlEvent} instances from {@code JsonParser}. Extensions of this class should
* adjust their behaviour according to the JSON notation they are supporting.
*
* @author Michal Gajdos (michal.gajdos at oracle.com)
*/
public abstract class XmlEventProvider {
private static class ProcessingInfo {
QName name;
boolean isArray;
boolean isFirstElement;
ProcessingInfo(QName name, boolean isArray, boolean isFirstElement) {
this.name = name;
this.isArray = isArray;
this.isFirstElement = isFirstElement;
}
}
/**
* This wrapper of the {@code JsonParser} allows to peek at the following tokens without actually processing them.
*/
private static class CachedJsonParser {
/**
* JSON parser.
*/
private final JsonParser parser;
private final Queue tokens = new LinkedList();
public CachedJsonParser(final JsonParser parser) {
this.parser = parser;
}
public JsonToken nextToken() throws IOException {
return tokens.isEmpty() ? parser.nextToken() : tokens.poll();
}
public JsonToken peekNext() throws IOException {
final JsonToken jsonToken = parser.nextToken();
tokens.add(jsonToken);
return jsonToken;
}
public JsonToken peek() throws IOException {
if (tokens.isEmpty()) {
tokens.add(parser.nextToken());
}
return tokens.peek();
}
public JsonToken poll() throws IOException {
return tokens.poll();
}
public void close() throws IOException {
parser.close();
}
public JsonLocation getCurrentLocation() {
return parser.getCurrentLocation();
}
public String getText() throws IOException {
return parser.getText();
}
public String getCurrentName() throws IOException {
return parser.getCurrentName();
}
public boolean hasMoreTokens() throws IOException {
try {
return peek() != null;
} catch (IOException e) {
return false;
}
}
}
private static final Logger LOGGER = Logger.getLogger(XmlEventProvider.class.getName());
private final JSONConfiguration configuration;
/**
* JSON parser.
*/
private final CachedJsonParser parser;
private final String rootName;
/**
* Queue of unprocessed events.
*/
private final Deque eventQueue = new LinkedList();
private final Stack processingStack = new Stack();
protected XmlEventProvider(final JsonParser parser, final JSONConfiguration configuration, final String rootName)
throws XMLStreamException {
this.parser = new CachedJsonParser(parser);
this.configuration = configuration;
this.rootName = rootName;
try {
readNext();
} catch (XMLStreamException ex) {
LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
throw new XMLStreamException(ex);
}
}
/**
* @see com.sun.jersey.json.impl.reader.JsonXmlStreamReader#close()
*/
void close() throws XMLStreamException {
eventQueue.clear();
processingStack.empty();
try {
parser.close();
} catch (IOException ioe) {
throw new XMLStreamException(ioe);
}
}
/**
* Creates an {@code EndElementEvent}.
*
* @param elementName name of element for which the end element should be created.
* @param location location of this end element in the JSON stream.
* @return end element event.
*/
protected JsonXmlEvent createEndElementEvent(final QName elementName, final Location location) {
return new EndElementEvent(elementName, location);
}
/**
* Creates an {@code StartElementEvent}.
*
* @param elementName name of element for which the start element should be created.
* @param location location of this start element in the JSON stream.
* @return start element event.
*/
protected JsonXmlEvent createStartElementEvent(final QName elementName, final Location location) {
return new StartElementEvent(elementName, location);
}
/**
* Returns the name of an attribute from the given field name without the leading '@' character if present.
*
* @param jsonFieldName field name representing the attribute name.
* @return the name of an attribute.
*/
protected String getAttributeName(final String jsonFieldName) {
return '@' == jsonFieldName.charAt(0) ? jsonFieldName.substring(1) : jsonFieldName;
}
/**
* Returns the attribute qualified name which is determined from the given {@code jsonFieldName} and configuration of the
* underlying implementation.
*
* @param jsonFieldName name of the json field to obtain the qualified name for.
* @return qualified name of the attribute.
*/
protected abstract QName getAttributeQName(final String jsonFieldName);
JsonXmlEvent getCurrentNode() {
return eventQueue.peek();
}
/**
* Returns the element qualified name which is determined from the given {@code jsonFieldName} and configuration of the
* underlying implementation.
*
* @param jsonFieldName name of the json field to obtain the qualified name for.
* @return qualified name of the element.
*/
protected abstract QName getElementQName(final String jsonFieldName);
protected JSONConfiguration getJsonConfiguration() {
return configuration;
}
/**
* Checks whether the {@code jsonToken} belongs to the group of primitive json values (not an object or an array) and returns
* the given {@code jsonFieldValue} if the conditions are met. If the {@code jsonFieldValue} does not represents primitive
* json value an {@code IOException} is thrown.
*
* @param jsonToken token to determine whether the given value is of simple json type.
* @param jsonFieldValue value to be returned if it's one of the simple json types.
* @return simple json type value.
* @throws IOException if the given value doesn't belong to the group of primitive json values.
*/
private String getPrimitiveFieldValue(final JsonToken jsonToken, final String jsonFieldValue) throws IOException {
if (jsonToken == JsonToken.VALUE_FALSE
|| jsonToken == JsonToken.VALUE_TRUE
|| jsonToken == JsonToken.VALUE_STRING
|| jsonToken == JsonToken.VALUE_NUMBER_FLOAT
|| jsonToken == JsonToken.VALUE_NUMBER_INT
|| jsonToken == JsonToken.VALUE_NULL) {
return jsonFieldValue;
}
throw new IOException("Not an XML value, expected primitive value!");
}
/**
* Determines whether the given json field name represents an attribute name.
*
* @param jsonFieldName json field name to be examined.
* @return {@code true} if the given name represents an attribute, {@code false} otherwise.
*/
protected abstract boolean isAttribute(final String jsonFieldName);
/**
* Retrieves and sets attributes for the current element from JSON stream.
*
* @throws XMLStreamException if an error occurred during the processing of the JSON stream.
* @see #processTokens(boolean)
*/
void processAttributesOfCurrentElement() throws XMLStreamException {
eventQueue.peek().setAttributes(new LinkedList());
processTokens(true);
}
private JsonXmlEvent processTokens(boolean processAttributes) throws XMLStreamException {
if (!processAttributes) {
// get rid of the current event
eventQueue.poll();
}
try {
while (eventQueue.isEmpty() || processAttributes) {
while (true) {
final JsonToken jsonToken = parser.nextToken();
final ProcessingInfo pi = processingStack.isEmpty() ? null : processingStack.peek();
if (jsonToken == null) {
return getCurrentNode();
}
switch (jsonToken) {
case FIELD_NAME:
final String fieldName = parser.getCurrentName();
if (isAttribute(fieldName)) {
// attribute
final QName attributeName = getAttributeQName(fieldName);
final String attributeValue = getPrimitiveFieldValue(parser.nextToken(), parser.getText());
eventQueue.peek().getAttributes().add(new JsonXmlEvent.Attribute(attributeName, attributeValue));
} else {
processAttributes = false;
// child event
if ("$".equals(fieldName)) {
// character event
final String value = getPrimitiveFieldValue(parser.nextToken(), parser.getText());
eventQueue.add(new CharactersEvent(value, new StaxLocation(parser.getCurrentLocation())));
} else {
// element event
final QName elementName = getElementQName(fieldName);
final JsonLocation currentLocation = parser.getCurrentLocation();
final boolean isRootEmpty = isEmptyElement(fieldName, true);
if (isRootEmpty) {
eventQueue.add(createStartElementEvent(elementName, new StaxLocation(currentLocation)));
eventQueue.add(createEndElementEvent(elementName, new StaxLocation(currentLocation)));
eventQueue.add(new EndDocumentEvent(new StaxLocation(parser.getCurrentLocation())));
} else {
if (!isEmptyArray() && !isEmptyElement(fieldName, false)) {
eventQueue.add(createStartElementEvent(elementName, new StaxLocation(currentLocation)));
processingStack.add(new ProcessingInfo(elementName, false, true));
}
if (!parser.hasMoreTokens()) {
eventQueue.add(new EndDocumentEvent(new StaxLocation(parser.getCurrentLocation())));
}
}
if (eventQueue.isEmpty()) {
continue;
}
return getCurrentNode();
}
}
break;
case START_OBJECT:
if (pi == null) {
eventQueue.add(new StartDocumentEvent(new StaxLocation(0, 0, 0)));
return getCurrentNode();
}
if (pi.isArray && !pi.isFirstElement) {
eventQueue.add(createStartElementEvent(pi.name, new StaxLocation(parser.getCurrentLocation())));
return getCurrentNode();
} else {
pi.isFirstElement = false;
}
break;
case END_OBJECT:
processAttributes = false;
// end tag
eventQueue.add(createEndElementEvent(pi.name, new StaxLocation(parser.getCurrentLocation())));
if (!pi.isArray) {
processingStack.pop();
}
if (processingStack.isEmpty()) {
eventQueue.add(new EndDocumentEvent(new StaxLocation(parser.getCurrentLocation())));
// Eat the last '}' and check whether there is another (unexpected) token.
final JsonToken nextToken = parser.nextToken();
if ((nextToken != null && nextToken != JsonToken.END_OBJECT)
|| parser.peek() != null) {
throw new RuntimeException("Unexpected token: " + parser.getText());
}
}
return getCurrentNode();
case VALUE_FALSE:
case VALUE_NULL:
case VALUE_NUMBER_FLOAT:
case VALUE_NUMBER_INT:
case VALUE_TRUE:
case VALUE_STRING:
if (!pi.isFirstElement) {
eventQueue.add(createStartElementEvent(pi.name, new StaxLocation(parser.getCurrentLocation())));
} else {
pi.isFirstElement = false;
}
if (jsonToken != JsonToken.VALUE_NULL) {
eventQueue.add(new CharactersEvent(parser.getText(), new StaxLocation(parser.getCurrentLocation())));
}
eventQueue.add(new EndElementEvent(pi.name, new StaxLocation(parser.getCurrentLocation())));
if (!pi.isArray) {
processingStack.pop();
}
if (processingStack.isEmpty()) {
eventQueue.add(new EndDocumentEvent(new StaxLocation(parser.getCurrentLocation())));
}
processAttributes = false;
return getCurrentNode();
case START_ARRAY:
processingStack.peek().isArray = true;
break;
case END_ARRAY:
processingStack.pop();
processAttributes = false;
break;
default:
throw new IllegalStateException("Unknown JSON token: " + jsonToken);
}
}
}
return eventQueue.peek();
} catch (Exception e) {
throw new XMLStreamException(e);
}
}
/**
* Checks if the currently processed JSON field is represented as an empty array. If so array tokens are thrown away and no
* {@code StartElementEvent} should be created.
*
* @return {@code true} if next tokens signalize an empty array, {@code false} otherwise.
* @throws IOException if there is a problem reading next {@code JsonToken}.
*/
private boolean isEmptyArray() throws IOException {
final JsonToken jsonToken = parser.peek();
if (jsonToken == JsonToken.START_ARRAY && parser.peekNext() == JsonToken.END_ARRAY) {
// throw away parser tokens
parser.poll();
parser.poll();
return true;
}
return false;
}
private boolean isEmptyElement(final String fieldName, boolean checkRoot) throws IOException {
if (!checkRoot || (fieldName != null && fieldName.equals(rootName))) {
final JsonToken jsonToken = parser.peek();
if (jsonToken == JsonToken.VALUE_NULL) {
parser.poll();
return true;
}
}
return false;
}
/**
* Reads and returns next {@code JsonXmlEvent}.
*
* @return an instance of {@code JsonXmlEvent}.
* @throws XMLStreamException if something went wrong.
*/
JsonXmlEvent readNext() throws XMLStreamException {
return processTokens(false);
}
}