blob: 813ca400be8d61f5fe7dda18e41adc1bb3275c07 [file] [log] [blame]
/*
* 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.moshi;
import com.squareup.moshi.JsonWriter;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.json.moshi.factory.JSONType;
import org.apache.axis2.json.moshi.factory.JsonConstant;
import org.apache.axis2.json.moshi.factory.JsonObject;
import org.apache.axis2.json.moshi.factory.XmlNode;
import org.apache.axis2.json.moshi.factory.XmlNodeGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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 MoshiXMLStreamWriter implements XMLStreamWriter {
Log log = LogFactory.getLog(MoshiXMLStreamWriter.class);
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 MoshiXMLStreamWriter(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 ex) {
log.error(ex.getMessage(), ex);
throw new XMLStreamException("Bad Response");
}
}
/**
* 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 {
// MoshiXMLStreamReader 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 MoshiNamespaceContext();
}
/**
* 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;
}
}