/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.axis2.json.gson; import com.google.gson.stream.JsonWriter; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.json.gson.factory.JSONType; import org.apache.axis2.json.gson.factory.JsonConstant; import org.apache.axis2.json.gson.factory.JsonObject; import org.apache.axis2.json.gson.factory.XmlNode; import org.apache.axis2.json.gson.factory.XmlNodeGenerator; import org.apache.ws.commons.schema.XmlSchema; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Stack; public class GsonXMLStreamWriter implements XMLStreamWriter { private JsonWriter jsonWriter; /** * queue is used to keep the outgoing response structure according to the response XMLSchema */ private Queue<JsonObject> queue = new LinkedList<JsonObject>(); /** * This stacks use to process the outgoing response */ private Stack<JsonObject> stack = new Stack<JsonObject>(); private Stack<JsonObject> miniStack = new Stack<JsonObject>(); private JsonObject flushObject; /** * topNestedArrayObj is use to keep the most top nested array object */ private JsonObject topNestedArrayObj; /** * processedJsonObject stack is used to keep processed JsonObject for future reference */ private Stack<JsonObject> processedJsonObjects = new Stack<JsonObject>(); private List<XmlSchema> xmlSchemaList; /** * Element QName of outgoing message , which is get from the outgoing message context */ private QName elementQName; /** * Intermediate representation of XmlSchema */ private XmlNode mainXmlNode; private ConfigurationContext configContext; private XmlNodeGenerator xmlNodeGenerator; private boolean isProcessed; /** * This map is used to keep namespace uri with prefixes */ private Map<String, String> uriPrefixMap = new HashMap<String, String>(); public GsonXMLStreamWriter(JsonWriter jsonWriter, QName elementQName, List<XmlSchema> xmlSchemaList, ConfigurationContext context) { this.jsonWriter = jsonWriter; this.elementQName = elementQName; this.xmlSchemaList = xmlSchemaList; this.configContext = context; } private void process() throws IOException { Object ob = configContext.getProperty(JsonConstant.XMLNODES); if (ob != null) { Map<QName, XmlNode> nodeMap = (Map<QName, XmlNode>) ob; XmlNode resNode = nodeMap.get(elementQName); if (resNode != null) { xmlNodeGenerator = new XmlNodeGenerator(); queue = xmlNodeGenerator.getQueue(resNode); } else { xmlNodeGenerator = new XmlNodeGenerator(xmlSchemaList, elementQName); mainXmlNode = xmlNodeGenerator.getMainXmlNode(); queue = xmlNodeGenerator.getQueue(mainXmlNode); nodeMap.put(elementQName, mainXmlNode); configContext.setProperty(JsonConstant.XMLNODES, nodeMap); } } else { Map<QName, XmlNode> newNodeMap = new HashMap<QName, XmlNode>(); xmlNodeGenerator = new XmlNodeGenerator(xmlSchemaList, elementQName); mainXmlNode = xmlNodeGenerator.getMainXmlNode(); queue = xmlNodeGenerator.getQueue(mainXmlNode); newNodeMap.put(elementQName, mainXmlNode); configContext.setProperty(JsonConstant.XMLNODES, newNodeMap); } isProcessed = true; this.jsonWriter.beginObject(); } private void writeStartJson(JsonObject jsonObject) throws IOException { if (jsonObject.getType() == JSONType.OBJECT) { jsonWriter.name(jsonObject.getName()); } else if (jsonObject.getType() == JSONType.ARRAY) { jsonWriter.name(jsonObject.getName()); jsonWriter.beginArray(); } else if (jsonObject.getType() == JSONType.NESTED_ARRAY) { jsonWriter.name(jsonObject.getName()); jsonWriter.beginArray(); jsonWriter.beginObject(); if (topNestedArrayObj == null) { topNestedArrayObj = jsonObject; processedJsonObjects.push(jsonObject); } } else if (jsonObject.getType() == JSONType.NESTED_OBJECT) { jsonWriter.name(jsonObject.getName()); jsonWriter.beginObject(); } } private void writeEndJson(JsonObject endJson) throws IOException { if (endJson.getType() == JSONType.OBJECT) { // nothing write to json writer } else if (endJson.getType() == JSONType.ARRAY) { jsonWriter.endArray(); } else if (endJson.getType() == JSONType.NESTED_ARRAY) { jsonWriter.endArray(); } else if (endJson.getType() == JSONType.NESTED_OBJECT) { jsonWriter.endObject(); } } private JsonObject popStack() { if (topNestedArrayObj == null || stack.peek().getType() == JSONType.NESTED_OBJECT || stack.peek().getType() == JSONType.NESTED_ARRAY) { return stack.pop(); } else { processedJsonObjects.push(stack.peek()); return stack.pop(); } } private void fillMiniStack(JsonObject nestedJsonObject) { while (!processedJsonObjects.peek().getName().equals(nestedJsonObject.getName())) { miniStack.push(processedJsonObjects.pop()); } processedJsonObjects.pop(); } /** * Writes a start tag to the output. All writeStartElement methods * open a new scope in the internal namespace context. Writing the * corresponding EndElement causes the scope to be closed. * * @param localName local name of the tag, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void writeStartElement(String localName) throws XMLStreamException { if (!isProcessed) { try { process(); } catch (IOException e) { throw new XMLStreamException("Error occours while write first begin object "); } } JsonObject stackObj = null; try { if (miniStack.isEmpty()) { if (!queue.isEmpty()) { JsonObject queObj = queue.peek(); if (queObj.getName().equals(localName)) { if (flushObject != null) { if (topNestedArrayObj != null && flushObject.getType() == JSONType.NESTED_ARRAY && flushObject.getName().equals(topNestedArrayObj.getName())) { topNestedArrayObj = null; processedJsonObjects.clear(); } popStack(); writeEndJson(flushObject); flushObject = null; } if (topNestedArrayObj != null && (queObj.getType() == JSONType.NESTED_ARRAY || queObj.getType() == JSONType.NESTED_OBJECT)) { processedJsonObjects.push(queObj); } writeStartJson(queObj); stack.push(queue.poll()); } else if (!stack.isEmpty()) { stackObj = stack.peek(); if (stackObj.getName().equals(localName)) { if (stackObj.getType() == JSONType.NESTED_ARRAY) { fillMiniStack(stackObj); jsonWriter.beginObject(); processedJsonObjects.push(stackObj); } flushObject = null; } else { throw new XMLStreamException("Invalid Staring element"); } } } else { if (!stack.isEmpty()) { stackObj = stack.peek(); if (stackObj.getName().equals(localName)) { flushObject = null; if (stackObj.getType() == JSONType.NESTED_ARRAY) { fillMiniStack(stackObj); jsonWriter.beginObject(); processedJsonObjects.push(stackObj); } } else { throw new XMLStreamException("Invalid Staring element"); } } else { throw new XMLStreamException("Invalid Starting element"); } } } else { JsonObject queObj = miniStack.peek(); if (queObj.getName().equals(localName)) { if (flushObject != null) { popStack(); writeEndJson(flushObject); flushObject = null; } if (topNestedArrayObj != null && (queObj.getType() == JSONType.NESTED_OBJECT || queObj.getType() == JSONType.NESTED_ARRAY)) { processedJsonObjects.push(queObj); } writeStartJson(queObj); stack.push(miniStack.pop()); } else if (!stack.isEmpty()) { stackObj = stack.peek(); if (stackObj.getName().equals(localName)) { flushObject = null; if (stackObj.getType() == JSONType.NESTED_ARRAY) { fillMiniStack(stackObj); jsonWriter.beginObject(); processedJsonObjects.push(stackObj); } } else { throw new XMLStreamException("Invalid Staring element"); } } } } catch (IOException e) { throw new XMLStreamException(" Json Writer throw an error"); } } /** * Writes a start tag to the output * * @param namespaceURI the namespaceURI of the prefix to use, may not be null * @param localName local name of the tag, may not be null * @throws javax.xml.stream.XMLStreamException * if the namespace URI has not been bound to a prefix and * javax.xml.stream.isRepairingNamespaces has not been set to true */ public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { writeStartElement(localName); } /** * Writes a start tag to the output * * @param localName local name of the tag, may not be null * @param prefix the prefix of the tag, may not be null * @param namespaceURI the uri to bind the prefix to, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { writeStartElement(localName); } /** * Writes an empty element tag to the output * * @param namespaceURI the uri to bind the tag to, may not be null * @param localName local name of the tag, may not be null * @throws javax.xml.stream.XMLStreamException * if the namespace URI has not been bound to a prefix and * javax.xml.stream.isRepairingNamespaces has not been set to true */ public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes an empty element tag to the output * * @param prefix the prefix of the tag, may not be null * @param localName local name of the tag, may not be null * @param namespaceURI the uri to bind the tag to, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes an empty element tag to the output * * @param localName local name of the tag, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void writeEmptyElement(String localName) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes an end tag to the output relying on the internal * state of the writer to determine the prefix and local name * of the event. * * @throws javax.xml.stream.XMLStreamException * */ public void writeEndElement() throws XMLStreamException { if (!isProcessed) { try { process(); } catch (IOException e) { throw new XMLStreamException("Error occours while write first begin object "); } } JsonObject stackObj; try { if (flushObject != null) { if (topNestedArrayObj != null && flushObject.getType() == JSONType.NESTED_ARRAY && flushObject.equals(topNestedArrayObj.getName())) { topNestedArrayObj = null; processedJsonObjects.clear(); } popStack(); writeEndJson(flushObject); flushObject = null; writeEndElement(); } else { if (stack.peek().getType() == JSONType.ARRAY) { flushObject = stack.peek(); } else if (stack.peek().getType() == JSONType.NESTED_ARRAY) { flushObject = stack.peek(); jsonWriter.endObject(); } else { stackObj = popStack(); writeEndJson(stackObj); } } } catch (IOException e) { throw new XMLStreamException("Json writer throw an exception"); } } /** * Closes any start tags and writes corresponding end tags. * * @throws javax.xml.stream.XMLStreamException * */ public void writeEndDocument() throws XMLStreamException { if (!isProcessed) { try { process(); } catch (IOException e) { throw new XMLStreamException("Error occours while write first begin object "); } } if (queue.isEmpty() && stack.isEmpty()) { try { if (flushObject != null) { writeEndJson(flushObject); } jsonWriter.endObject(); jsonWriter.flush(); jsonWriter.close(); } catch (IOException e) { throw new XMLStreamException("JsonWriter threw an exception", e); } } else { throw new XMLStreamException("Invalid xml element"); } } /** * Close this writer and free any resources associated with the * writer. This must not close the underlying output stream. * * @throws javax.xml.stream.XMLStreamException * */ public void close() throws XMLStreamException { try { jsonWriter.close(); } catch (IOException e) { throw new RuntimeException("Error occur while closing JsonWriter"); } } /** * Write any cached data to the underlying output mechanism. * * @throws javax.xml.stream.XMLStreamException * */ public void flush() throws XMLStreamException { } /** * Writes an attribute to the output stream without * a prefix. * * @param localName the local name of the attribute * @param value the value of the attribute * @throws IllegalStateException if the current state does not allow Attribute writing * @throws javax.xml.stream.XMLStreamException * */ public void writeAttribute(String localName, String value) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes an attribute to the output stream * * @param prefix the prefix for this attribute * @param namespaceURI the uri of the prefix for this attribute * @param localName the local name of the attribute * @param value the value of the attribute * @throws IllegalStateException if the current state does not allow Attribute writing * @throws javax.xml.stream.XMLStreamException * if the namespace URI has not been bound to a prefix and * javax.xml.stream.isRepairingNamespaces has not been set to true */ public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException { // GsonXMLStreamReader doesn't write Attributes } /** * Writes an attribute to the output stream * * @param namespaceURI the uri of the prefix for this attribute * @param localName the local name of the attribute * @param value the value of the attribute * @throws IllegalStateException if the current state does not allow Attribute writing * @throws javax.xml.stream.XMLStreamException * if the namespace URI has not been bound to a prefix and * javax.xml.stream.isRepairingNamespaces has not been set to true */ public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes a namespace to the output stream * If the prefix argument to this method is the empty string, * "xmlns", or null this method will delegate to writeDefaultNamespace * * @param prefix the prefix to bind this namespace to * @param namespaceURI the uri to bind the prefix to * @throws IllegalStateException if the current state does not allow Namespace writing * @throws javax.xml.stream.XMLStreamException * */ public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { } /** * Writes the default namespace to the stream * * @param namespaceURI the uri to bind the default namespace to * @throws IllegalStateException if the current state does not allow Namespace writing * @throws javax.xml.stream.XMLStreamException * */ public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { // do nothing } /** * Writes an xml comment with the data enclosed * * @param data the data contained in the comment, may be null * @throws javax.xml.stream.XMLStreamException * */ public void writeComment(String data) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes a processing instruction * * @param target the target of the processing instruction, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void writeProcessingInstruction(String target) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes a processing instruction * * @param target the target of the processing instruction, may not be null * @param data the data contained in the processing instruction, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void writeProcessingInstruction(String target, String data) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes a CData section * * @param data the data contained in the CData Section, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void writeCData(String data) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Write a DTD section. This string represents the entire doctypedecl production * from the XML 1.0 specification. * * @param dtd the DTD to be written * @throws javax.xml.stream.XMLStreamException * */ public void writeDTD(String dtd) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Writes an entity reference * * @param name the name of the entity * @throws javax.xml.stream.XMLStreamException * */ public void writeEntityRef(String name) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Write the XML Declaration. Defaults the XML version to 1.0, and the encoding to utf-8 * * @throws javax.xml.stream.XMLStreamException * */ public void writeStartDocument() throws XMLStreamException { if (!isProcessed) { try { process(); } catch (IOException e) { throw new XMLStreamException("Error occur while write first begin object "); } } } /** * Write the XML Declaration. Defaults the XML version to 1.0 * * @param version version of the xml document * @throws javax.xml.stream.XMLStreamException * */ public void writeStartDocument(String version) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Write the XML Declaration. Note that the encoding parameter does * not set the actual encoding of the underlying output. That must * be set when the instance of the XMLStreamWriter is created using the * XMLOutputFactory * * @param encoding encoding of the xml declaration * @param version version of the xml document * @throws javax.xml.stream.XMLStreamException * If given encoding does not match encoding * of the underlying stream */ public void writeStartDocument(String encoding, String version) throws XMLStreamException { if (!isProcessed) { try { process(); } catch (IOException e) { throw new XMLStreamException("Error occur while trying to write start document element", e); } } } /** * Write text to the output * * @param text the value to write * @throws javax.xml.stream.XMLStreamException * */ public void writeCharacters(String text) throws XMLStreamException { if (!isProcessed) { try { process(); } catch (IOException e) { throw new XMLStreamException("Error occur while trying to write first begin object ", e); } } try { JsonObject peek = stack.peek(); String valueType = peek.getValueType(); if (valueType.equals("string")) { jsonWriter.value(text); } else if (valueType.equals("int")) { Number num = new Integer(text); jsonWriter.value(num); } else if (valueType.equals("long")) { jsonWriter.value(Long.valueOf(text)); } else if (valueType.equals("double")) { jsonWriter.value(Double.valueOf(text)); } else if (valueType.equals("boolean")) { jsonWriter.value(Boolean.valueOf(text)); } else { jsonWriter.value(text); } } catch (IOException e) { throw new XMLStreamException("JsonWriter throw an exception"); } } /** * Write text to the output * * @param text the value to write * @param start the starting position in the array * @param len the number of characters to write * @throws javax.xml.stream.XMLStreamException * */ public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Gets the prefix the uri is bound to * * @return the prefix or null * @throws javax.xml.stream.XMLStreamException * */ public String getPrefix(String uri) throws XMLStreamException { return uriPrefixMap.get(uri); } /** * Sets the prefix the uri is bound to. This prefix is bound * in the scope of the current START_ELEMENT / END_ELEMENT pair. * If this method is called before a START_ELEMENT has been written * the prefix is bound in the root scope. * * @param prefix the prefix to bind to the uri, may not be null * @param uri the uri to bind to the prefix, may be null * @throws javax.xml.stream.XMLStreamException * */ public void setPrefix(String prefix, String uri) throws XMLStreamException { uriPrefixMap.put(uri, prefix); } /** * Binds a URI to the default namespace * This URI is bound * in the scope of the current START_ELEMENT / END_ELEMENT pair. * If this method is called before a START_ELEMENT has been written * the uri is bound in the root scope. * * @param uri the uri to bind to the default namespace, may be null * @throws javax.xml.stream.XMLStreamException * */ public void setDefaultNamespace(String uri) throws XMLStreamException { //do nothing. } /** * Sets the current namespace context for prefix and uri bindings. * This context becomes the root namespace context for writing and * will replace the current root namespace context. Subsequent calls * to setPrefix and setDefaultNamespace will bind namespaces using * the context passed to the method as the root context for resolving * namespaces. This method may only be called once at the start of * the document. It does not cause the namespaces to be declared. * If a namespace URI to prefix mapping is found in the namespace * context it is treated as declared and the prefix may be used * by the StreamWriter. * * @param context the namespace context to use for this writer, may not be null * @throws javax.xml.stream.XMLStreamException * */ public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { throw new UnsupportedOperationException("Method is not implemented"); } /** * Returns the current namespace context. * * @return the current NamespaceContext */ public NamespaceContext getNamespaceContext() { return new GsonNamespaceConext(); } /** * Get the value of a feature/property from the underlying implementation * * @param name The name of the property, may not be null * @return The value of the property * @throws IllegalArgumentException if the property is not supported * @throws NullPointerException if the name is null */ public Object getProperty(String name) throws IllegalArgumentException { return null; } }