New JSON support in Apache Axis2

Update: Moshi support is now included as an alternative to GSON, though both are supported and will continue to be. Both libs are very similar in features though Moshi is widely considered to have better performance. GSON development has largely ceased. Switching between Moshi and GSON is a matter of editing the axis2.xml file. For users of JSON and Spring Boot, see the sample application in the JSON and Spring Boot User's Guide.

This documentation explains how the existing JSON support in Apache Axis2 have been improved with two new methods named, Native approach and XML stream API based approach. Here it initially explains about the drawbacks of the old JSON support, how to overcome those drawbacks with the new approaches and, how to use those approaches to send pure JSON objects without converting it to any XML representations. XML Stream API based approach addresses the namespace problem with pure JSON support, and provides a good solution for that.

Introduction

Apache Axis2 is an efficient third generation SOAP processing web services engine. JSON (JavaScript Object Notation) is a lightweight data-interchange format and, an alternative for XML which is easily human readable and writable as well as it can be parsed and generated easily for machines.

The existing JSON implementation in Apache Axis2/Java supports badgerfish format of the JSON object, which is an XML representation of JSON object. With this approach, it first completely converts the badgerfish JSON string to the relevant XML format in the server side and, treats it as a normal SOAP message. The current JSON implementation also supports Mapped JSON, which is another XML representation of the JSON object, if we set xmlToJsonNamespaceMap parameter in the services.xml file of the service. The main problem with Mapped JSON format is, at the client side it is not aware of the namespaces which is used in server side to validate the request. Therefore with current implementation, it is required to do modifications to the existing services to use this mapped format support. The current JSON implementations of Axis2 are slower than the existing SOAP support too. Therefore the existing JSON support doesn't expose its advantages at all.

However this JSON support can be improved to support pure JSON objects without using any format to convert it into a XML, as JSON is a light weighted alternative to XML. Thre are two new approaches have been used with google-gson library, a rich library to convert a JSON string to a Java object and vice-versa, in order to improve the existing JSON support in Apache Axis2/java.

Gson[1] is a Java library that can be used to convert Java Objects into their JSON representation. It can be also used to convert a JSON string to an equivalent Java object. Gson can work with arbitrary Java objects including pre-existing objects for which you do not have the source code.

There are a few open source projects, capable of converting Java objects into JSON. However, most of them require to place Java annotations in all classes; something that cannot be done if we do not have access to the source code. Most of them also do not fully support the use of Java Generics. Gson has considered both of these facts as very important design goals and, the following are some of the advantages of using Gson to convert JSON into Java objects and vice-versa.

  • It provides simple toJSON() and fromJSON() methods to convert Java objects into JSON objects and vice-versa.
  • It allows pre-existing unmodifiable objects to be converted into/from JSON objects.
  • It has the extensive support of Java Generics.
  • It allows custom representations for objects.
  • It supports arbitrarily complex objects (with deep inheritance hierarchies and extensive use of generic types).

As mentioned above, these two new approaches have been introduced to overcome the above explained problems in the existing JSON implementation (Badgerfish and Mapped) of Axis2. The first one, the Native approach, has been implemented with completely pure JSON support throughout the axis2 message processing process while with the second approach which is XML stream API based approach, an XMLStreamReader/XMLStreamWriter implementation using google-gson with the help of XMLSchema is being implemented. The detailed description on the two approaches is given out in the next sections of the documentation.

Native Approach

With this approach you can expose your POJO service to accept pure JSON request other than converting to any representation or format. You just need to send a valid JSON string request to the service url and, in the url you should have addressed the operation as well as the service. Because in this scenario Axis2 uses URI based operation dispatcher to dispatch the correct operation. in here you can find the complete user guide for this native approach.

The Native approach is being implemented to use pure JSON throughout the axis2 message processing process. In Axis2 as the content-type header is used to specify the type of data in the message body, and depending on the content type, the wire format varies. Therefore, we need to have a mechanism to format the message depending on content type. We know that any kind of message is represented in Axis2 using AXIOM, and when we serialize the message, it needs to be formatted based on content type. MessageFormatters exist to do that job for us. We can specify MessageFormatters along with the content type in axis2.xml. On the other hand, a message coming into Axis2 may or may not be XML, but for it to go through Axis2, an AXIOM element needs to be created. As a result, MessageBuilders are employed to construct the message depending on the content type.

After building the message it gets pass through AXIS2 execution chain to execute the message. If the message has gone through the execution chain without having any problem, then the engine will hand over the message to the message receiver in order to do the business logic invocation. After this, it is up to the message receiver to invoke the service and send the response. So according to the Axis2 architecture to accomplish this approach it is required to implement three main components, a MessageBuilder, a MessageReceiver and a MessageFormatter, where in this Native implementation those are JSONBuilder, JSONRPCMessageReceiver and JSONFomatter, to handle the pure JSON request and invoke the service and return a completely pure JSON string as a response. In the builder, it creates and returns a dummy SOAP envelop to process over execution chain, while storing input json stream and a boolean flag IS_JSON_STREAM as a property of input MessageContext. The next one is JSONRPCMessageReceiver which is an extended subclass of RPCMessageReceiver. In here it checks IS_JSON_STREAM property value, if it is 'true' then it processes InputStream stored in input MessageContext, using JsonReader in google-gson API. Then it invokes request service operation by Gson in google-gson API which uses Java reflection to invoke the service. To write the response to wire, it stores the returned object and return java bean type, as properties of output MessageContext. If IS_JSON_STREAM is 'false' or null then it is handed over to its super class RPCMessageReceiver, in order to handle the request. This means, using JSONRPCMessageReceiver as the message receiver of your service you can send pure JSON messages as well as SOAP requests too. There is a JSONRPCInOnly-MessageReceiver which extends RPCInOnlyMessageReceiver class, to handle In-Only JSON requests too. In JSONformatter it gets return object and the java bean type, and writes response as a pure JSON string to the wire using JsonWriter in google-gson API. The native approach doesn’t support namespaces. If you need namespace support with JSON then go through XML Stream API based approach.

XML Stream API Base Approach

As you can see the native approach can only be used with POJO services but if you need to expose your services which is generated by using ADB or xmlbeans databinding then you need to use this XML Stream API based approach. With this approach you can send pure JSON requests to the relevant services. Similar to the native approach you need to add operation name after the service name to use uri based operation dispatch to dispatch the request operation. Here you can see the user guide for this XML Stream API based approach.

As mentioned in Native approach, Apache Axis2 uses AXIOM to process XML. If it can be implement a way to represent JSON stream as an AXIOM object which provides relevant XML infoset while processing JSON stream on fly, that would be make JSON, in line with Axis2 architecture and will support the services which have written on top of xmlstream API too.

There are a few libraries like jettison , Json-lib etc. which already provide this XMLStreaReader/XMLStreamWriter interfaces for JSON. There is no issue in converting JSON to XML, as we can use jettison for that, but when it comes to XML to JSON there is a problem. How could we identify the XML element which represent JSON array type? Yes we can identify it if there is two or more consecutive XML elements as jettison does, but what happen if the expected JSON array type has only one value? Then there is only one XML element. If we use Json-lib then xml element should have an attribute name called "class" value to identify the type of JSON. As you can see this is not a standard way and we cannot use this with Axis2 as well. Hence we can't use above libraries to convert XML to JSON accurately without distort expected JSON string even it has one value JSON array type.

Therefore with this new improvement Axis2 have it's own way to handle incoming JSON requests and outgoing JSON responses. It uses GsonXMLStreamReader and GsonXMLStreamWriter which are implementations of XMLStreamReader/XMLStreamWriter, to handle this requests and responses. To identify expected request OMElement structure and namespace uri, it uses XmlSchema of the request and response operations. With the XmlSchema it can provide accurate XML infoset of the JSON message. To get the relevant XMLSchema for the operation, it uses element qname of the message. At the MessageBuilder level Axis2 doesn't know the element qname hence it can't get the XmlSchema of the operation. To solve this issue Axis2 uses a new handler call JSONMessageHandler, which executes after the RequestURIOperationDispatcher handler. In the MessageBuilder it creates GsonXMLStreamReader parsing JsonReader instance which is created using inputStream and stores it in input MessageContext as a message property and returns a default SOAP envelop. Inside the JSONMessageHandler it checks for this GsonXMLStreamReader property, if it is not null and messageReceiver of the operation is not an instance of JSONRPCMessageReceiver, it gets the element qname and relevant XMLSchema list from the input MessageContext and pass it to GsonXMLStreamReader. After that it creates StAXOMBuilder passing this GsonXMLStreamReader as the XMLStreamReader and get the document element. Finally set this document element as child of default SOAP body. If Axis2 going to process XMLSchema for every request this would be a performance issue. To solve this, Axis2 uses an intermediate representation(XmlNode) of operation XmlSchema list and store it inside the ConfigurationContext with element qname as a property to use it for a next request which will come to the same operation. Hence it only processes XmlSchema only once for each operation.

Same thing happens in the JsonFormatter, as it uses GsonXMLStreamWriter to write response to wire and uses intermediate representation to identify the structure of outgoing OMElement. As we know the structure here we can clearly identify expected JSON response. Even expected JSON string have a JSON Array type which has only one value.

In addition, XML Stream API based approach supports namespace uri where it get namespaces from the operation XMLSchema and provides it when it is asked.