blob: a21fc3932a8e4f2ef9c53d9a68a52bc2f6deff4c [file] [log] [blame]
<?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 <a href=
"json_support_gson.html">JSON Support Guide.</a>. For users of
JSON and Spring Boot, see the sample application in the <a href=
"json-springboot-userguide.html">JSON and Spring Boot 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">&lt;root&gt;&lt;test&gt;json
object&lt;/test&gt;&lt;/root&gt; ==
{{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">&lt;xsl:root
xmlns:xsl="http://foo.com"&gt;&lt;data&gt;my json
string&lt;/data&gt;&lt;/xsl:root&gt;</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 -&gt; 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 <a href="http://jettison.codehaus.org/">Jettison</a> to do the JSON&lt;->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>