Create Request and Reply Classes with JAXB Bindings
ZIP file containing Java code project for this example: JAXBBindingExample.zip
Topics containing code samples for this project: JAXBBindingExample.java and MessageAdjustments.java
Background
In Create Connections with URLConnection, the example created a connection, sent a message, and received a reply. But the example hard-coded the request and only printed the SOAP response to an output buffer. To create the SOAP messages and work with the responses, we can create bindings to the service description, the WSDL document, for the endpoint. WSDL (Web Services Description Language) documents are XML and a few techniques are readily available to bind XML to Java object. JAXB, Java Architecture for XML Binding, is an established technology for binding that comes with Java. Other ways to bind Java classes to XML include ADB, Axis 2 Data Binding, and XML Beans. These are Apache projects and as such are not a core part of Java. For now, we continue with core Java technologies.
Also, Java supports something called JAX-WS 2.0, which sits above JAXB in the web services API stack for Java. Although this can be useful, we will focus on JAXB bindings for the time being.
We will work with NetBeans IDE in this example, but analogous wizards are available in other IDEs and of course you don't have to use an IDE at all if that is your preferred development style. We already covered how to create a connection, and we laid the groundwork to separate the logic for the SOAP message string from the logic to create the connection and make the request in Accommodate HTTPS Connections.
Get Started
We will pull the HttpURLConnectionTechnique class into this example.
Shown here is a project for a Java application that contains a class with a main method and a copy of the class created in Accommodate HTTPS Connections.
Binding
We want to add a binding to a WSDL for a web service endpoint. To get the WSDL or the URL for the WSDL, we can use the generated Endpoint Reference. We will continue to use a BizOps endpoint to avoid complications. However, at this point, note that since we create bindings to endpoints, we have to create a binding for every endpoint we use. Many BizOps endpoints exist, but only one AppFxWebService endpoint is available. So this is a disadvantage of BizOps.
For example, in the generated reference for a local installation of an Infinity application web service endpoint hosted at:
http://localhost/bbAppFx/vpp/bizops/db[BBINFINITY]/codetables/ADDRESSTYPECODE/soap.asmx
A link with the text, Service Description, leads to the WSDL.
For the Infinity application BizOps endpoint, the WSDL can be viewed at the same URL as the endpoint qualified with ?WSDL. Let's use a NetBeans wizard to create a binding to this WSDL.
Right-click the project node in Projects and select New > JAXB Binding. You may have to navigate to Other... Also, you may have to configure NetBeans to handle this.
The New JAXB Binding screen appears.
Enter a Binding Name and a Package Name. Use the same name for now.
Warning: Don't use the default package name. The same default name will be the same for the BizOps endpoints.
Let's use AddressTypeCodeService. Now under Schema File, select Select From URL and enter the URL string for the WSDL. Remember to use percent-encoding for the square brackets.
Tip: When you cut and paste the endpoint URL for Business Operations Services, make sure to change the square brackets around the database name to %5B and %5D respectively. This is called percent-encoding.
In NetBeans, when you enter the WSDL URL, you may get the following message:
"WSDL" schema type is "experimental and not supported" by JAXB RI.
But the process works. Other documentation gives no indication of this.
Click Finish. The wizard creates the binding and generates the sources.
Copy the Binding and Examine the Classes
Notice that the generated code is organized under the package name that we specified but within a folder called Generated Sources (jaxb). Let's make a copy and add it to our Source Packages folder. We will make some minor modifications, and we don't want to overwrite them when the JAXB Bindings' sources are regenerated. Right-click the AddressTypeCodeService package and copy it to the Source Packages folder.
Then change the binding information so that the package name for the generated code is not the same. Right-click the binding node and select Change JAXB Options.
The Change JAXB options screen appears. It looks like the New screen you saw when you created the binding. Change the package name in Package Name to "AddressTypeCodeServiceGenerated." The bindings will regenerate, but your copied package will stay the same. Now let's look at the AddressTypeCodeService package we copied. Expand it.
You can see the correspondence between the WSDL and the generated classes. Let's open one. Double-click the GetTableEntryListRequest.java class file. Notice that it extends the more general BizOpsRequest class. The commenting gives you an idea about the markup in the source WSDL. There are some annotations that JAXB uses. Finally, there is a class with some basic methods in it. Keep an eye on those annotations. We'll come back to those.
@XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "GetTableEntryListRequest", propOrder = { "includeInactive", "refreshCache" })
Something else to look at is the ObjectFactory.java class. We'll use this to instantiate objects.
What Does Binding Give Us?
But what do we have now? What we have are Java artifacts that can be marshaled into XML. Furthermore, XML that corresponds to objects based on these classes can be unmarshaled into the objects. So you can work with business information in the Java object, present it and so forth. Then you can convert it to XML (SOAP) and send it to the web service. You can also receive XML(SOAP) from the web service, convert the message to Java objects and work with that. We're not all of the way there yet. So let's continue.
At this point, those classes should be accessible through auto-completion in the IDE editor. Try it out. Begin typing this into the main method in the JAXBBindingExample class:
AddressTypeCodeService.
Auto-completion should kick in.
But Be Careful - Create a Request Message Object
It is tempting to just start coding. But there are a few considerations here. One is that we need to instantiate the objects correctly in order for them to work the way JAXB intends. So suppose we want to create a request message with these classes. Here is how we do that.
AddressTypeCodeService.GetTableEntryListRequest listRequestObj = new AddressTypeCodeService.GetTableEntryListRequest(); AddressTypeCodeService.ObjectFactory objectFactoryAddTyCoSvc = new AddressTypeCodeService.ObjectFactory(); listRequestObj = objectFactoryAddTyCoSvc.createGetTableEntryListRequest();
First create an instance of AddressTypeCodeService.GetTableEntryListRequest. Then create the object factory. Then assign an object created with the object factory to the AddressTypeCodeService.GetTableEntryListRequest instance. You can create the object factory once at the beginning of your code block and reuse it if you will be calling methods from it more than once.
The point is to not just instantiate AddressTypeCodeService.GetTableEntryListRequest. Use the object factory. Follow the above pattern when you create instances of these objects. Refer to this explanation for more guidance: Java Architecture for XML Binding (JAXB).
Warning: You may also be tempted at this point to import AddressTypeCodeService into a class. The difficulty with this is that other BizOps endpoints will use many of the same names for classes and methods. So unless you only plan to use one endpoint in a given class, don't import. Although it is more verbose, fully qualify items from these generated classes.
Now that we have an object for a BizOps request that requests of lists of entries in the Address Type code table, we can work with the object itself. Remember, the we called the object listRequestObj. Create a new line and see what you get with that object.
Most of these classes contain Get and Set methods that let you manipulate that data within the object. We're just requesting a list. So we won't manipulate much here. But later on you will use Gets and Sets frequently.
JAXBContext and Marshaller
The program is going to need something called a JAXBContext for the message. Add this line:
JAXBContext contextForRequest = JAXBContext.newInstance (AddressTypeCodeService.GetTableEntryListRequest.class);
To support this line, you need to import javax.xml.bind.JAXBContext.
Class description: JAXBContext
You also need to handle JAXBException. So add these imports:
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException;
And add this throws clause to the main method: JAXBException. The IDE should help you along with that.
Now declare a Marshaller and set its properties to use JAXB formatted output:
Marshaller m = contextForRequest.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Interface description: Marshaller
You need another import:
import javax.xml.bind.Marshaller;
Now add code to marshal the Java object to XML. place the XML in a String.
Note: You can handle the XML in other ways, such as writing to a file or directly to a connection. But we are will do some adjusting, so placing it in a String is useful here.
StringWriter st = new StringWriter(); m.marshal(listRequestObj, st); String listRequestStr = st.toString();
An import for StringWriter:
import java.io.StringWriter;
Class description: StringWriter
Why We Made Copies
Just for to see how far along we are, add a line to output what is in the String and run the program:
System.out.println(listRequestStr);
You get an exception:
Exception in thread "main" javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.internal.SAXException2: unable to marshal type "AddressTypeCodeService.GetTableEntryListRequest" as an element because it is missing an @XmlRootElement annotation]
Remember how we made copies of the generated classes? This is why. Open the file for GetTableEntryListRequest in the package that you copied. Add this annotation:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "GetTableEntryListRequest", propOrder = {
"includeInactive",
"refreshCache"
})
@XmlRootElement(name = "GetTableEntryListRequest")
Also add the import for that annotation to the class file for GetTableEntryListRequest:
import javax.xml.bind.annotation.XmlRootElement;
Save everything and run it again. You should get this in the Output window:
run: <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <GetTableEntryListRequest xmlns="blackbaud_appfx_server_bizops"> <IncludeInactive>false</IncludeInactive> <RefreshCache>false</RefreshCache> </GetTableEntryListRequest> BUILD SUCCESSFUL (total time: 0 seconds)
Wrap the Message Fragment
So now we are close to a valid request message, but something is missing. We use a binding technique that does not wrap the message for us. In fact, we had to add the root annotation just to get the message to marshal. So we need to add the SOAP-specific XML to the message. We'll use SOAP 1.2 for everything, so we'll create a method that adds that wrapper for us.
Create a class file called MessageAdjustments.java. To that class, add this method:
public static String fixSOAPRequestMessage (String messageFragment) throws IOException { String lvMessageFragment = messageFragment.substring(56); String top = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">\n" + " <soap12:Body>\n"; String bottom = " </soap12:Body>\n" + "</soap12:Envelope>"; String wholeMessage = top + lvMessageFragment + bottom; return wholeMessage; }
This method grabs the message fragment minus its root element (<?xml...) and wraps it in SOAP Body and Envelope elements. it also adds an XML declaration back to the top of the XML document.
In the main method, after marshaling, apply this method to listRequestString:
listRequestStr = MessageAdjustments.fixSOAPRequestMessage(listRequestStr);
Now when you run the program, you're Output window should look like this:
run: <?xml version="1.0" encoding="utf-8"?> <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> <soap12:Body> <GetTableEntryListRequest xmlns="blackbaud_appfx_server_bizops"> <IncludeInactive>false</IncludeInactive> <RefreshCache>false</RefreshCache> </GetTableEntryListRequest> </soap12:Body> </soap12:Envelope> BUILD SUCCESSFUL (total time: 0 seconds)
Actually Send the Request and Get a Response
Now we have a valid SOAP message that we can send using our HttpURLConnectionTechnique.createHttpURLConnectionAndMakeRequest method. The method we built requires a String for the SOAP message and a String for the URL.
String urlString = "http://localhost/bbAppFx/vpp/bizops/db%5BBBINFINITY%5D/codetables/ADDRESSTYPECODE/soap.asmx"; String listReplyStr = HttpURLConnectionTechnique.createHttpURLConnectionAndMakeRequest (listRequestStr, urlString);
With this line, the program will send an HTTP request containing the SOAP request message we defined with Java objects. The program assigns the reply message that comes back to a String.
Add another output line and run the program:
System.out.println(listRequestStr); System.out.println("\nResponse message:\n"); System.out.println(listReplyStr);
The Output window should look like this:
run: <?xml version="1.0" encoding="utf-8"?> <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> <soap12:Body> <GetTableEntryListRequest xmlns="blackbaud_appfx_server_bizops"> <IncludeInactive>false</IncludeInactive> <RefreshCache>false</RefreshCache> </GetTableEntryListRequest> </soap12:Body> </soap12:Envelope> Response message: <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><GetTableEntryListReply xmlns="blackbaud_appfx_server_bizops"><StatusOK>true</StatusOK><StatusCode>200</StatusCode><StatusMessage>OK</StatusMessage><Rows><TableEntryListRow><ID>144f6283-b479-48fb-97fe-9444abb6d8a5</ID><Description>Billing</Description><Sequence>1</Sequence></TableEntryListRow><TableEntryListRow><ID>2e05bc62-2d51-47a5-b7a0-8ac0d4243caf</ID><Description>Business</Description><Sequence>2</Sequence></TableEntryListRow><TableEntryListRow><ID>578306f0-9b37-47d4-8185-05380b66cbc8</ID><Description>Employment History</Description><Sequence>3</Sequence></TableEntryListRow><TableEntryListRow><ID>46f09a15-665f-4517-b0d9-e5dee3db63ad</ID><Description>Former Address</Description><Sequence>4</Sequence></TableEntryListRow><TableEntryListRow><ID>e4906793-821f-49b9-b9b7-9bfefa6c6165</ID><Description>Home</Description><Sequence>5</Sequence></TableEntryListRow><TableEntryListRow><ID>b8b8d7a0-852c-4760-a25f-8eaeab6aba43</ID><Description>Invalid</Description><Sequence>6</Sequence></TableEntryListRow><TableEntryListRow><ID>3e495340-f4b7-488b-8669-53e742ebe30d</ID><Description>Relationship</Description><Sequence>7</Sequence></TableEntryListRow><TableEntryListRow><ID>8100ea3d-a5ea-48ef-bd01-f655464b5f1b</ID><Description>Rental Property</Description><Sequence>8</Sequence></TableEntryListRow><TableEntryListRow><ID>c0150cc4-b2ee-43d5-87fb-cfbd4738268a</ID><Description>Retreat</Description><Sequence>9</Sequence></TableEntryListRow><TableEntryListRow><ID>3fa6b4ca-7031-4ffd-9c84-5931c3d42abd</ID><Description>School Residence</Description><Sequence>10</Sequence></TableEntryListRow><TableEntryListRow><ID>de66c7fa-a24a-46b4-b4c0-9f863f4a1ac1</ID><Description>Shipping</Description><Sequence>11</Sequence></TableEntryListRow><TableEntryListRow><ID>22dd9406-5f74-472c-bb80-3c1e8ec527bc</ID><Description>Spouse Business</Description><Sequence>12</Sequence></TableEntryListRow><TableEntryListRow><ID>0701a2fc-9d35-4c55-96c8-5f18f7fa1b8c</ID><Description>Summer Home</Description><Sequence>13</Sequence></TableEntryListRow><TableEntryListRow><ID>06c30b72-d9f8-4590-92bf-f2e674c0cd30</ID><Description>Time Share</Description><Sequence>14</Sequence></TableEntryListRow><TableEntryListRow><ID>2d8e0e37-e93c-4e67-9080-55843c597f18</ID><Description>Vacation Home</Description><Sequence>15</Sequence></TableEntryListRow><TableEntryListRow><ID>8f76cb82-9f01-45c0-bf09-1041d00ae052</ID><Description>Winter Home</Description><Sequence>16</Sequence></TableEntryListRow></Rows><UserDefinedSort>true</UserDefinedSort></GetTableEntryListReply></soap:Body></soap:Envelope> BUILD SUCCESSFUL (total time: 1 second)
The response message doesn't have line breaks. But if you reformat it with the XML editor in Visual Studio, depending on the code table entries in your database, it looks like this (abridged):
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <GetTableEntryListReply xmlns="blackbaud_appfx_server_bizops"> <StatusOK>true</StatusOK> <StatusCode>200</StatusCode> <StatusMessage>OK</StatusMessage> <Rows> <TableEntryListRow> <ID>144f6283-b479-48fb-97fe-9444abb6d8a5</ID> <Description>Billing</Description> <Sequence>1</Sequence> </TableEntryListRow> <TableEntryListRow> <ID>2e05bc62-2d51-47a5-b7a0-8ac0d4243caf</ID> <Description>Business</Description> <Sequence>2</Sequence> </TableEntryListRow> ... <TableEntryListRow> <ID>8f76cb82-9f01-45c0-bf09-1041d00ae052</ID> <Description>Winter Home</Description> <Sequence>16</Sequence> </TableEntryListRow> </Rows> <UserDefinedSort>true</UserDefinedSort> </GetTableEntryListReply> </soap:Body> </soap:Envelope>
Unmarshal - Put the Reply into an Object
We still haven't made the full round trip to the Java objects. We need to unmarshal the XML into the object. As with marshaling, we have to make an adjustment for the SOAP-specific elements. Add this to the MessageAdjustments.java class file:
public static String stripSOAPReplyMessage (String messageFragment) throws IOException { String strippedMessage = messageFragment; String badString = "xsi:nil=\"true\""; strippedMessage = strippedMessage.replaceAll(badString, ""); strippedMessage = strippedMessage.substring(216, strippedMessage.length()-28); return strippedMessage; }
This strips the XML header and SOAP Envelope and Body elements from the message. Apply that to listReplyStr:
listReplyStr = MessageAdjustments.stripSOAPReplyMessage(listReplyStr);
We need to instantiate a reply object the same way we created the request object. But we can reuse the object factory.
AddressTypeCodeService.GetTableEntryListReply listReplyObj = new AddressTypeCodeService.GetTableEntryListReply(); listReplyObj = objectFactoryAddTyCoSvc.createGetTableEntryListReply();
Then we need the program to create a JAXBContext and an Unmarshaller.
JAXBContext contextForReply = JAXBContext.newInstance (AddressTypeCodeService.GetTableEntryListReply.class); Unmarshaller u = contextForReply.createUnmarshaller();
Interface description: Unmarshaller
To unmarshal, the program uses a StringBuilder and StreamSource:
StringBuilder xmlStr = new StringBuilder(listReplyStr); listReplyObj = (AddressTypeCodeService.GetTableEntryListReply) u.unmarshal (new StreamSource( new StringReader(xmlStr.toString())));
Class description: StringBuilder
Class description: StreamSource
All of these imports should appear in the class:
import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.transform.stream.StreamSource;
A (Small) Taste of How to Use the Objects
Let's also add a line to send information from the reply object to the Output window:
System.out.println(listRequestStr);
System.out.println("\nReply message:\n");
System.out.println(listReplyStr);
System.out.println("\nValue of reply message object:\n");
System.out.println
(listReplyObj.getRows().getTableEntryListRow().get(0).getID());
The reply object in this case maintains the rows from the reply. Those can be accessed with the getRows method. From the rows, a specific row can be obtained by index with getTableEntryListRow().get(0). From the at row the ID information for the entry can be grabbed with the getID method. This is just to show how you can get at information in the message objects. In practice, you will probably instantiate objects for specific parts of the message and use those within your client program where necessary.
ZIP file containing Java code for this example: JAXBBindingExample.