I'm working on a project revamp for a company, where they want to split their system between front-end/client and back-end/server (more like a middleman between the front-end and the database server), and I'm supposed to use JAX-WS RPC and maintain the current functionality.
By maintaining functionality they mean that some methods are supposed to return null, which is forbidden by WS-I.
Searching for possible solutions, I stumbled upon this article: http://victor-ichim.blogspot.com.br/2011/03/rpcliteral-and-null-object-pattern.html which basically solves a similar problem by using EJB Interceptors to intercept and replace null results with empty objects.
Working around the concept, I thought of intercepting the results just like so, replacing null with something like a string template, intercepting it again on the client and replacing that template back with null.
My questions are:
Since I also had problems with JAXB not handling interfaces, what I ended up doing was using the @XmlJavaTypeAdapter
annotation to enable (selectively, since every return and parameter that could practically be null needs to be annotated) converting values from and back to null, in sort of a hackjob manner. I created a generic-ish adapter for Serializable
objects, and followed the same sort of approach for other kinds of Object
s:
public class SerializableAdapter extends XmlAdapter<String, Serializable>>{
private static final String NULL = "'NULL'"; // Will hopefully never collide
@Override
public Serializable unmarshal(String e) throws Exception {
if (e == NULL) {
return null;
}
byte [] eB = e.getBytes("ISO-8859-1");
InputStream iS = new ByteArrayInputStream(Base64.getDecoder().decode(eB));
ObjectInputStream oIS = new ObjectInputStream(iS);
return (Serializable) oIS.readObject();
}
@Override
public String marshal(Serializable o) throws Exception {
if (o == null) {
return NULL;
}
ByteArrayOutputStream bAOS = new ByteArrayOutputStream();
ObjectOutputStream oOS = new ObjectOutputStream(bAOS);
oOS.writeObject(o);
return Base64.getEncoder().encodeToString(bAOS.toByteArray());
}
}
And then annotated every Serializable
instance with @XmlJavaTypeAdapter(SerializableAdapter.class)
since using package-level @XmlJavaTypeAdapters
didn't work for some reason, and so forth for other cases. JAXB seems to eagerly cast the types encoded to and from when calling the adapters, so it will compile just fine even if the object to be marshalled isn't an instance of the expected class/interface, and throw exceptions only at runtime.
I don't recommend doing it this way, since it will require annotating every single method/parameter or package, and will break at the first one that didn't get annotated and yet received null. This very adapter still serves a purpose for cases where I need to work with interfaces, and the implementing classes also implement Serializable, although there are cases that still need specific adapters, but that's usually badly thought-out code.
Partially because of the hackness of this and the hassle of annotating everything, I managed to convince the company to move away from SOAP RPC bindings, so I was able to have null parameters and returns without this.