Overview
As of version 6, Java Standard Edition (“Mustang”) now offers web service capabilities out of the box in much the same way as RMI. Mustang includes:
- JAX-WS 2.0 – a means of declaring a POJO to be a web service using annotations such as @WebService and @WebMethod. JSR 224 – Java API for XML-Based Web Services (JAX-WS 2.0) relies heavily on JSR 175 – A Metadata Facility for the Java Programming Language and JSR 181 – Web Services Metadata for the Java Platform.
- JAXB 2.0 – a means of serializing between Java and XML. See: JSR 222 – Java Architecture for XML Binding (JAXB) 2.0.
- Endpoint API – a means of publishing a web service via the built-in light-weight HTTP server.
Background
I recently used Java SE 6 to expose an existing stand-alone pricing application as a web service for use throughout an investment bank. For various reasons, the application was not running as a JEE application, nor was it running within any other container. It was simply exposed as an RMI service using Java SE.
Mustang allowed me to turn the existing application into a web service by annotating existing code with JAX-WS annotations. I then published the service via the Endpoint API. Whilst this was very easy, I did encounter a few sticky points which I discuss in the Additional Notes section below.
Links
I provide here some links I collated when implementing this service. Hopefully these will be useful to anyone developing a web service using Java SE 6.
- Java SE 6 Key Features – Lists the key features of Java SE 6. Your starting point to understanding Mustang.
- Introducing JAX-WS 2.0 With the Java SE 6 Platform, Part 1 – Introduction to POJO annotated web services using JAX-WS and the Endpoint API, from the source (Sun).
- Web Services in JDK 6 – A concise tutorial instructing how to annotate a POJO as a web service using JAX-WS annotations and JAXB bindings.
- Web Service Endpoints in Mustang – Another tutorial demonstrating how to expose a POJO as a web service via Java SE 6. In addition, this tutorial goes further to explain how to then expose this via the Endpoint API.
- Java WS Articles on Java.net twiki/wiki – Further articles on java.net twiki about JAX-WS, etc.
- Mustang: The fast track to Web services – A JavaWorld article providing an overview of the web service features of Java SE 6 and a tutorial.
- The Unofficial JAXB Guide – This is invaluable – you’ll see why later.
Additional Notes
A couple of additional observations from my experience implementing a web service using Mustang.
Programming to Interfaces
JAXB doesn’t like interfaces. If you need to expose an object using its interface you’ll need to tell JAXB where to find an implementation of your interface when deserializing.
An example of this problem is when you specify that your web service method (operation) accepts implementations of a given interface, say Loan (interface). You must tell JAXB what implementation of Loan to deserialize to.
// LoanManagerWebService.java
@WebService
public class LoanManagerWebService {
@WebMethod
public Price priceLoan(Loan loan) {
// Implementation
}
}
The easiest way to do this is to annotate the Loan interface with the following annotation:
// Loan.java
@XmlJavaTypeAdapter(LoanAdapter.class)
public interface Loan {
// Definition of a Loan
}
…and create a LoanAdapter:
// LoanAdapter.java
public class LoanAdapter extends XmlAdapter<Loan,LoanBean> {
LoanBean unmarshal(Loan v) {
return (LoanBean) v;
}
Loan marshal(LoanBean v) {
return v;
}
}
…which adapts Loan XML to a specific Loan implementation, here a LoanBean:
// LoanBean.java
public class LoanBean implements Loan {
// Implementation of a Loan
}
This section of the unofficial JAXB guide will help enormously with this and related issues: http://jaxb.dev.java.net/guide/Mapping_interfaces.html. Additionally, this tutorial is also very helpful: Using JAXB 2.0′s XmlJavaTypeAdapter.
Lists versus Arrays
Everyone (or at least most people) know this already. I’ll restate it since it caught me out… Web services and lists (or any sort of language-specific collection) don’t mix well. Web services only like language-agnostic arrays.
How did I manage to make such a school-boy error? Well, I built a solution anticipating usage only via Java and exposed it only as an RMI service. When the need to access it from .NET later emerged (much later), I had the pleasure of rewriting large portions of my input and output object graphs (the public API) to use arrays rather than Lists.
I used Lists in the first place because they are more dynamic and easier to work with when building the object graph. Should I have better forward planned, or would doing so have been an example of over-engineering? I’ll certainly think twice about it next time.
This is clearly something to bear in mind when you have a need to expose an existing application as a web service – JAX-WS and JAXB help enormously, but good (or at least, appropriate) design is still key.
Java Beans
JAXB offers ease-of-use at a cost of conformity to the Java Bean standard. Any object that must be serialized by JAXB (i.e. any object in your public object graph sent to or from the web service operation) must have a public no argument constructor and provide public getters and setters for the properties you wish to get or set from the web service client.
Exceptions
Java exceptions don’t transport well across web service boundaries. This is again a pretty obvious point when you stop and think about it, but it is an issue that only became apparent after I built a C# .NET client to the web service. Throwing a well-defined exception, such as LoanPricingException or InvalidLoanIdentifierException, only results in a general WebException being received by the web service client. This WebException does not contain the well-chosen human-readable message you may have put into an exception either. Nor is the server-side exception accessible as part of or the root of the WebException thrown by the client-side proxy. Basically, all that is known is that something went wrong.
I’m not 100% sure I didn’t miss a trick, but I’m pretty confident I implemented the web service as instructed in all the tutorials. Time being a crucial factor for my client and on consulting fellow colleagues who had encountered similar problems previously, I opted for the path of least resistance. I created a result object that contained any non-catastrophic business errors that occurred on the server-side and/or the actual result of the web service operation, as a payload within the result object. Any business exceptions that occurred were caught and added to the result object. I still added a throws WebServiceException to the web service method for catastrophic web-service-related (i.e. infrastructure) exceptions.
// WebServiceError.java
public class WebServiceError {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
// WebServiceResult.java
public abstract class WebServiceResult {
private WebServiceError[] errors;
public WebServiceError[] getErrors() {
return errors;
}
public void setErrors(WebServiceError[] errors) {
this.errors = errors;
}
}
// WebServicePriceResult.java
public class WebServicePriceResult extends WebServiceResult {
private Price price;
public Price getPrice() {
return price;
}
public void setPrice(Price price) {
this.price = price;
}
}
// LoanManagerWebService.java
@WebService
public class LoanManagerWebService {
private LoanManager loanManager = new LoanManager();
@WebMethod
public WebServicePriceResult priceLoan(Loan loan) throws WebServiceException {
WebServicePriceResult result = new WebServicePriceResult();
List<WebServiceError> errors = new ArrayList<WebServiceError>();
try {
Price price = loanManager.priceLoan(loan);
result.setPrice(price);
} catch (Exception e) {
WebServiceError error = new WebServiceError();
error.setMessage(e.getMessage());
errors.add(error);
}
if (!errors.isEmpty()) {
result.setErrors(errors.toArray(new WebServiceError[0]));
}
return result;
}
}
Like I said, perhaps I wasn’t using the web service Fault correctly (it certainly looked OK in the WSDL, etc.), but it does make sense to me that something language-specific, such as the make-up of a complex Java Exception object, won’t be common across platforms. Therefore, the sensible and logical design idioms to apply are composition and encapsulation – encapsulating the errors into a complex object that can be transported and that the service doesn’t rely on a language-specific Exception control-flow.
This is an area that certainly interests me and one which I need to look into more… I don’t like things getting the better of me like this.
Posted on May 4, 2009