| <?xml version="1.0"?> | |
| <!-- | |
| ~ 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. | |
| --> | |
| <document xmlns="http://maven.apache.org/XDOC/2.0" | |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd"> | |
| <properties> | |
| <title>JSON Support in Axis2</title> | |
| </properties> | |
| <body> | |
| <h1>JSON Support in Axis2</h1> | |
| <section name="Introduction"> | |
| <p>Update: This documentation represents early forms of JSON | |
| conventions, Badgerfish and Mapped. GSON support was added a few | |
| years later. Moshi support is now included as an alternative to | |
| GSON. For users of JSON seeking modern features, see the <a href= | |
| "json_support_gson.html">JSON Support Guide.</a>. For users of | |
| JSON and Spring Boot 3, see the sample application in the <a href= | |
| "json-springboot-userguide.html">JSON and Spring Boot 3 User's Guide.</a> | |
| </p> | |
| <p>This document explains the JSON support implementation in Axis2. | |
| It includes an introduction to JSON, an outline as to why JSON | |
| support is useful to Axis2 and how it should be used. This document | |
| also provides details on test cases and samples.</p> | |
| </section> | |
| <section name="What is JSON?"> | |
| <p><a href="http://www.json.org/">JSON</a> (Java Script Object | |
| Notation) is another data exchangeable format like XML, but more | |
| lightweight and easily readable. It is based on a subset of the | |
| JavaScript language. Therefore, JavaScript can understand JSON, and | |
| it can make JavaScript objects by using JSON strings. JSON is based | |
| on key-value pairs and it uses colons to separate keys and values. | |
| JSON doesn't use end tags, and it uses braces (curly brackets) to | |
| enclose JSON Objects.</p> | |
| <p><font size="3">e.g. <font size="2"><root><test>json | |
| object</test></root> == | |
| {{json object}}</font></font></p> | |
| <p>When it comes to converting XML to JSON and vice versa, there | |
| are two major conventions, one named "<a href= | |
| "http://www.sklar.com/badgerfish/">Badgerfish</a>" and the other, | |
| Mapped. The main difference | |
| between these two conventions exists in the way they map XML | |
| namespaces into JSON.</p> | |
| <p><font size="3">e.g. <font size="2"><xsl:root | |
| xmlns:xsl="http://foo.com"><data>my json | |
| string</data></xsl:root></font></font></p> | |
| <p>This XML string can be converted into JSON as follows.</p> | |
| <p><b>Using Badgerfish</b></p> | |
| <p><font size= | |
| "2">{"xsl:root":{"@xmlns":{"xsl":"http://foo.com"},"data":{"$":"my | |
| json string"}}}</font></p> | |
| <p><b>Using Mapped</b></p> | |
| <p>If we use the namespace mapping as http://foo.com -> foo</p> | |
| <p><font size="2">{"foo.root":{"data":"my json string"}}</font></p> | |
| </section> | |
| <section name="Why JSON Support for Axis2?"> | |
| <p><a href="../index.html">Apache Axis2</a> is a Web | |
| services stack that delivers incoming messages into target | |
| applications. In most cases, these messages are SOAP messages. In | |
| addition, it is also possible to send REST messages through Axis2. | |
| Both types of messages use XML as their data exchangeable format. | |
| So if we can use XML as a format, why use JSON as another | |
| format?</p> | |
| <p>There are many advantages of implementing JSON support in Axis2. | |
| Mainly, it helps the JavaScript users (services and clients written | |
| in JavaScript) to deal with Axis2. When the service or the client | |
| is in JavaScript, it can use the JSON string and directly build | |
| JavaScript objects to retrieve information, without having to build | |
| the object model (OMElement in Axis2). Also, JavaScript services | |
| can return the response through Axis2, just as a JSON string can be | |
| shipped in a JSONDataSource.</p> | |
| <p>Other than for that, there are some extra advantages of using | |
| JSON in comparison to XML. Although the conversation | |
| XML or JSON? is still a hot topic, | |
| many people accept the fact that JSON can be passed and built more | |
| easily by machines than XML.</p> | |
| </section> | |
| <section name="How to use JSON in Axis2"> | |
| <p>At the moment JSON doesn't have a standard and unique content | |
| type. <tt>application/json</tt> (this is | |
| the content type which is approved in the <a href= | |
| "http://www.ietf.org/rfc/rfc4627.txt">JSON RFC</a>), | |
| <tt>text/javascript</tt> and | |
| <tt>text/json</tt> are some of the commonly | |
| used content types for JSON. Fortunately, in Axis2, the user | |
| has the freedom of specifying the content type to use.</p> | |
| <subsection name="Configuring axis2.xml"> | |
| <p>First of all, you need to map the appropriate message formatters and builders to the | |
| content type you are using in the <tt>axis2.xml</tt> file. This applies both the to | |
| client side and the server side.</p> | |
| <p>E.g., if you are using the | |
| Mapped convention with the content | |
| type <tt>application/json</tt>, add the following declaration:</p> | |
| <pre><![CDATA[ | |
| <messageFormatters> | |
| <messageFormatter contentType="application/json" | |
| class="org.apache.axis2.json.JSONMessageFormatter"/> | |
| <!-- more message formatters --> | |
| </messageFormatters> | |
| <messageBuilders> | |
| <messageBuilder contentType="application/json" | |
| class="org.apache.axis2.json.JSONOMBuilder"/> | |
| <!-- more message builders --> | |
| </messageBuilders> | |
| ]]></pre> | |
| <p>If you are using the | |
| Badgerfish convention with the | |
| content type <tt>text/javascript</tt>, add:</p> | |
| <pre><![CDATA[ | |
| <messageFormatters> | |
| <messageFormatter contentType="text/javascript" | |
| class="org.apache.axis2.json.JSONBadgerfishMessageFormatter"> | |
| <!-- more message formatters --> | |
| </messageFormatters> | |
| <messageBuilders> | |
| <messageBuilder contentType="text/javascript" | |
| class="org.apache.axis2.json.JSONBadgerfishOMBuilder"/> | |
| <!-- more message builders --> | |
| </messageBuilders> | |
| ]]></pre> | |
| </subsection> | |
| <subsection name="Client-side configuration"> | |
| <p>On the client side, make the ConfigurationContext by reading the | |
| axis2.xml in which the correct mappings are given.</p> | |
| <p>e.g.</p> | |
| <pre> | |
| File configFile = new File("test-resources/axis2.xml"); | |
| configurationContext = ConfigurationContextFactory | |
| .createConfigurationContextFromFileSystem(null, configFile.getAbsolutePath()); | |
| .......... | |
| ServiceClient sender = new ServiceClient(configurationContext, null); | |
| </pre> | |
| <p>Set the <i>MESSAGE_TYPE</i> option with exactly the same content | |
| type you used in the axis2.xml.</p> | |
| <p>e.g. If you use the content type | |
| application/json,</p> | |
| <pre> | |
| Options options = new Options(); | |
| options.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/json"); | |
| //more options | |
| //................... | |
| ServiceClient sender = new ServiceClient(configurationContext, null); | |
| sender.setOptions(options); | |
| </pre> | |
| <p>If you are sending a request to a remote service, you have to | |
| know the exact JSON content type that is used by that service, and | |
| you have to use that content type in your client as well.</p> | |
| <p>HTTP POST is used as the default method to send JSON messages | |
| through Axis2, if the HTTP method is not explicitly set by the | |
| user. But if you want to send JSON in HTTP GET method as a | |
| parameter, you can do that by just setting an option on the client | |
| side.</p> | |
| <p>e.g.</p> | |
| <code>options.setProperty(Constants.Configuration.HTTP_METHOD, | |
| Constants.Configuration.HTTP_METHOD_GET);</code> | |
| <p>Here, the Axis2 receiving side (JSONOMBuilder) builds the | |
| OMElement by reading the JSON string which is sent as a parameter. | |
| The request can be made even through the browser.</p> | |
| <p>e.g. Sample JSON request through HTTP GET. The JSON message is | |
| encoded and sent.</p> | |
| <code>GET | |
| /axis2/services/EchoXMLService/echoOM?query=%7B%22echoOM%22:%7B%22data%22:%5B%22my%20json%20string%22,%22my%20second%20json%20string%22%5D%7D%7D | |
| HTTP/1.1</code> | |
| </subsection> | |
| <subsection name="Server-side configuration"> | |
| <p>Since Badgerfish defines a 1-to-1 transformation between JSON and XML, no additional configuration | |
| is required on the server side if that convention is used. Any service deployed into Axis2 will work | |
| out of the box.</p> | |
| <p>On the other hand, if the Mapped JSON convention is used, then Axis2 needs to know the mappings | |
| between XML namespaces and JSON "namespaces" in order to translate messages from JSON | |
| into XML representations and vice-versa. To use the Mapped convention with a service deployed into Axis2, | |
| add a <tt>xmlToJsonNamespaceMap</tt> property with these mappings to the <tt>services.xml</tt> file for that service, as | |
| shown in the following example:</p> | |
| <pre><![CDATA[ | |
| <service name="..."> | |
| ... | |
| <parameter name="xmlToJsonNamespaceMap"> | |
| <mappings> | |
| <mapping xmlNamespace="http://example.org/foo" jsonNamespace=""/> | |
| <mapping xmlNamespace="http://example.org/bar" jsonNamespace="bar"/> | |
| </mappings> | |
| </parameter> | |
| ... | |
| </service> | |
| ]]></pre> | |
| </subsection> | |
| </section> | |
| <section name="How the JSON implementation works - Architecture"> | |
| <subsection name="Introduction"> | |
| <p>The Axis2 architecture is based on the assumption that any message flowing through | |
| the Axis2 runtime is representable as a SOAP infoset, i.e. as XML wrapped in a SOAP | |
| envelope. Conceptually, the two message builders <code>JSONOMBuilder</code> and | |
| <code>JSONBadgerfishOMBuilder</code> convert incoming messages from JSON to XML and | |
| the two message formatters <code>JSONMessageFormatter</code> and <code>JSONBadgerfishMessageFormatter</code> | |
| convert outgoing messages from XML to JSON. Axis2 doesn't implement its own JSON parser and serializer, and | |
| instead relies on <s><a href="http://jettison.codehaus.org/">Jettison</a></s> <em>(Codehaus hosting no longer accessible - project moved to <a href="https://github.com/jettison-json/jettison">GitHub</a>)</em> to do the JSON<->XML conversions.</p> | |
| <p>On the server side the XML for an incoming | |
| message is typically converted to Java objects by a databinding (such as ADB or JAX-WS) | |
| before the invocation of the service implementation. In the same way, the Java object returned by the | |
| service implementation is converted to XML. In the case we are interested in, that XML is then converted | |
| by the message formatters to JSON. The usage of an intermediate XML representation is the reason why | |
| JSON can be enabled on any service deployed in Axis2.</p> | |
| <p>It is important to note that the explanation given in the previous two paragraphs is only valid from | |
| a conceptual point of view. The actual processing model is more complicated. In the next two sections | |
| we will explain in detail how Axis2 processes incoming and outgoind JSON messages.</p> | |
| </subsection> | |
| <subsection name="Processing of incoming JSON messages"> | |
| <p>Axis2 relies on <a href="http://ws.apache.org/axiom/">Apache Axiom</a> as its XML object model. Although | |
| Axiom has a DOM like API, it also has several advanced features that enable Axis2 to avoid | |
| building a complete object model representation of the XML message. This is important for performance | |
| reasons and distinguishes Axis2 from previous generation SOAP stacks. To leverage these features, the | |
| JSON message builders create a SOAP envelope the body of which contains a single <code>OMSourcedElement</code>.</p> | |
| <p>An <code>OMSourcedElement</code> is a special kind of <code>OMElement</code> that wraps an arbitrary | |
| Java object that can be converted to XML in a well defined way. More precisely, the Java object as well as the logic | |
| to convert the object to XML are encapsulated in an <code>OMDataSource</code> instance and it is that | |
| <code>OMDataSource</code> instance that is used to create the <code>OMSourcedElement</code>. | |
| For JSON, the <code>OMDataSource</code> implementation is <code>JSONDataSource</code> or <code>JSONBadgerfishDataSource</code>, | |
| depending on the convention being used. The base class (<code>AbstractJSONDataSource</code>) of these two classes | |
| actually contains the code that invokes Jettison to perform the JSON to XML conversion.</p> | |
| <p>An <code>OMSourcedElement</code> still behaves like a normal <code>OMElement</code>. In particular, if the | |
| element is accessed using DOM like methods, then Axiom will convert the data encapsulated by | |
| the <code>OMDataSource</code> on the fly to an object model representation. This process is called <i>expansion</i> of the | |
| <code>OMSourcedElement</code>. However, the <code>OMDataSource</code> API is designed such that the conversion to | |
| XML is always done using a streaming API: either the <code>OMDataSource</code> produces an <code>XMLStreamReader</code> | |
| instance from which the XML representation can be read (this is the case for JSON and the <code>XMLStreamReader</code> implementation | |
| is actually provided by Jettison) or it serializes the XML representation to an <code>XMLStreamWriter</code>. | |
| Because of this, expansion of the <code>OMSourcedElement</code> is often not necessary, so that the overhead of | |
| creating an object model representation can usually be avoided. E.g. a databinding will typically consume the message by requesting an | |
| <code>XMLStreamReader</code> for the element in the SOAP body, and this doesn't require expansion of the | |
| <code>OMSourcedElement</code>. In this case, the databinding pulls the XML data almost directly from the | |
| underlying Jettison <code>XMLStreamReader</code> and no additional Axiom objects are created.</p> | |
| <p>Actually here again, things are slightly more complicated because in order to dispatch to the right | |
| operation, Axis2 needs to determine the name of the element in the body. Since the name is not known | |
| in advance, that operation requires expansion of the <code>OMSourcedElement</code>. However, at this point | |
| none of the children of the <code>OMSourcedElement</code> will be built. Fortunately the databindings | |
| generally request the <code>XMLStreamReader</code> with caching turned off, so that the child nodes will never be | |
| built. Therefore the conclusion of the previous paragraph remains valid: processing the message with a databinding | |
| will not create a complete object model representation of the XML.</p> | |
| <p>Usage of an <code>OMSourcedElement</code> also solves another architectural challenge posed by | |
| the Mapped JSON convention: the JSON payload can only be converted to XML if the namespace mappings | |
| are known. Since they are defined per service, they are only known after the incoming message has been | |
| dispatched and the target service has been identified. This typically occurs | |
| in <code>RequestURIBasedDispatcher</code>, which is executed after | |
| the message builder. This means that <code>JSONOMBuilder</code> cannot actually perform the conversion. | |
| Usage of an <code>OMSourcedElement</code> avoids this issue because the conversion is done lazily when | |
| the <code>OMSourcedElement</code> is first accessed, and this occurs after <code>RequestURIBasedDispatcher</code> | |
| has been executed.</p> | |
| <p>Another advantage of using <code>OMSourcedElement</code> is that a JSON aware service could directly process | |
| the JSON payload without going through the JSON to XML conversion. That is possible because the <code>OMDataSource</code> | |
| simply keeps a reference to the JSON payload and this reference is accessible to JSON aware code.</p> | |
| </subsection> | |
| <subsection name="Processing of outgoing messages"> | |
| <p>For outgoing messages, the two JSON message formatters <code>JSONMessageFormatter</code> and | |
| <code>JSONBadgerfishMessageFormatter</code> use Jettision to create an appropriate <code>XMLStreamWriter</code> | |
| and then request Axiom to serialize the body element to that <code>XMLStreamWriter</code>. If a databinding | |
| is used, then the body element will typically be an <code>OMSourcedElement</code> with an <code>OMDataSource</code> | |
| implementation specific to that databinding. <code>OMSourcedElement</code> will delegate the serialization | |
| request to the appropriate method defined by <code>OMDataSource</code>. This means that the databinding code | |
| directly writes to the <code>XMLStreamWriter</code> instance provided by Jettision, without building an | |
| intermediate XML object model.</p> | |
| <p>Before doing this, the JSON message formatters actually check if the element is an <code>OMSourcedElement</code> | |
| backed by a corresponding JSON <code>OMDataSource</code> implementation. If that is the case, then they will | |
| extract the JSON payload and directly write it to the output stream. This allows JSON aware services to | |
| bypass the XML to JSON conversion entirely.</p> | |
| </subsection> | |
| </section> | |
| <section name="Tests and Samples"> | |
| <subsection name="Integration Test"> | |
| <p>The JSON integration test is available under | |
| test in the | |
| json module of Axis2. It uses the | |
| SimpleHTTPServer to deploy the service. A simple echo service is | |
| used to return the incoming OMSourcedElement object, which | |
| contains the JSONDataSource. There are two test cases for two | |
| different conventions and another one test case to send the request | |
| in GET.</p> | |
| </subsection> | |
| <subsection name="Yahoo-JSON Sample"> | |
| <p>This sample is available in the | |
| samples module of Axis2. It is a | |
| client which calls the Yahoo search API using the GET method, with | |
| the parameter output=json. The | |
| Yahoo search service sends the response as a | |
| formatted JSON string with | |
| the content type text/javascript. | |
| This content type is mapped with the JSONOMBuilder in the | |
| axis2.xml. All the results are shown in a GUI. To run the sample, | |
| execute the ant script.</p> | |
| <p>These two applications provide good examples of using JSON | |
| within Axis2. By reviewing these samples, you will be able to | |
| better understand Axis2's JSON support implementation.</p> | |
| </subsection> | |
| <subsection name="Enabling mapped JSON on the ADB quickstart sample"> | |
| <p>To illustrate how JSON can be enabled on an existing service deployed in Axis2, | |
| we will use the ADB stock quote service sample from the | |
| <a href="quickstartguide.html#adb">Quick Start Guide</a>. The code for this sample | |
| can be found in the <tt>samples/quickstartadb</tt> folder in the binary distribution.</p> | |
| <p>Only a few steps are necessary to enable JSON (using the Mapped convention) on | |
| that service:</p> | |
| <ol> | |
| <li> | |
| <p>Configure the JSON message builders and formatters in <tt>conf/axis2.xml</tt>. | |
| Add the following element to the <tt>messageFormatters</tt>:</p> | |
| <pre><![CDATA[ | |
| <messageFormatter contentType="application/json" | |
| class="org.apache.axis2.json.JSONMessageFormatter"/> | |
| ]]></pre> | |
| <p>Also add the following element to the <tt>messageBuilders:</tt></p> | |
| <pre><![CDATA[ | |
| <messageBuilder contentType="application/json" | |
| class="org.apache.axis2.json.JSONOMBuilder"/> | |
| ]]></pre> | |
| </li> | |
| <li> | |
| <p>Edit the <tt>services.xml</tt> for the stock quote service and add the following | |
| configuration:</p> | |
| <pre><![CDATA[ | |
| <parameter name="xmlToJsonNamespaceMap"> | |
| <mappings> | |
| <mapping xmlNamespace="http://quickstart.samples/xsd" jsonNamespace=""/> | |
| </mappings> | |
| </parameter> | |
| ]]></pre> | |
| <p>The <tt>services.xml</tt> file can be found under | |
| <tt>samples/quickstartadb/resources/META-INF</tt>.</p> | |
| </li> | |
| <li> | |
| <p>Build and deploy the service by executing the ant script in | |
| <tt>samples/quickstartadb</tt> and then start the Axis2 server using | |
| <tt>bin/axis2server.sh</tt> or <tt>bin/axis2server.bat</tt>.</p> | |
| </li> | |
| </ol> | |
| <p>That's it; the stock quote service can now be invoked using JSON. This can be tested | |
| using the well known <a href="http://curl.haxx.se/">curl</a> tool:</p> | |
| <pre>curl -H 'Content-Type: application/json' -d '{"getPrice":{"symbol":"IBM"}}' http://localhost:8080/axis2/services/StockQuoteService</pre> | |
| <p>This will give the following result:</p> | |
| <pre>{"getPriceResponse":{"return":42}}</pre> | |
| </subsection> | |
| </section> | |
| </body> | |
| </document> |