Since JSON formatted data is likely to be needed only when communicating with a partner system, we'll assume that it's most common to convert between XML and JSON in Oracle Service Bus. For working with XML, we'll use XMLBeans, as that's what OSB uses. For parsing and generating JSON, we'll use Jackson (Version 1.9.x).
This recipe assumes the use of an existing OSB configuration project within the OSB workshop for development. So, ensure that you have installed and familiarized yourself with it prior to beginning.
If you wish to follow along exactly with these instructions you will require a copy of the schema and WSDL files used in this recipe. Copies of these are included with the code samples for the book.
Oracle Service Bus uses XMLBeans to provide its XML/object mapping. We'll use the same library to make our task simpler.
In order to map between JSON and objects, we'll use the Jackson library. Download Version 1.9.x of the Jackson Core ASL and Mapper ASL libraries from http://wiki.fasterxml.com/JacksonDownload.
CreditCardServiceMessages
. Keep the remaining defaults and click on Next.CreditCardServiceMessages/build/classes
. Click on Finish.dist
and click on OK.genbuild
, gensrc
, lib
, resources
, and test
in the Java project.Next, we need to import the schema and WSDL files we are using for this example.
getting ready
, select the folder resources
.<MIDDLEWARE_HOME>/osb/modules
com.bea.core.xml.xmlbeans_2.1.0.0_2-5-1.jar
and click on Finish. This will create a copy of the JAR file in the lib
folder within the Java project.jackson-core-asl-1.9.x.jar
and jackson-mapper-asl-1.9.x.jar
libraries. Add the libraries to the projects build path by selecting the three files, right-clicking, and selecting Build Path | Add to Build Path.The structure of our Java Project should resemble the following screenshot:
getting ready
. Within the Import window, ensure build.xml is checked and click on Finish.The most important target to note at this point within the build.xml
file is scomp
.
<taskdef name="xmlbean" classname="org.apache.xmlbeans.impl.tool.XMLBean" classpath="${lib}/com.bea.core.xml.xmlbeans_2.1.0.0_2-5-1.jar" /> <!-- Compile the config schema definition with XmlBeans --> <target name="scomp" depends="init" description="compile xsd"> <xmlbean srcgendir="${gensrc}" classgendir="${genbuild}" destfile="${dist}/${ant.project.name}XmlBeans_1.0.jar" failonerror="true" classpathref="project.class.path"> <fileset dir="${schemadir}" includes="wsdl/CreditCardService.xsd" /> </xmlbean> </target>
This compiles the schemas we have imported using XmlBeans, and will be required for subsequent steps.
build.xml
file into the Ant View, and double-click on the scomp
target.This will run the scomp
target, which will use xmlbeans to generate Java classes which represent the schema types in our imported schemas and package them into the JAR file, named CreditCardServiceMessagesXmlBeans_1.0.jar
.
You should see output similar to the following, in the Console view:
The generated JAR file will be placed in the dist
directory (refresh the project structure view if you can't see it).
For our example we will create the following classes:
CreditCard
DebitCreditCard
DebitCreditCardResponse
An excerpt from the CreditCard
class is shown as follows:
package com.rubiconred.ckbk.creditcardsvc.pojo; public class CreditCard { private String cardType; private String cardHolderName; private String cardNumber; private Integer expiryMonth; private Integer expiryYear; private String securityNo; public String getCardType() { return cardType; } public void setCardType(String cardType) { this.cardType = cardType; } public String getCardHolderName() { return cardHolderName; } // … }
Primitive types are not used for the previous numeric values. It is common for values in JSON objects to be optional, and either be omitted entirely from the serialised representation, or be serialised with a value of null
. Java's primitive types cannot represent the absence of a value, so the object types should in general be used for numeric.
CreditCardServiceMapperFactory
that will be used to convert between the previous POJOs and their JSON representations.A snapshot of the code to do this is shown as follows. The full source code is provided in the sample for the chapter.
package com.rubiconred.ckbk.creditcardsvc.json; import … public class CreditCardServiceMapperFactory { private static ObjectMapper mapper; private static ObjectReader debitCreditCardReader; private static ObjectWriter debitCreditCardWriter; static { mapper = new ObjectMapper(); // Include null values in generated JSON mapper.setSerializationConfig(mapper.getSerializationConfig() .withSerializationInclusion(Inclusion.ALWAYS)); debitCreditCardReader = mapper.reader(DebitCreditCard.class); debitCreditCardWriter = mapper.writerWithType(DebitCreditCard.class); // … } public static ObjectReader getDebitCreditCardReader() { return debitCreditCardReader; } public static ObjectWriter getDebitCreditCardWriter() { return debitCreditCardWriter; } // … }
Jackson makes this very straightforward. The ObjectReader
and ObjectWriter
instances that we create are immutable, so they're thread-safe and can be shared as required.
The ObjectMapper
is the object on which the details of the JSON (de-) serialization are configured. For this example, we'll configure the mapper to include null
values, rather than omitting them.
Now that we have the necessary scaffolding in place, we can write the code that will convert between the XML format (exposed as an XMLBeans XmlObject
instance) and the JSON format (represented by the POJOs we created earlier). This is the code that will later be invoked using Java Callout actions in OSB proxy services.
ObjectReader
to parse it into the POJO we created earlier.The fields of the POJO are used to populate a new instance of the appropriate XmlObject
. This is illustrated in the following method:
public static XmlObject debitCreditCardJsonToXml(String json) { ObjectReader reader = CreditCardServiceMapperFactory .getDebitCreditCardReader(); DebitCreditCardDocument debitDoc = DebitCreditCardDocument .Factory.newInstance(); DebitCreditCard jsonDebitCreditCard; TDebitCreditCard xmlDebitCreditCard; TCreditCard xmlCreditCard; try { jsonDebitCreditCard = reader.readValue(json); CreditCard jsonCreditCard = jsonDebitCreditCard .getCreditCard(); xmlDebitCreditCard = TDebitCreditCard.Factory .newInstance(); xmlCreditCard = TCreditCard.Factory.newInstance(); xmlCreditCard.setCardHolderName(jsonCreditCard .getCardHolderName()); xmlCreditCard.setCardNumber(jsonCreditCard .getCardNumber()); xmlCreditCard.setCardType(jsonCreditCard .getCardType()); // Set Remainder of Credit Card Details… xmlDebitCreditCard.setCreditCard(xmlCreditCard); Double trnAmount =jsonDebitCreditCard.getTrnAmount(); if (trnAmount != null) { xmlDebitCreditCard.setTrnAmount( BigDecimal.valueOf(trnAmount)); } xmlDebitCreditCard.setTrnDesc(jsonDebitCreditCard .getTrnDesc()); debitDoc.setDebitCreditCard(xmlDebitCreditCard); } catch (JsonProcessingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return debitDoc; }
DebitCreditCardDocument doc; doc = (DebitCreditCardDocument) DebitCreditCardConverter .debitCreditCardJsonToXml(DEBIT_CREDIT_CARD_JSON_STRING); TDebitCreditCard debitCreditCard = doc.getDebitCreditCard();
Where DEBIT_CREDIT_CARD_JSON_STRING
contains the JSON to convert. The result of this debitCreditCard
is an XMLBeans generated class that gives us a Java wrapper around the ML conversion of debitCreditCard
with JavaBeans-style accessors.
XmlObject
and producing a String
containing JSON, as illustrated in the following method:public static String debitCreditCardXmlToJson(XmlObject xml) { ObjectWriter writer = CreditCardServiceMapperFactory .getDebitCreditCardWriter(); DebitCreditCard debitCreditCard = new DebitCreditCard(); String json = null; DebitCreditCardDocument debitCreditCardDoc; TDebitCreditCard source = null; XmlObject doc = null; try { doc = XmlObject.Factory.parse(xml.newXMLStreamReader()); if (doc instanceof DebitCreditCardDocument) { debitCreditCardDoc = (DebitCreditCardDocument) doc; source = debitCreditCardDoc.getDebitCreditCard(); TCreditCard sourceCC = source.getCreditCard(); BigDecimal trnAmount = source.getTrnAmount(); if (trnAmount != null) { debitCreditCard.setTrnAmount(trnAmount .doubleValue()); } debitCreditCard.setTrnDesc(source.getTrnDesc()); CreditCard creditCard = new CreditCard(); creditCard.setCardHolderName(sourceCC .getCardHolderName()); creditCard.setCardNumber(sourceCC.getCardNumber()); creditCard.setCardType(sourceCC.getCardType()); creditCard.setExpiryMonth(sourceCC.getExpiryMonth()); creditCard.setExpiryYear(sourceCC.getExpiryYear()); creditCard.setSecurityNo(sourceCC.getSecurityNo()); debitCreditCard.setCreditCard(creditCard); json = writer.writeValueAsString(debitCreditCard); } else { System.out.println("debitCreditCardXmlToJson(): PARSE FAILED!!!"); } } catch (XmlException e) { e.printStackTrace(); } catch (JsonGenerationException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return json;
XmlObject debitCreditCardXmlObject; debitCreditCardXmlObject = XmlObject.Factory .parse(DEBIT_CREDIT_CARD_XML); String json = DebitCreditCardConverter .debitCreditCardXmlToJson(debitCreditCardXmlObject);
Where DEBIT_CREDIT_CARD_XML
contains the XML to convert, the result of this JSON is a string containing the JSON representation of our XML object.
It is important to keep in mind, when implementing these conversions, that some incoming values may be null
. One example of such an issue is when working with numeric values parsed from XML by XMLBeans; they will typically be instances of BigDecimal
or BigInteger
. Should you want to assign these values to a Double
or Integer
in your own objects, you must ensure that the returned value is not null
before invoking the doubleValue()
or intValue()
methods.
Running the dist
Ant task will produce a JAR file CreditCardServiceMessages_1.0.jar
in the dist
directory. This will be used later, along with the previously generated CreditCardServiceMessagesXmlBeans_1.0.jar
, to perform the conversions between XML and JSON inside the OSB proxy services.
We use XMLBeans to parse and generate XML, and we use Jackson to parse and generate JSON. We then implement an adapter that takes care of the translation between the two representations.
It should be noted that there are other simpler frameworks which provide a mechanism for converting between XML and JSON, for example:
Given the simplicity of the previous example, any of these approaches would be fine. However, for more complex scenarios we find that Jackson provides the most control (it's also extremely fast – but that tends to be less relevant).
For example, Jackson provides many features for configuring the serialization and deserialization of your objects. In the event that the default behavior isn't appropriate for your use case, there are many options built-in, and custom (de-) serializers can be built very easily.
The final reason for leaning towards Jackson, is that it's leveraged by Coherence for its REST interface, so is a tried and tested component within the context of the Oracle stack.