I have a SOAP service I need to send requests to (specifically Ivanti Integration Web Service).
I use Apache CXF 3.2.7 to connect to the service. I generate Java classes from the service’s WSDL using wsdl2java
.
The WSDL makes no mention of any GUIDs and seems entirely self-sufficient. However, there is one field (named Value
) that is untyped, i. e. an xsd:element
without a type
attribute, and the server sends responses with values of various types in this field. They look like this:
<Value xsi:type="xsd:string">foobar</Value>
<Value xsi:type="xsd:short">1</Value>
<Value xsi:type="q2:guid" xmlns:q2="http://microsoft.com/wsdl/types/">c3aca40a-439d-4af2-b42e-59b1ddcf3d6e</Value>
Strings and shorts are fine, but the GUIDs produce this exception on the client:
javax.xml.bind.UnmarshalException: unrecognized type name: {http://microsoft.com/wsdl/types/}guid
How do I avoid this exception? I don’t actually care about the value of this field, although a solution that achieves type-safe unmarshalling would, of course, be ideal.
What I’ve tried
No matter what I do, the exception just doesn’t go away. In particular, I’ve tried:
adding
<jaxb:binding><jaxb:property><jaxb:baseType>
to my customized binding XML to make it treat the field as a string—it made the Java property a string but apparently kept unmarshaling data according to the specified types and broke because it coudn’t convert a date to a string;adding
<jaxb:javaType>
or<jxc:javaType>
with a custom unmarshalling method—this didn’t work at all,wsdl2java
failed with “compiler was unable to honor this conversion customization. It is attached to a wrong place, or its inconsistent with other bindings” no matter where I placed the element and no matter what Java type I specified;manually adding the type definition from one of these sources:
<xs:schema elementFormDefault="qualified" targetNamespace="http://microsoft.com/wsdl/types/" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:simpleType name="guid"> <xs:restriction base="xs:string"> <xs:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"/> </xs:restriction> </xs:simpleType> </xs:schema>
to the service’s WSDL file before invoking
wsdl2java
on it—after adding anxsd:element
in addition to thexsd:simpleType
, I finally gotwsdl2java
to generate a method onObjectFactory
annotated with@XmlElementDecl(namespace = "http://microsoft.com/wsdl/types/", name = "guid")
, but this method still wasn’t used, plainString
was still used wherever my WSDL referred toguid
, and theUnmarshalException
persisted;even adding an in-
Interceptor
on theUSER_STREAM
phase that eats up the entireInputStream
into a string, brutally finds all things that look like the GUIDxsi:type
/xmlns:q2
attributes and replaces them withxsi:type="xsd:string"
similar to this answer—but I must have made some mistake, because the exception still didn’t go away; here’s my code:import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; public class GuidExpungeInterceptor extends AbstractPhaseInterceptor<Message> { private static class GuidExpungedInputStream extends ByteArrayInputStream { private final InputStream stream; public GuidExpungedInputStream(InputStream stream) throws IOException { super(guidExpungedByteArray(stream)); this.stream = stream; } private static byte[] guidExpungedByteArray(InputStream stream) throws IOException { String content = IOUtils.toString(stream, StandardCharsets.ISO_8859_1); content = content.replaceAll("<Value xsi:type=\"([A-Za-z_][A-Za-z0-9_.-]*):guid\" xmlns:\\1=\"http://microsoft.com/wsdl/types/\">", "<Value xsi:type=\"xsd:string\">"); return content.getBytes(StandardCharsets.ISO_8859_1); } @Override public void close() throws IOException { stream.close(); super.close(); } } public GuidExpungeInterceptor() { super(Phase.USER_STREAM); } @Override public void handleMessage(Message message) { if (message == message.getExchange().getInMessage()) { try { InputStream stream = message.getContent(InputStream.class); message.setContent(InputStream.class, new GuidExpungedInputStream(stream)); } catch (IOException e) { throw new Fault(e); } } } }
class BlahController { BlahController() { JaxWsProxyFactoryBean proxyFactory = new JaxWsProxyFactoryBean(); proxyFactory.setServiceClass(FRSHEATIntegrationSoap.class); proxyFactory.setAddress(this.properties.getFrsHeatIntegrationUrl()); this.service = (FRSHEATIntegrationSoap) proxyFactory.create(); Client client = ClientProxy.getClient(service); client.getInInterceptors().add(new GuidExpungeInterceptor()); } }
Then I use
this.service
to invoke the strongly typed operation methods. Perhaps the interceptor isn’t preserved beyond the localclient
variable?
If I understand correctly (which I’m not at all sure about), this exception means that JAXB doesn’t have an unmarshaller registered for the GUID type and it should be resolved if I could somehow get a hold on the JAXB registry and add my own marshaller. But after looking at CXF’s JavaDocs, I have no idea how, or even if, I could gain access to this registry. Some methods sound like I might be able to get a JAXBContext
, but I don’t see how I could add anything to an already existing JAXBContext
instance.