简体   繁体   中英

How to teach JAXB used by Apache CXF JAX-WS client to unmarshal {http://microsoft.com/wsdl/types/}guid values in an untyped property?

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, ie 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 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, eg 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 , eg <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.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM