/* * 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.io.Reader; import java.util.Collections; import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.stream.Location; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import com.sun.jersey.api.json.JSONConfiguration; import com.sun.jersey.api.json.JSONJAXBContext; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; /** * Implementation of {@link XMLStreamReader} for JSON streams in natural or mapped notation. This class contains a factory * method for an instance creation. * * @author Jakub Podlesak (jakub.podlesak at oracle.com) * @author Michal Gajdos (michal.gajdos at oracle.com) */ public class JsonXmlStreamReader implements XMLStreamReader { /** * Provider of Xml events. */ private final XmlEventProvider eventProvider; /** * Default namespace context for this class. */ private final JsonNamespaceContext namespaceContext = new JsonNamespaceContext(); /** * Exception that occurred during processing of the JSON stream. This property is supposed to be used in methods that are not * designed to throw the {@code XMLStreamException} directly (i.e. {@code #getAttributeXXX}). */ private XMLStreamException validationException; /** * Factory method for creating instances of this class. * * @param reader JSON input. * @param configuration JSON configuration. * @param rootName if non-{@code null} then the {@code JsonXmlStreamReader} emulates presence of root element with this * name for JAXB provider. * @param expectedType expected type of JAXB element. * @param jaxbContext JAXB context. * @param readingList flag whether it is expected that root is an JSON array instead of an object. * @return an instance of JSON XML stream reader * @throws XMLStreamException if an {@link IOException} has been thrown during the creation of an {@code JsonParser} instance. */ public static XMLStreamReader create(final Reader reader, final JSONConfiguration configuration, String rootName, final Class<?> expectedType, JAXBContext jaxbContext, final boolean readingList) throws XMLStreamException { try { if ((rootName == null || "".equals(rootName)) && (configuration.isRootUnwrapping())) { rootName = "rootElement"; } final JsonParser rawParser = new JsonFactory().createJsonParser(reader); final JsonParser nonListParser = configuration.isRootUnwrapping() ? JacksonRootAddingParser.createRootAddingParser (rawParser, rootName) : rawParser; XmlEventProvider eventStack = null; switch (configuration.getNotation()) { case MAPPED: eventStack = new MappedNotationEventProvider(nonListParser, configuration, rootName); break; case NATURAL: if (jaxbContext instanceof JSONJAXBContext) { jaxbContext = ((JSONJAXBContext) jaxbContext).getOriginalJaxbContext(); } if (!readingList) { eventStack = new NaturalNotationEventProvider(nonListParser, configuration, rootName, jaxbContext, expectedType); } else { eventStack = new NaturalNotationEventProvider( JacksonRootAddingParser.createRootAddingParser(nonListParser, "jsonArrayRootElement"), configuration, rootName, jaxbContext, expectedType); } break; } return new JsonXmlStreamReader(eventStack); } catch (IOException ex) { throw new XMLStreamException(ex); } } private JsonXmlStreamReader(final XmlEventProvider nodeStack) { this.eventProvider = nodeStack; } /** * Returns a list of attribute of the current element. This method also checks if the parser is in the proper state ({@code * XMLStreamConstants.START_ELEMENT} or {@code XMLStreamConstants.ATTRIBUTE}). * * @return list of the current elements attributes. */ private List<JsonXmlEvent.Attribute> getAttributes() { if (getEventType() != XMLStreamConstants.START_ELEMENT && getEventType() != XMLStreamConstants.ATTRIBUTE) { throw new IllegalArgumentException("Parser must be on START_ELEMENT or ATTRIBUTE to read next attribute."); } final JsonXmlEvent currentNode = eventProvider.getCurrentNode(); try { if (currentNode.getAttributes() == null) { eventProvider.processAttributesOfCurrentElement(); } return currentNode.getAttributes(); } catch (XMLStreamException xse) { // Cannot throw an exception from here - #getAttributeXXX methods doesn't support it - throw it when #next() method // is invoked. validationException = xse; return Collections.emptyList(); } } /** * Returns an attribute of the current element at given index. * * @param index index of an attribute to retrieve. * @return attribute at given index or {@code null} if the index is outside of boundaries of the list of attributes. */ private JsonXmlEvent.Attribute getAttribute(int index) { List<JsonXmlEvent.Attribute> attributes = getAttributes(); if (index < 0 || index >= attributes.size()) { return null; } return attributes.get(index); } @Override public void close() throws XMLStreamException { eventProvider.close(); } @Override public int getAttributeCount() { return getAttributes().size(); } @Override public String getAttributeLocalName(int index) { JsonXmlEvent.Attribute attribute = getAttribute(index); return attribute == null ? null : attribute.getName().getLocalPart(); } @Override public QName getAttributeName(int index) { JsonXmlEvent.Attribute attribute = getAttribute(index); return attribute == null ? null : attribute.getName(); } @Override public String getAttributeNamespace(int index) { JsonXmlEvent.Attribute attribute = getAttribute(index); return attribute == null ? null : attribute.getName().getNamespaceURI(); } @Override public String getAttributePrefix(int index) { JsonXmlEvent.Attribute attribute = getAttribute(index); return attribute == null ? null : attribute.getName().getPrefix(); } @Override public String getAttributeType(int index) { return null; } @Override public String getAttributeValue(String namespaceURI, String localName) { if (localName == null || "".equals(localName)) { return null; } for (JsonXmlEvent.Attribute attribute : getAttributes()) { if (localName.equals(attribute.getName().getLocalPart()) && ((namespaceURI == null) || (namespaceURI.equals(attribute.getName().getNamespaceURI())))) { return attribute.getValue(); } } return null; } @Override public String getAttributeValue(int index) { JsonXmlEvent.Attribute attribute = getAttribute(index); return attribute == null ? null : attribute.getValue(); } @Override public String getCharacterEncodingScheme() { return "UTF-8"; } @Override public String getElementText() throws XMLStreamException { if(getEventType() != XMLStreamConstants.START_ELEMENT) { throw new XMLStreamException( "Parser must be on START_ELEMENT to read next text.", getLocation()); } int eventType = next(); StringBuilder content = new StringBuilder(); while (eventType != XMLStreamConstants.END_ELEMENT) { if(eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA || eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.ENTITY_REFERENCE) { content.append(getText()); } else if (eventType == XMLStreamConstants.PROCESSING_INSTRUCTION || eventType == XMLStreamConstants.COMMENT) { // skipping } else if (eventType == XMLStreamConstants.END_DOCUMENT) { throw new XMLStreamException( "Unexpected end of document when reading element text content.", getLocation()); } else if (eventType == XMLStreamConstants.START_ELEMENT) { throw new XMLStreamException( "Element text content may not contain START_ELEMENT.", getLocation()); } else { throw new XMLStreamException( "Unexpected event type " + eventType + ".", getLocation()); } eventType = next(); } return content.toString(); } @Override public String getEncoding() { return "UTF-8"; } @Override public int getEventType() { return eventProvider.getCurrentNode().getEventType(); } @Override public String getLocalName() { final int eventType = getEventType(); if (eventType != XMLStreamReader.START_ELEMENT && eventType != XMLStreamReader.END_ELEMENT && eventType != XMLStreamReader.ENTITY_REFERENCE) { throw new IllegalArgumentException( "Parser must be on START_ELEMENT, END_ELEMENT or ENTITY_REFERENCE to read local name."); } return eventProvider.getCurrentNode().getName().getLocalPart(); } @Override public Location getLocation() { return eventProvider.getCurrentNode().getLocation(); } @Override public QName getName() { final int eventType = getEventType(); if (eventType != XMLStreamReader.START_ELEMENT && eventType != XMLStreamReader.END_ELEMENT) { throw new IllegalArgumentException("Parser must be on START_ELEMENT or END_ELEMENT to read the name."); } return eventProvider.getCurrentNode().getName(); } @Override public NamespaceContext getNamespaceContext() { return namespaceContext; } @Override public int getNamespaceCount() { return this.namespaceContext.getNamespaceCount(); } @Override public String getNamespacePrefix(int index) { return null; } @Override public String getNamespaceURI(String prefix) { return null; } @Override public String getNamespaceURI(int index) { return null; } @Override public String getNamespaceURI() { final int eventType = getEventType(); if (eventType != XMLStreamReader.START_ELEMENT && eventType != XMLStreamReader.END_ELEMENT) { throw new IllegalArgumentException("Parser must be on START_ELEMENT or END_ELEMENT to read the namespace URI."); } return eventProvider.getCurrentNode().getName().getNamespaceURI(); } @Override public String getPIData() { return null; } @Override public String getPITarget() { return null; } @Override public String getPrefix() { return eventProvider.getCurrentNode().getPrefix(); } @Override public Object getProperty(String name) throws IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("Name is null."); } return null; } @Override public String getText() { final int eventType = getEventType(); if(eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA || eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.ENTITY_REFERENCE) { return eventProvider.getCurrentNode().getText(); } throw new IllegalArgumentException( "Parser must be on CHARACTERS, CDATA, SPACE or ENTITY_REFERENCE to read text."); } @Override public char[] getTextCharacters() { final String text = getText(); return text != null ? text.toCharArray() : new char[0]; } @Override public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException { getText().getChars(sourceStart, sourceStart + length, target, targetStart); return length; } @Override public int getTextLength() { final String text = getText(); return text == null ? 0 : text.length(); } @Override public int getTextStart() { return 0; } @Override public String getVersion() { return null; } @Override public boolean hasName() { final int eventType = getEventType(); if (eventType != XMLStreamReader.START_ELEMENT && eventType != XMLStreamReader.END_ELEMENT) { throw new IllegalArgumentException("Parser must be on START_ELEMENT or END_ELEMENT to read the name."); } return eventProvider.getCurrentNode().getName() != null; } @Override public boolean hasNext() throws XMLStreamException { // Failure in the previous state? if (validationException != null) { throw validationException; } return eventProvider.getCurrentNode().getEventType() != XMLStreamConstants.END_DOCUMENT; } @Override public boolean hasText() { final int eventType = getEventType(); return eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA || eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.ENTITY_REFERENCE || eventType == XMLStreamConstants.COMMENT || eventType == XMLStreamConstants.DTD; } @Override public boolean isAttributeSpecified(int index) { return false; } @Override public boolean isCharacters() { return eventProvider.getCurrentNode().getEventType() == XMLStreamConstants.CHARACTERS; } @Override public boolean isEndElement() { return eventProvider.getCurrentNode().getEventType() == XMLStreamConstants.END_ELEMENT; } @Override public boolean isStandalone() { return false; } @Override public boolean isStartElement() { return eventProvider.getCurrentNode().getEventType() == XMLStreamConstants.START_ELEMENT; } @Override public boolean isWhiteSpace() { return false; // JsonParser does not return any whitespace element. } @Override public int next() throws XMLStreamException { if (!hasNext()) { throw new IllegalArgumentException("No more parsing elements."); } return eventProvider.readNext().getEventType(); } @Override public int nextTag() throws XMLStreamException { int eventType = next(); while ((eventType == XMLStreamConstants.CHARACTERS && isWhiteSpace()) // skip whitespace || (eventType == XMLStreamConstants.CDATA && isWhiteSpace()) // skip whitespace || eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.PROCESSING_INSTRUCTION || eventType == XMLStreamConstants.COMMENT) { eventType = next(); } if (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_ELEMENT) { throw new XMLStreamException("Expected start or end tag.", getLocation()); } return eventType; } @Override public void require(int type, String namespaceURI, String localName) throws XMLStreamException { } @Override public boolean standaloneSet() { return false; } }