如何教 Apache CXF JAX-WS 客户端使用的 JAXB 解组未类型化 属性 中的 {http://microsoft.com/wsdl/types/}guid 值?
How to teach JAXB used by Apache CXF JAX-WS client to unmarshal {http://microsoft.com/wsdl/types/}guid values in an untyped property?
我有一个 SOAP 服务需要向其发送请求(特别是 Ivanti Integration Web Service)。
我使用 Apache CXF 3.2.7 连接到该服务。我使用 wsdl2java
.
从服务的 WSDL 生成 Java 类
WSDL 没有提及任何 GUID,似乎完全自给自足。但是,有一个字段(名为 Value
)是未类型化的,即。 e.没有 type
属性的 xsd:element
,并且服务器在该字段中发送具有各种类型值的响应。它们看起来像这样:
<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>
字符串和短裤没问题,但 GUID 在客户端产生此异常:
javax.xml.bind.UnmarshalException: unrecognized type name: {http://microsoft.com/wsdl/types/}guid
我如何避免这个异常?我实际上并不关心这个字段的值,尽管实现类型安全解组的解决方案当然是理想。
我试过的
无论我做什么,异常都不会消失。特别是,我试过:
将 <jaxb:binding><jaxb:property><jaxb:baseType>
添加到我的自定义绑定 XML 以使其将字段视为字符串 - 它使 Java 属性 成为字符串但显然一直根据指定的类型对数据进行解组处理,但由于无法将日期转换为字符串而中断;
使用自定义解组方法添加 <jaxb:javaType>
或 <jxc:javaType>
——这根本不起作用,wsdl2java
失败,“编译器无法接受这个转换定制。它附加到错误的位置,或者与其他绑定不一致”,无论我将元素放置在何处,无论我指定的 Java 类型如何;
手动添加来自 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>
在调用 wsdl2java
之前 添加到服务的 WSDL 文件中——除了 xsd:simpleType
之外还添加了 xsd:element
之后,我终于得到了 wsdl2java
来生成一个方法在用 @XmlElementDecl(namespace = "http://microsoft.com/wsdl/types/", name = "guid")
注释的 ObjectFactory
上,但仍未使用此方法,在我的 WSDL 引用 guid
和 UnmarshalException
的任何地方仍然使用纯 String
坚持;
甚至在 USER_STREAM
阶段添加一个 in-Interceptor
将整个 InputStream
吞噬成一个字符串,残酷地找到所有看起来像GUID xsi:type
/xmlns:q2
属性并将它们替换为 xsi:type="xsd:string"
类似于 this answer — 但我一定是犯了一些错误,因为异常 still 没有消失;这是我的代码:
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());
}
}
然后我使用this.service
调用强类型操作方法。也许拦截器没有保存在本地 client
变量之外?
如果我理解正确(我一点也不确定),这个异常意味着 JAXB 没有为 GUID 类型注册的解组器,如果我能以某种方式获得支持,它应该被解决JAXB 注册表并添加我自己的编组器。但是在查看了 CXF 的 JavaDocs 之后,我不知道如何,甚至是否可以访问此注册表。有些方法听起来我可能可以获得 JAXBContext
,但我看不出如何向已经存在的 JAXBContext
实例添加任何内容。
如果您将 wsdl2java
生成的源代码从您的原始 WSDL 导入您的源代码管理(并停止在每次构建时生成它们),您可以添加自定义 class 映射 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;
}
并向您的 wsdl2java
生成的 class 之一添加一个 @XmlSeeAlso(Guid.class)
注释,这些注释已经被 JAXB 拾取,例如。 G。实际服务 class。服务 class 可能已经有 @XmlSeeAlso({ObjectFactory.class})
,因此您可以将其更改为 @XmlSeeAlso({ObjectFactory.class, Guid.class})
。
这样,JAXB 将成功地将 GUID 解组为具有纯字符串内容的 Guid
实例。如果你想要实际的 java.util.UUID
个实例,你可以在 @XmlValue
字段上添加 @XmlJavaTypeAdapter
,但我还没有测试过这个。
(顺便说一句:当您尝试将 xsd:element
添加到 WSDL 时,我认为您添加了一个名为 XML 元素 的映射 guid
,例如 <q2:guid xmlns:q2="http://microsoft.com/wsdl/types/">c3aca40a-439d-4af2-b42e-59b1ddcf3d6e</q2:guid>
。这不是您想要的,所以这解释了为什么它没有帮助您。)
我有一个 SOAP 服务需要向其发送请求(特别是 Ivanti Integration Web Service)。
我使用 Apache CXF 3.2.7 连接到该服务。我使用 wsdl2java
.
WSDL 没有提及任何 GUID,似乎完全自给自足。但是,有一个字段(名为 Value
)是未类型化的,即。 e.没有 type
属性的 xsd:element
,并且服务器在该字段中发送具有各种类型值的响应。它们看起来像这样:
<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>
字符串和短裤没问题,但 GUID 在客户端产生此异常:
javax.xml.bind.UnmarshalException: unrecognized type name: {http://microsoft.com/wsdl/types/}guid
我如何避免这个异常?我实际上并不关心这个字段的值,尽管实现类型安全解组的解决方案当然是理想。
我试过的
无论我做什么,异常都不会消失。特别是,我试过:
将
<jaxb:binding><jaxb:property><jaxb:baseType>
添加到我的自定义绑定 XML 以使其将字段视为字符串 - 它使 Java 属性 成为字符串但显然一直根据指定的类型对数据进行解组处理,但由于无法将日期转换为字符串而中断;使用自定义解组方法添加
<jaxb:javaType>
或<jxc:javaType>
——这根本不起作用,wsdl2java
失败,“编译器无法接受这个转换定制。它附加到错误的位置,或者与其他绑定不一致”,无论我将元素放置在何处,无论我指定的 Java 类型如何;手动添加来自 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>
wsdl2java
之前添加到服务的 WSDL 文件中——除了
xsd:simpleType
之外还添加了xsd:element
之后,我终于得到了wsdl2java
来生成一个方法在用@XmlElementDecl(namespace = "http://microsoft.com/wsdl/types/", name = "guid")
注释的ObjectFactory
上,但仍未使用此方法,在我的 WSDL 引用guid
和UnmarshalException
的任何地方仍然使用纯String
坚持;甚至在
USER_STREAM
阶段添加一个 in-Interceptor
将整个InputStream
吞噬成一个字符串,残酷地找到所有看起来像GUIDxsi:type
/xmlns:q2
属性并将它们替换为xsi:type="xsd:string"
类似于 this answer — 但我一定是犯了一些错误,因为异常 still 没有消失;这是我的代码: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()); } }
然后我使用
this.service
调用强类型操作方法。也许拦截器没有保存在本地client
变量之外?
如果我理解正确(我一点也不确定),这个异常意味着 JAXB 没有为 GUID 类型注册的解组器,如果我能以某种方式获得支持,它应该被解决JAXB 注册表并添加我自己的编组器。但是在查看了 CXF 的 JavaDocs 之后,我不知道如何,甚至是否可以访问此注册表。有些方法听起来我可能可以获得 JAXBContext
,但我看不出如何向已经存在的 JAXBContext
实例添加任何内容。
如果您将 wsdl2java
生成的源代码从您的原始 WSDL 导入您的源代码管理(并停止在每次构建时生成它们),您可以添加自定义 class 映射 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;
}
并向您的 wsdl2java
生成的 class 之一添加一个 @XmlSeeAlso(Guid.class)
注释,这些注释已经被 JAXB 拾取,例如。 G。实际服务 class。服务 class 可能已经有 @XmlSeeAlso({ObjectFactory.class})
,因此您可以将其更改为 @XmlSeeAlso({ObjectFactory.class, Guid.class})
。
这样,JAXB 将成功地将 GUID 解组为具有纯字符串内容的 Guid
实例。如果你想要实际的 java.util.UUID
个实例,你可以在 @XmlValue
字段上添加 @XmlJavaTypeAdapter
,但我还没有测试过这个。
(顺便说一句:当您尝试将 xsd:element
添加到 WSDL 时,我认为您添加了一个名为 XML 元素 的映射 guid
,例如 <q2:guid xmlns:q2="http://microsoft.com/wsdl/types/">c3aca40a-439d-4af2-b42e-59b1ddcf3d6e</q2:guid>
。这不是您想要的,所以这解释了为什么它没有帮助您。)