我在使用MS Exchange Web服务(EWS)的客户端中使用CXF(v2.7.10)。
我发现EWS返回的其中一个元素(UniqueHash)包含XML v1.0中无效的字符。由于我无法控制这一点,我试图使用入站拦截器来删除UniqueHash元素(我不需要它们),如下所示:
Map<String, String> inTransformMap = Collections.singletonMap(
"{http://schemas.microsoft.com/exchange/services/2006/types}UniqueHash", "");
TransformInInterceptor transformInInterceptor = new TransformInInterceptor();
transformInInterceptor.setInTransformElements(inTransformMap);
client.getInInterceptors().add(transformInInterceptor);
我可以看到转换(TransformInInterceptor)运行良好且早期(后流):
FINE: Chain org.apache.cxf.phase.PhaseInterceptorChain@be78549 was created. Current flow:
receive [PolicyInInterceptor, LoggingInInterceptor, AttachmentInInterceptor]
post-stream [TransformInInterceptor, StaxInInterceptor]
read [WSDLGetInterceptor, ReadHeadersInterceptor, SoapActionInInterceptor, StartBodyInterceptor]
pre-protocol [MustUnderstandInterceptor]
post-protocol [CheckFaultInterceptor, JAXBAttachmentSchemaValidationHack]
unmarshal [DocLiteralInInterceptor, SoapHeaderInterceptor]
post-logical [WrapperClassInInterceptor]
pre-invoke [SwAInInterceptor, HolderInInterceptor]
但即使它出现按步骤通过代码工作,当DocLiteralInInterceptor稍后触发它时会抛出这个解组错误(在这种情况下0x4在UniqueHash元素内我认为我&#39 ; d掉线):
org.apache.cxf.interceptor.Fault: Unmarshalling Error: Illegal character entity: expansion character (code 0x4
at [row,col {unknown-source}]: [1,2230]
at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:881)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:702)
at org.apache.cxf.jaxb.io.DataReaderImpl.read(DataReaderImpl.java:160)
at org.apache.cxf.interceptor.DocLiteralInInterceptor.handleMessage(DocLiteralInInterceptor.java:192)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:272)
at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:835)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1614)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1504)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1310)
at org.apache.cxf.transport.http.asyncclient.AsyncHTTPConduit$AsyncWrappedOutputStream.close(AsyncHTTPConduit.java:381)
at org.apache.cxf.io.CacheAndWriteOutputStream.postClose(CacheAndWriteOutputStream.java:50)
at org.apache.cxf.io.CachedOutputStream.close(CachedOutputStream.java:223)
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:628)
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:272)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:565)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:474)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:377)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:330)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:135)
at com.sun.proxy.$Proxy67.searchMailboxes(Unknown Source)
Caused by: javax.xml.bind.UnmarshalException
以下是我正在使用的XML响应:
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:ServerVersionInfo MajorVersion="15" MinorVersion="0" MajorBuildNumber="847" MinorBuildNumber="31" Version="V2_8" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<m:SearchMailboxesResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<m:ResponseMessages>
<m:SearchMailboxesResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:SearchMailboxesResult>
<t:SearchQueries>
<t:MailboxQuery>
<t:Query>"general quarters"</t:Query>
<t:MailboxSearchScopes>
<t:MailboxSearchScope>
<t:Mailbox>/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=6f8abfc1a1694cf299c7b3ae5522d8c4-John</t:Mailbox>
<t:SearchScope>All</t:SearchScope>
</t:MailboxSearchScope>
</t:MailboxSearchScopes>
</t:MailboxQuery>
</t:SearchQueries>
<t:ResultType>PreviewOnly</t:ResultType>
<t:ItemCount>1</t:ItemCount>
<t:Size>3169</t:Size>
<t:PageItemCount>1</t:PageItemCount>
<t:PageItemSize>3169</t:PageItemSize>
<t:Items>
<t:SearchPreviewItem>
<t:Id Id="AAMkADY4MDY1MWViLTMzMWItNDEyYi1iMjUzLTQ2ZjMwNWVkYmIzYQBGAAAAAABkY13xq9IqS5OySCQXk7W3BwC9AjA7QbibQa9DQZUO2Dm3AAAAAAAMAAC9AjA7QbibQa9DQZUO2Dm3AAAE/bU4AAA=" ChangeKey="CQAAABYAAAC9AjA7QbibQa9DQZUO2Dm3AAAE/ceM"/>
<t:Mailbox>
<t:MailboxId>/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=6f8abfc1a1694cf299c7b3ae5522d8c4-John</t:MailboxId>
<t:PrimarySmtpAddress>john.smith@internal.local</t:PrimarySmtpAddress>
</t:Mailbox>
<t:ParentId Id="AQMkADY4MDY1MWViLTMzADFiLTQxMmItYjI1My00NmYzMDVlZGJiADNhAC4AAANkY13xq9IqS5OySCQXk7W3AQC9AjA7QbibQa9DQZUO2Dm3AAADDAAAAA==" ChangeKey="AQAAAA=="/>
<t:ItemClass>IPM.Note</t:ItemClass>
<t:UniqueHash>00036<0788d814ffea4e499c2fdb479c8617a2@ex01.internal.local>0010General Quarters000C���?"{</t:UniqueHash>
<t:SortValue>001B2014-03-11T19:42:42.00000000000006F00001182</t:SortValue>
<t:OwaLink>https://ex01.internal.local/owa/integrated/?viewmodel=ItemReadingPaneViewModelPopOutFactory&IsDiscoveryView=1&exsvurl=1&ItemID=AAMkADY4MDY1MWViLTMzMWItNDEyYi1iMjUzLTQ2ZjMwNWVkYmIzYQBGAAAAAABkY13xq9IqS5OySCQXk7W3BwC9AjA7QbibQa9DQZUO2Dm3AAAAAAAMAAC9AjA7QbibQa9DQZUO2Dm3AAAE%2FbU4AAA%3D</t:OwaLink>
<t:Sender>John Smith</t:Sender>
<t:ToRecipients>
<t:SmtpAddress>Our Shared Mailbox</t:SmtpAddress>
</t:ToRecipients>
<t:CreatedTime>2014-03-11T19:42:42Z</t:CreatedTime>
<t:ReceivedTime>2014-03-11T19:42:42Z</t:ReceivedTime>
<t:SentTime>2014-03-11T19:42:42Z</t:SentTime>
<t:Subject>General Quarters</t:Subject>
<t:Size>3169</t:Size>
<t:Preview/>
<t:Importance>Normal</t:Importance>
<t:Read>true</t:Read>
<t:HasAttachment>false</t:HasAttachment>
</t:SearchPreviewItem>
</t:Items>
<t:MailboxStats>
<t:MailboxStat>
<t:MailboxId>/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=6f8abfc1a1694cf299c7b3ae5522d8c4-John</t:MailboxId>
<t:DisplayName>John Smith</t:DisplayName>
<t:ItemCount>1</t:ItemCount>
<t:Size>3169</t:Size>
</t:MailboxStat>
</t:MailboxStats>
</m:SearchMailboxesResult>
</m:SearchMailboxesResponseMessage>
</m:ResponseMessages>
</m:SearchMailboxesResponse>
</s:Body>
</s:Envelope>
有谁知道我在这里做错了什么?关于我如何摆脱这个元素以及它的麻烦内容的任何指示?
答案 0 :(得分:4)
想出来(感谢Daniel Kulp通过cxf用户邮件列表确认)。
问题在于InTransformReader
延伸DepthXMLStreamReader
。这意味着即使我试图删除或替换无效字符,TransformInInterceptor
也会首先尝试解组它们。
解决方案是在调用AbstractPhaseInterceptor
之前创建一个扩展StaxInInterceptor
并在PRE_STREAM阶段使用正则表达式过滤掉无效文本的新拦截器。
一旦你知道怎么做就很容易!
示例:
以下内容将从soap消息中删除无效的XML字符:
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.io.CachedOutputStream;
public class InvalidCharInterceptor extends AbstractPhaseInterceptor<Message> {
public InvalidCharInterceptor() {
super(Phase.PRE_STREAM);
}
/**
* From xml spec valid chars:<br>
* #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]<br>
* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.<br>
*
* @param text
* The String to clean
* @param replacement
* The string to be substituted for each match
* @return The resulting String
*/
public static String cleanInvalidXmlChars(String text, String replacement) {
String re = "[^\\x09\\x0A\\x0D\\x20-\\xD7FF\\xE000-\\xFFFD\\x10000-x10FFFF]";
return text.replaceAll(re, replacement);
}
@Override
public void handleMessage(Message message) throws Fault {
boolean isOutbound = false;
isOutbound = message == message.getExchange().getOutMessage()
|| message == message.getExchange().getOutFaultMessage();
if (isOutbound) {
OutputStream os = message.getContent(OutputStream.class);
CachedOutputStream cs = new CachedOutputStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
IOUtils.closeQuietly(cs);
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
csnew.flush();
IOUtils.closeQuietly(csnew);
String res = cleanInvalidXmlChars(currentEnvelopeMessage, "");
res = res != null ? res : currentEnvelopeMessage;
InputStream replaceInStream = IOUtils.toInputStream(res, "UTF-8");
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
os.flush();
message.setContent(OutputStream.class, os);
IOUtils.closeQuietly(os);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}
}
然后将其添加到您的客户端:
client.getOutInterceptors().add(new InvalidCharInterceptor());
答案 1 :(得分:0)
好的,以防万一它可以帮助某人,这是我解决我的问题的方式,这似乎适合OP处理包含无效字符的入站消息。实际上,对于我来说,接受的答案不起作用,因为它似乎可以处理出站流量!?
这是我重写代码以使其在我的情况下可以工作的方式(入站SOAP WS响应包含无效字符,这会触发解组异常。
public class vidcam extends Activity implements View.OnClickListener {
String ID, prepend;
private final int VIDEO_REQUEST_CODE = 100;
private final int REQUEST_TAKE_GALLERY_VIDEO =22;
File video_file;
Button RecordButton, tobaitana, viduploadbutton, uploadvideo;
Uri selectedVideo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vidcam);
RecordButton = (Button) findViewById(R.id.RecordButton);
RecordButton.setOnClickListener(this);
tobaitana = (Button) findViewById(R.id.tobaitana);
tobaitana.setOnClickListener(this);
viduploadbutton = (Button) findViewById(R.id.viduploadbutton);
viduploadbutton.setOnClickListener(this);
uploadvideo = (Button) findViewById(R.id.uploadvideo);
uploadvideo.setOnClickListener(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent
data) {
if (requestCode == VIDEO_REQUEST_CODE){
Toast.makeText(getApplicationContext(), "Video Saved",
Toast.LENGTH_LONG).show();
}
if (resultCode == RESULT_OK) {
if(requestCode == REQUEST_TAKE_GALLERY_VIDEO){
selectedVideo = data.getData();
}}}
public File getfilepath () throws IOException {
File videofolder = new File("sdcard/video_app");
if (!videofolder.exists())
{
videofolder.mkdir();
}
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
prepend = "MOBILITY_" + timestamp + "_";
video_file = File.createTempFile(prepend, ".mp4", videofolder);
return video_file;
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.RecordButton:
Intent video_intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
File videolocation = null;
try {
videolocation = getfilepath();
} catch (IOException e) {
e.printStackTrace();
}
Uri video_uri = Uri.fromFile(videolocation);
video_intent.putExtra(MediaStore.EXTRA_OUTPUT, video_uri);
video_intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(video_intent, VIDEO_REQUEST_CODE);
break;
case R.id.tobaitana:
Intent toanbait = new Intent(this, Abait.class);
startActivity(toanbait);
break;
case R.id.viduploadbutton:
Intent selectfile = new Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(selectfile, REQUEST_TAKE_GALLERY_VIDEO);
break;
case R.id.uploadvideo:
uploadFile(selectedVideo);
break;
}
}
public void useID() {
SharedPreferences shareID = getSharedPreferences("shareID", Context.MODE_PRIVATE);
ID = shareID.getString("ID", "");
}
private String getRealPathFromURI(Uri contentUri) {
String filePath;
Cursor cursor = getContentResolver().query(contentUri, null, null, null, null);
if(cursor == null)
{
filePath = contentUri.getPath();
}else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Video.VideoColumns.DATA);
filePath = cursor.getString(idx);
cursor.close();
Toast.makeText(getApplicationContext(), "realpath toast" + filePath, Toast.LENGTH_LONG).show();
}
return filePath;
}
private void uploadFile(Uri selectedVideo) {
useID();
File vidfile = new File(getRealPathFromURI(selectedVideo));
if (vidfile.exists()) {
RequestBody requestBody = RequestBody.create(MediaType.parse(getContentResolver().getType(selectedVideo)), vidfile);
MultipartBody.Part file = MultipartBody.Part.createFormData("filename", vidfile.getName(), requestBody);
RequestBody desc = RequestBody.create(MediaType.parse("text/plain"), vidfile.getName());
Call<MyResponse> call = RetrofitClient.getInstance().getAPIService().uploadImage(file, desc, ID);
call.enqueue(new Callback<MyResponse>() {
@Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
if (!response.body().error) {
Toast.makeText(getApplicationContext(), "File Uploaded Successfully...", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "Some error occurred...", Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(Call<MyResponse> call, Throwable t) {
Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show();
}
});
} else {Toast.makeText(getApplicationContext(), "no file exists...", Toast.LENGTH_LONG).show();
}
}
}
然后我在cxf定义中使用此类
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import com.google.common.io.ByteStreams;
/**
* This Adapter is meant to intercept XML response and remove invalid characters
*/
public class InvalidCharInterceptor extends AbstractPhaseInterceptor<Message> {
// https://stackoverflow.com/a/4237934/2143734
// Valid Characters in XML 1.0
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
// now we create a regexp filtering the opposite, i.e. all invalid char intervals
private static final Pattern XML_1_0_FORBIDDEN_CHARS_PATTERN = Pattern
.compile("[^" + "\u0009\r\n" + "\u0020-\uD7FF" + "\uE000-\uFFFD" + "\ud800\udc00-\udbff\udfff" + "]");
/**
* used by cxf.xml to create the interceptor bean
* <a href="http://cxf.apache.org/docs/interceptors.html">It register to the RECEIVE phase in cxf flow</a>
*/
public InvalidCharInterceptor() {
super(Phase.RECEIVE);
}
/**
* Remove all XML 1.0 invalid characters from the String
*
* @param s
* the string to process
* @return the purged string
*/
private static String removeForbiddenChars(String s) {
Matcher m = XML_1_0_FORBIDDEN_CHARS_PATTERN.matcher(s);
return m.replaceAll("");
}
@Override
public void handleMessage(Message message) {
boolean isInbound = message == message.getExchange().getInMessage()
|| message == message.getExchange().getInFaultMessage();
if (isInbound) {
try (InputStream is = message.getContent(InputStream.class);) {
if (is != null) {
byte[] rawContent = ByteStreams.toByteArray(is);
String content = new String(rawContent, StandardCharsets.UTF_8);
String cleanContent = removeForbiddenChars(content);
message.setContent(InputStream.class,
new ByteArrayInputStream(cleanContent.getBytes(StandardCharsets.UTF_8)));
}
} catch (IOException e) {
throw new Fault(e);
}
}
}
}