I need to make a Rest POST to a service that returns either a <job/>
or an <exception/>
and always status code 200
. (lame 3rd party product!).
I have code like:
Job job = getRestTemplate().postForObject(url, postData, Job.class);
And my applicationContext.xml looks like:
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg ref="httpClientFactory"/>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="jaxbMarshaller"/>
<property name="unmarshaller" ref="jaxbMarshaller"/>
</bean>
<bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
</list>
</property>
</bean>
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>domain.fullspec.Job</value>
<value>domain.fullspec.Exception</value>
</list>
</property>
</bean>
When I try to make this call and the service fails, I get:
Failed to convert value of type 'domain.fullspec.Exception' to required type 'domain.fullspec.Job'
In the postForObject() call, I am asking for a Job.class and not getting one and it is getting upset.
I am thinking I need to be able to do something along the lines of:
Object o = getRestTemplate().postForObject(url, postData, Object.class);
if (o instanceof Job.class) {
...
else if (o instanceof Exception.class) {
}
But this doesnt work because then JAXB complains that it doesnt know how to marshal to Object.class - not surprisingly.
I have attempted to create subclass of MarshallingHttpMessageConverter and override readFromSource()
protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) {
Object o = null;
try {
o = super.readFromSource(clazz, headers, source);
} catch (Exception e) {
try {
o = super.readFromSource(MyCustomException.class, headers, source);
} catch (IOException e1) {
log.info("Failed readFromSource "+e);
}
}
return o;
}
Unfortunately, this doesnt work because the underlying inputstream inside source has been closed by the time I retry it.
Any suggestions gratefully received,
Tom
UPDATE: I have got this to work by taking a copy of the inputStream
protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) {
InputStream is = ((StreamSource) source).getInputStream();
// Take a copy of the input stream so we can use it for initial JAXB conversion
// and if that fails, we can try to convert to Exception
CopyInputStream copyInputStream = new CopyInputStream(is);
// input stream in source is empty now, so reset using copy
((StreamSource) source).setInputStream(copyInputStream.getCopy());
Object o = null;
try {
o = super.readFromSource(clazz, headers, source);
// we have failed to unmarshal to 'clazz' - assume it is <exception> and unmarshal to MyCustomException
} catch (Exception e) {
try {
// reset input stream using copy
((StreamSource) source).setInputStream(copyInputStream.getCopy());
o = super.readFromSource(MyCustomException.class, headers, source);
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
return o;
}
CopyInputStream is taken from http://www.velocityreviews.com/forums/t143479-how-to-make-a-copy-of-inputstream-object.html, i'll paste it here.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class CopyInputStream
{
private InputStream _is;
private ByteArrayOutputStream _copy = new ByteArrayOutputStream();
/**
*
*/
public CopyInputStream(InputStream is)
{
_is = is;
try
{
copy();
}
catch(IOException ex)
{
// do nothing
}
}
private int copy() throws IOException
{
int read = 0;
int chunk = 0;
byte[] data = new byte[256];
while(-1 != (chunk = _is.read(data)))
{
read += data.length;
_copy.write(data, 0, chunk);
}
return read;
}
public InputStream getCopy()
{
return (InputStream)new ByteArrayInputStream(_copy.toByteArray());
}
}