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.
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 an xsd:element
in addition to the xsd:simpleType
, I finally got wsdl2java
to generate a method on ObjectFactory
annotated with @XmlElementDecl(namespace = "http://microsoft.com/wsdl/types/", name = "guid")
, but this method still wasn’t used, plain String
was still used wherever my WSDL referred to guid
, and the UnmarshalException
persisted;
even adding an in-Interceptor
on the USER_STREAM
phase that eats up the entire InputStream
into a string, brutally finds all things that look like the GUID xsi:type
/xmlns:q2
attributes and replaces them with xsi: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 local client
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.
If you import the sources generated by wsdl2java
from your original WSDL into your source control (and stop generating them on every build), you can add a custom class mapping the simpleType
:
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
@XmlType(namespace = "http://microsoft.com/wsdl/types/", name = "guid")
public class Guid {
@XmlValue
public String guid;
}
and add a @XmlSeeAlso(Guid.class)
annotation to one of your wsdl2java
-generated classes that are already being picked up by JAXB, e. g. the actual service class. The service class probably already has @XmlSeeAlso({ObjectFactory.class})
, so you can just change that to @XmlSeeAlso({ObjectFactory.class, Guid.class})
.
This way, JAXB will successfully unmarshal the GUIDs as Guid
instances with plain string contents. If you want actual java.util.UUID
instances, you may be able to add @XmlJavaTypeAdapter
on the @XmlValue
field, but I haven’t tested this.
(By the way: when you tried adding xsd:element
to the WSDL, I think you added a mapping for an XML element named guid
, e. g. <q2:guid xmlns:q2="http://microsoft.com/wsdl/types/">c3aca40a-439d-4af2-b42e-59b1ddcf3d6e</q2:guid>
. This isn’t what you wanted, so this explains why it didn’t help you.)