EWS GetStreamingEvents. Response is received only by timeout - ios

In my iOS app I'm trying to get streaming events from EWS following this manual. So first I subscribe for notifications, receive a subscription id and then perform GetStreamingEvents request. The subscription process is successful, however getting streaming events is not. I receive a response only by timeout. Yes, it does contain all the notifications, so no problems with that but I expect to receive a response once an event has occurred not when the request time is up.
So I launched Charles and examined the traffic of Mac Mail app. I can see that it receives one response right after making GetStreamingEvents request and then it successfully receives responses every time an event has occurred as expected. So I made my XML look exactly like Mac Mail app's but still no luck.
This is my Subscribe xml:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<soap:Header>
<t:RequestedServerVersion Version="Exchange2010_SP2" />
</soap:Header>
<soap:Body>
<m:Subscribe xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:StreamingSubscriptionRequest SubscribeToAllFolders="true">
<t:EventTypes>
<t:EventType>CopiedEvent</t:EventType>
<t:EventType>CreatedEvent</t:EventType>
<t:EventType>DeletedEvent</t:EventType>
<t:EventType>ModifiedEvent</t:EventType>
<t:EventType>MovedEvent</t:EventType>
<t:EventType>NewMailEvent</t:EventType>
<t:EventType>FreeBusyChangedEvent</t:EventType>
</t:EventTypes>
</m:StreamingSubscriptionRequest>
</m:Subscribe>
</soap:Body>
</soap:Envelope>
and this is my GetStreamingEvents xml:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<soap:Header>
<t:RequestServerVersion Version="Exchange2010_SP2" />
</soap:Header>
<soap:Body>
<m:GetStreamingEvents xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:SubscriptionIds>
<t:SubscriptionId>JwBkYjVwcjA2bWIxNDY0LmV1cnByZDA2LnByb2Qub3V0bG9vay5jb20QAAAAF9r7tBXj+U+UapGUZ4XFytKbA+ad1dQIEAAAAMiCIP/mQqJOjzx9Aog45Fk=</t:SubscriptionId>
</m:SubscriptionIds>
<m:ConnectionTimeout>30</m:ConnectionTimeout>
</m:GetStreamingEvents>
</soap:Body>
</soap:Envelope>
Any ideas, guys?
UPDATE:
This is how I build my subscription:
bcstring SubscribeRequestMessage::buildSoapRequest() const
{
xml_document<> doc;
xml_node<>* decl = doc.allocate_node(node_declaration);
decl->append_attribute(doc.allocate_attribute("version", "1.0"));
decl->append_attribute(doc.allocate_attribute("encoding", "utf-8"));
doc.append_node(decl);
// Envelope
xml_node<>* envelope = doc.allocate_node(node_element, "soap:Envelope");
envelope->append_attribute(doc.allocate_attribute("xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/"));
envelope->append_attribute(doc.allocate_attribute("xmlns:t", "http://schemas.microsoft.com/exchange/services/2006/types"));
doc.append_node(envelope);
{
// Header
xml_node<>* header = doc.allocate_node(node_element, "soap:Header");
{
xml_node<>* reqServerVersion = doc.allocate_node(node_element, "t:RequestedServerVersion");
xml_attribute<>* serverVersionAttribute = doc.allocate_attribute("Version", m_requestedServerVersion.c_str());
reqServerVersion->append_attribute(serverVersionAttribute);
header->append_node(reqServerVersion);
}
envelope->append_node(header);
// Body
xml_node<>* body = doc.allocate_node(node_element, "soap:Body");
{
xml_node<>* subscribe = doc.allocate_node(node_element, "m:Subscribe");
{
subscribe->append_attribute(doc.allocate_attribute("xmlns:m", "http://schemas.microsoft.com/exchange/services/2006/messages"));
xml_node<>* streamingSubscriptionRequest = doc.allocate_node(node_element, "m:StreamingSubscriptionRequest");
{
if (m_folderIds.size() > 0)
{
xml_node<>* folderIds = doc.allocate_node(node_element, "t:FolderIds");
{
for (const bcstring& folderId : m_folderIds)
{
xml_node<>* folderIdNode = doc.allocate_node(node_element, "t:FolderId");
folderIdNode->append_attribute(doc.allocate_attribute("Id", folderId.c_str()));
folderIds->append_node(folderIdNode);
}
}
streamingSubscriptionRequest->append_node(folderIds);
}
else
{
xml_attribute<>* subscribeToAll = doc.allocate_attribute("SubscribeToAllFolders", "true");
streamingSubscriptionRequest->append_attribute(subscribeToAll);
}
xml_node<>* eventTypes = doc.allocate_node(node_element, "t:EventTypes");
{
for (const bcstring& eventType : m_eventTypes)
{
xml_node<>* eventTypeNode = doc.allocate_node(node_element, "t:EventType");
xml_node<>* eventTypeValueNode = doc.allocate_node(node_data, "", eventType.c_str());
eventTypeNode->append_node(eventTypeValueNode);
eventTypes->append_node(eventTypeNode);
}
}
streamingSubscriptionRequest->append_node(eventTypes);
}
subscribe->append_node(streamingSubscriptionRequest);
}
body->append_node(subscribe);
}
envelope->append_node(body);
}
bcstring retVal;
print(std::back_inserter(retVal), doc, print_no_indenting);
return retVal;
}
and this is how I build get streaming xml:
bcstring GetStreamingEventsRequest::buildSoapRequest() const
{
xml_document<> doc;
xml_node<>* decl = doc.allocate_node(node_declaration);
decl->append_attribute(doc.allocate_attribute("version", "1.0"));
decl->append_attribute(doc.allocate_attribute("encoding", "utf-8"));
doc.append_node(decl);
// Envelope
xml_node<>* envelope = doc.allocate_node(node_element, "soap:Envelope");
envelope->append_attribute(doc.allocate_attribute("xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/"));
envelope->append_attribute(doc.allocate_attribute("xmlns:t", "http://schemas.microsoft.com/exchange/services/2006/types"));
doc.append_node(envelope);
{
// Header
xml_node<>* header = doc.allocate_node(node_element, "soap:Header");
{
xml_node<>* reqServerVersion = doc.allocate_node(node_element, "t:RequestServerVersion");
xml_attribute<>* serverVersionAttribute = doc.allocate_attribute("Version", m_requestedServerVersion.c_str());
reqServerVersion->append_attribute(serverVersionAttribute);
header->append_node(reqServerVersion);
}
envelope->append_node(header);
// Body
xml_node<>* body = doc.allocate_node(node_element, "soap:Body");
{
xml_node<>* getStreamingEvents = doc.allocate_node(node_element, "m:GetStreamingEvents");
{
getStreamingEvents->append_attribute(doc.allocate_attribute("xmlns:m", "http://schemas.microsoft.com/exchange/services/2006/messages"));
xml_node<>* subscriptionIds = doc.allocate_node(node_element, "m:SubscriptionIds");
{
for (const bcstring& subscriptionId : m_subscriptionIds)
{
xml_node<>* subscriptionIdNode = doc.allocate_node(node_element, "t:SubscriptionId");
xml_node<>* subscriptionIdValue = doc.allocate_node(node_data, "", subscriptionId.c_str());
subscriptionIdNode->append_node(subscriptionIdValue);
subscriptionIds->append_node(subscriptionIdNode);
}
}
getStreamingEvents->append_node(subscriptionIds);
xml_node<>* connectionTimeout = doc.allocate_node(node_element, "m:ConnectionTimeout");
bcstring connectionTimeoutString = std::to_string(m_connectionTimeout);
xml_node<>* connectionTimeoutValue = doc.allocate_node(node_data, "", connectionTimeoutString.c_str());
connectionTimeout->append_node(connectionTimeoutValue);
getStreamingEvents->append_node(connectionTimeout);
}
body->append_node(getStreamingEvents);
}
envelope->append_node(body);
}
bcstring retVal;
print(std::back_inserter(retVal), doc, print_no_indenting);
return retVal;
}
UPDATE:
This is the XML of Mac Mail app that I see via Charles:
Subscription:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<soap:Header>
<t:RequestServerVersion Version="Exchange2010_SP2" />
</soap:Header>
<soap:Body>
<m:Subscribe xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:StreamingSubscriptionRequest SubscribeToAllFolders="true">
<t:EventTypes>
<t:EventType>CopiedEvent</t:EventType>
<t:EventType>CreatedEvent</t:EventType>
<t:EventType>DeletedEvent</t:EventType>
<t:EventType>ModifiedEvent</t:EventType>
<t:EventType>MovedEvent</t:EventType>
<t:EventType>NewMailEvent</t:EventType>
<t:EventType>FreeBusyChangedEvent</t:EventType>
</t:EventTypes>
</m:StreamingSubscriptionRequest>
</m:Subscribe>
</soap:Body>
</soap:Envelope>
and streaming:
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<soap:Header>
<t:RequestServerVersion Version="Exchange2010_SP2" />
</soap:Header>
<soap:Body>
<m:GetStreamingEvents xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:SubscriptionIds>
<t:SubscriptionId>JwBkYjVwcjA2bWIxNDY0LmV1cnByZDA2LnByb2Qub3V0bG9vay5jb20QAAAADv7qpS0xbU6V0mCxt2SvFHYOYoCq1dQIEAAAAMiCIP/mQqJOjzx9Aog45Fk=</t:SubscriptionId>
</m:SubscriptionIds>
<m:ConnectionTimeout>30</m:ConnectionTimeout>
</m:GetStreamingEvents>
</soap:Body>
</soap:Envelope>
and this is the response from EWS arriving upon timeout and containing a notification about a new mail:
<Envelope
xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<soap11:Header
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<ServerVersionInfo
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="15" MinorVersion="1" MajorBuildNumber="1282" MinorBuildNumber="22" Version="V2017_04_14"
xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap11:Header>
<soap11:Body
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<m:GetStreamingEventsResponse
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:GetStreamingEventsResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:ConnectionStatus>OK</m:ConnectionStatus>
</m:GetStreamingEventsResponseMessage>
</m:ResponseMessages>
</m:GetStreamingEventsResponse>
</soap11:Body>
</Envelope>
<Envelope
xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<soap11:Header
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<ServerVersionInfo
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="15" MinorVersion="1" MajorBuildNumber="1282" MinorBuildNumber="22" Version="V2017_04_14"
xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap11:Header>
<soap11:Body
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<m:GetStreamingEventsResponse
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:GetStreamingEventsResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:Notifications>
<m:Notification>
<t:SubscriptionId>JwBkYjVwcjA2bWIxNDY0LmV1cnByZDA2LnByb2Qub3V0bG9vay5jb20QAAAA4mbQZqHZf0aA35Z5r1UEvPZtJfmw1dQIEAAAAMiCIP/mQqJOjzx9Aog45Fk=</t:SubscriptionId>
<t:CreatedEvent>
<t:TimeStamp>2017-07-28T12:06:04Z</t:TimeStamp>
<t:ItemId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQBGAAAAAADKa2Su6/EXRrsmOefDSCL3BwBEITO0krAyRbRmLsC3JNeGAAAAAAEMAABEITO0krAyRbRmLsC3JNeGAAAdPAOsAAA=" ChangeKey="CQAAAA==" />
<t:ParentFolderId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQAuAAAAAADKa2Su6/EXRrsmOefDSCL3AQBEITO0krAyRbRmLsC3JNeGAAAAAAEMAAA=" ChangeKey="AQAAAA==" />
</t:CreatedEvent>
<t:NewMailEvent>
<t:TimeStamp>2017-07-28T12:06:04Z</t:TimeStamp>
<t:ItemId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQBGAAAAAADKa2Su6/EXRrsmOefDSCL3BwBEITO0krAyRbRmLsC3JNeGAAAAAAEMAABEITO0krAyRbRmLsC3JNeGAAAdPAOsAAA=" ChangeKey="CQAAAA==" />
<t:ParentFolderId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQAuAAAAAADKa2Su6/EXRrsmOefDSCL3AQBEITO0krAyRbRmLsC3JNeGAAAAAAEMAAA=" ChangeKey="AQAAAA==" />
</t:NewMailEvent>
<t:ModifiedEvent>
<t:TimeStamp>2017-07-28T12:06:04Z</t:TimeStamp>
<t:FolderId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQAuAAAAAADKa2Su6/EXRrsmOefDSCL3AQBEITO0krAyRbRmLsC3JNeGAAAAAAEMAAA=" ChangeKey="AQAAAA==" />
<t:ParentFolderId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQAuAAAAAADKa2Su6/EXRrsmOefDSCL3AQBEITO0krAyRbRmLsC3JNeGAAAAAAEIAAA=" ChangeKey="AQAAAA==" />
<t:UnreadCount>1</t:UnreadCount>
</t:ModifiedEvent>
</m:Notification>
</m:Notifications>
</m:GetStreamingEventsResponseMessage>
</m:ResponseMessages>
</m:GetStreamingEventsResponse>
</soap11:Body>
</Envelope>
<Envelope
xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<soap11:Header
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<ServerVersionInfo
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="15" MinorVersion="1" MajorBuildNumber="1282" MinorBuildNumber="22" Version="V2017_04_14"
xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap11:Header>
<soap11:Body
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<m:GetStreamingEventsResponse
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:GetStreamingEventsResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:ConnectionStatus>OK</m:ConnectionStatus>
</m:GetStreamingEventsResponseMessage>
</m:ResponseMessages>
</m:GetStreamingEventsResponse>
</soap11:Body>
</Envelope>
<Envelope
xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<soap11:Header
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<ServerVersionInfo
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="15" MinorVersion="1" MajorBuildNumber="1282" MinorBuildNumber="22" Version="V2017_04_14"
xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap11:Header>
<soap11:Body
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<m:GetStreamingEventsResponse
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:GetStreamingEventsResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:ConnectionStatus>OK</m:ConnectionStatus>
</m:GetStreamingEventsResponseMessage>
</m:ResponseMessages>
</m:GetStreamingEventsResponse>
</soap11:Body>
</Envelope>
<Envelope
xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<soap11:Header
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<ServerVersionInfo
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="15" MinorVersion="1" MajorBuildNumber="1282" MinorBuildNumber="22" Version="V2017_04_14"
xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap11:Header>
<soap11:Body
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<m:GetStreamingEventsResponse
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:GetStreamingEventsResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:Notifications>
<m:Notification>
<t:SubscriptionId>JwBkYjVwcjA2bWIxNDY0LmV1cnByZDA2LnByb2Qub3V0bG9vay5jb20QAAAA4mbQZqHZf0aA35Z5r1UEvPZtJfmw1dQIEAAAAMiCIP/mQqJOjzx9Aog45Fk=</t:SubscriptionId>
<t:ModifiedEvent>
<t:TimeStamp>2017-07-28T12:07:39Z</t:TimeStamp>
<t:ItemId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQBGAAAAAADKa2Su6/EXRrsmOefDSCL3BwBEITO0krAyRbRmLsC3JNeGAAAAAAEMAABEITO0krAyRbRmLsC3JNeGAAAdPAOsAAA=" ChangeKey="CQAAAA==" />
<t:ParentFolderId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQAuAAAAAADKa2Su6/EXRrsmOefDSCL3AQBEITO0krAyRbRmLsC3JNeGAAAAAAEMAAA=" ChangeKey="AQAAAA==" />
</t:ModifiedEvent>
<t:ModifiedEvent>
<t:TimeStamp>2017-07-28T12:07:39Z</t:TimeStamp>
<t:FolderId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQAuAAAAAADKa2Su6/EXRrsmOefDSCL3AQBEITO0krAyRbRmLsC3JNeGAAAAAAEMAAA=" ChangeKey="AQAAAA==" />
<t:ParentFolderId Id="AAMkAGZmMjA4MmM4LTQyZTYtNGVhMi04ZjNjLTdkMDI4ODM4ZTQ1OQAuAAAAAADKa2Su6/EXRrsmOefDSCL3AQBEITO0krAyRbRmLsC3JNeGAAAAAAEIAAA=" ChangeKey="AQAAAA==" />
<t:UnreadCount>0</t:UnreadCount>
</t:ModifiedEvent>
</m:Notification>
</m:Notifications>
</m:GetStreamingEventsResponseMessage>
</m:ResponseMessages>
</m:GetStreamingEventsResponse>
</soap11:Body>
</Envelope>
<Envelope
xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<soap11:Header
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<ServerVersionInfo
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" MajorVersion="15" MinorVersion="1" MajorBuildNumber="1282" MinorBuildNumber="22" Version="V2017_04_14"
xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
</soap11:Header>
<soap11:Body
xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
<m:GetStreamingEventsResponse
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages">
<m:ResponseMessages>
<m:GetStreamingEventsResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:ConnectionStatus>Closed</m:ConnectionStatus>
</m:GetStreamingEventsResponseMessage>
</m:ResponseMessages>
</m:GetStreamingEventsResponse>
</soap11:Body>
</Envelope>

"GetStreamingEvents" responses really are streamed.
This means that the http connection stays open, and the notifications are sent through it in more of a continuous flow than a standard request/response mechanism. This is similar to the way streaming a video works. You don't do a single request for the entire video. Instead, you would read the streamed data from the video as it is being sent, and process it accordingly.
You need to handle responses to "GetStreamingEvents" in a similar fashion.
The details provided in the question don't show how you are handling the responses, but I'm guessing that you are using an API that simply fetches a URL and returns a response. That works great for standard requests like the "StreamingSubscriptionRequest", but it won't work for streamed responses.
APIs that simply fetch a url won't return until a complete response is received and/or the connection is closed. That's why it seems like you're not receiving the streamed events until the timeout expires.
In fact, you are receiving those events, you're just not reading them and processing them as they come in.
To do that, you'll need to use an API that allows to you connect to an http server and read from that connection as the data arrives. For iOS, this is as simple as implementing the [connection didReceiveData:] delegate method on NSURLConnection. Presumably, there is some similar higher-level mechanism that can be used in your environment.

I also ran into this problem once, but I don't know if it is the same for you. I was using EWS over HTTP with chunked transfer encoding and also received responses only at timeout. It turned out that a terminating 0 chunk was only sent at timeout, not after each response, therefore I was always waiting for more data until the timeout.
Note that it is not too bad to have a low timeout since you might want to use multiple subscriptions in a single GetEvents request and you can't add or remove subscriptions from a running GetEvents request.

Related

How to search for all nodes in a sap.m.Tree?

I'm currently working at a MasterDetail application for my company, which provides expandable categorys represented as nodes.
The binding of the nodes and it's child nodes with a navigation property isn't a problem. However, if I want to search for a certain group node in the search field above, it only filters between the four highest nodes. It can search for the nodes on the first level, but it isn't able to find nodes if they're below the first level.
Binding of the tree:
<Tree
selectionChange="onSelectionChange"
id="list"
noDataText="{masterView>/noDataText}"
busyIndicatorDelay="{masterView>/delay}"
items="{path: '/GroupNodes',
parameters : {
expand: 'ChildGroupNodes',
navigation: {
'GroupNodes': 'ChildGroupNodes'
}
}
}">
<StandardTreeItem
title="{Stext}"
type="Navigation"
press="onSelectionChange"/>
</Tree>
onSearch:
onSearch: function(oEvent) {
if (oEvent.getParameters().refreshButtonPressed) {
this.onRefresh();
return;
}
var sQuery = oEvent.getParameter("query");
if (sQuery) {
this._oListFilterState.aSearch = [new Filter("Stext", FilterOperator.Contains, sQuery)];
} else {
this._oListFilterState.aSearch = [];
}
this._applyFilterSearch();
},
_applyFilterSearch:
_applyFilterSearch: function() {
var aFilters = this._oListFilterState.aSearch.concat(this._oListFilterState.aFilter),
oViewModel = this.getModel();
this._oList.getBinding("items").filter(aFilters, "Application");
if (aFilters.length !== 0) {
oViewModel.setProperty("/noDataText", this.getResourceBundle().getText("masterListNoDataWithFilterOrSearchText"));
} else if (this._oListFilterState.aSearch.length > 0) {
oViewModel.setProperty("/noDataText", this.getResourceBundle().getText("masterListNoDataText"));
}
},
Filterstate in the onInit() function:
this._oListFilterState = {
aFilter: [],
aSearch: []
};
Metadata:
<EntityType Name="GroupNode" sap:content-version="1">
<Key>
<PropertyRef Name="Grpid"/>
</Key>
<Property Name="Grpid" Type="Edm.String" Nullable="false" MaxLength="8" sap:unicode="false" sap:label="Id Trainingsgruppe" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
<Property Name="Short" Type="Edm.String" MaxLength="12" sap:unicode="false" sap:label="Kürzel Trainingsgruppe" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
<Property Name="Stext" Type="Edm.String" MaxLength="40" sap:unicode="false" sap:label="Bezeichnung Trainingsgruppe" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
<Property Name="Begda" Type="Edm.DateTime" Precision="0" sap:unicode="false" sap:label="Beginndatum" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
<Property Name="Endda" Type="Edm.DateTime" Precision="0" sap:unicode="false" sap:label="Endedatum" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
<Property Name="Level" Type="Edm.Int32" sap:unicode="false" sap:label="Level" sap:creatable="false" sap:updatable="false" sap:sortable="false" sap:filterable="false"/>
<Property Name="Parentid" Type="Edm.String" Nullable="false" MaxLength="8" sap:unicode="false" sap:label="ParentId" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
<NavigationProperty Name="ChildGroupNodes" Relationship="Z_HR_LSO_WORKCENTER_SRV.GroupNodeToParent" FromRole="FromRole_GroupNodeToParent" ToRole="ToRole_GroupNodeToParent"/>
<NavigationProperty Name="GroupToTrainingType" Relationship="Z_HR_LSO_WORKCENTER_SRV.GroupToTrainingType" FromRole="FromRole_GroupToTrainingType" ToRole="ToRole_GroupToTrainingType"/>
</EntityType>
We're working with OData V2, so there's no possibility to implement an FilterContains.All filter.
Is it even possible to filter through the child nodes of a sap.m.Tree in the front-end?
First of all, building the tree hierarchy via navigation is deprecated since 1.44. Instead, SAP recommends to leverage metadata annotations:
The use of navigation properties to build up the hierarchy structure is deprecated and it is recommended to use the hierarchy annotations [...].
Once the migration to the annotation approach is done, filter either client-side or server-side.
Client-side
The operationMode of the ODataTreeBinding (or the defaultOperationMode of the v2.ODataModel) needs to be "Client".
When calling .filter, the FilterType as the 2nd argument needs to be "Control".
Here is a working example: https://embed.plnkr.co/moTGOT
Server-side
For server-side filtering, only the FilterType "Application" and the operationMode: "Server" are supported. In that case, the server needs to respond to the $filter request with a ready-made tree structure. The same applies to paging requests for sibling and child nodes.
Limitations
suspended: true in the binding info is not yet supported (Issue #3161).
The above content applies to OData V2 only.
V4 ODataModel doesn't support tree binding at all yet (Issue #2728).
I think this may have something to do with the way you're binding the items, because I am able to filter on child nodes just fine in my example using your JS.
Check that this._oList.getItems() has all the items in it before you do the filter.
I'll post my code, so you can rebuild my project and get a feel for how it works. Let me know if you have more questions.
Page.view.xml
<mvc:View
controllerName="sap.m.sample.Tree.Page"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<SearchField value="{json>/search}" search="onSearch"/>
<Tree
id="Tree"
items="{path: '/'}">
<StandardTreeItem title="{text}"/>
</Tree>
</mvc:View>
Page.controller.js
sap.ui.define(['sap/ui/core/mvc/Controller', 'sap/ui/model/json/JSONModel', 'sap/ui/model/Filter', 'sap/ui/model/FilterOperator'],
function(Controller, JSONModel, Filter, FilterOperator) {
"use strict";
var PageController = Controller.extend("sap.m.sample.Tree.Page", {
onInit: function(evt) {
// set explored app's demo model on this sample
var oModel = new JSONModel(jQuery.sap.getModulePath("sap.m.sample.Tree", "/Tree.json"));
this.getView().setModel(oModel);
var oJSONModel = new JSONModel();
this.getView().setModel("json", oJSONModel);
this._oList = this.byId("Tree");
this._oListFilterState = {
aFilter: [],
aSearch: []
};
},
onSearch: function(oEvent) {
if (oEvent.getParameters().refreshButtonPressed) {
this.onRefresh();
return;
}
var sQuery = oEvent.getParameter("query");
if (sQuery) {
this._oListFilterState.aSearch = [new Filter("text", FilterOperator.Contains, sQuery)];
} else {
this._oListFilterState.aSearch = [];
}
this._applyFilterSearch();
},
_applyFilterSearch: function() {
var aFilters = this._oListFilterState.aSearch.concat(this._oListFilterState.aFilter);
//oViewModel = this.getModel();
this._oList.getBinding("items").filter(aFilters, "Application");
// if (aFilters.length !== 0) {
// oViewModel.setProperty("/noDataText", this.getResourceBundle().getText("masterListNoDataWithFilterOrSearchText"));
// } else if (this._oListFilterState.aSearch.length > 0) {
// oViewModel.setProperty("/noDataText", this.getResourceBundle().getText("masterListNoDataText"));
// }
}
});
return PageController;
});
Tree.json
[
{
"text": "Node1",
"ref": "sap-icon://attachment-audio",
"nodes":
[
{
"text": "Node1-1",
"ref": "sap-icon://attachment-e-pub",
"nodes":[
{
"text": "Node1-1-1",
"ref": "sap-icon://attachment-html"
},
{
"text": "Node1-1-2",
"ref": "sap-icon://attachment-photo",
"nodes":[
{
"text": "Node1-1-2-1",
"ref": "sap-icon://attachment-text-file",
"nodes":[
{
"text": "Node1-1-2-1-1",
"ref": "sap-icon://attachment-video"
},
{
"text": "Node1-1-2-1-2",
"ref": "sap-icon://attachment-zip-file"
},
{
"text": "Node1-1-2-1-3",
"ref": "sap-icon://course-program"
}
]
}
]
}
]
},
{
"text": "Node1-2",
"ref": "sap-icon://create"
}
]
},
{
"text": "Node2",
"ref": "sap-icon://customer-financial-fact-sheet"
}
]
That's all you should need, but just incase you need these as well...
index.html
<!DOCTYPE HTML>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>Tree - Basic</title>
<script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_belize"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-preload="async"
data-sap-ui-compatVersion="edge"
data-sap-ui-resourceroots='{"sap.m.sample.Tree": "./", "sap.ui.demo.mock": "mockdata"}'>
</script>
<!-- Application launch configuration -->
<script>
sap.ui.getCore().attachInit(function() {
new sap.m.App ({
pages: [
new sap.m.Page({
title: "Tree - Basic",
enableScrolling : true,
content: [ new sap.ui.core.ComponentContainer({
name : "sap.m.sample.Tree"
})]
})
]
}).placeAt("content");
});
</script>
</head>
<!-- UI Content -->
<body class="sapUiBody" id="content" role="application">
</body>
Component.js
sap.ui.define(['sap/ui/core/UIComponent'],
function(UIComponent) {
"use strict";
var Component = UIComponent.extend("sap.m.sample.Tree.Component", {
metadata : {
rootView : "sap.m.sample.Tree.Page",
dependencies : {
libs : [
"sap.m",
"sap.ui.layout"
]
},
config : {
sample : {
files : [
"Page.view.xml",
"Page.controller.js",
"Tree.json"
]
}
}
}
});
return Component;
});
Not sure if you managed to solve your problem, but I actually did this exact thing a few months ago.
In your controller, put the following function:
onSearch: function(oEvent) {
var searchVal = oEvent.getParameter("newValue");
var treeFilter = new sap.ui.model.Filter("Text", sap.ui.model.FilterOperator.Contains, searchVal);
var oBinding = this.getView().byId("tree").mBindingInfos.rows.binding;
oBinding.filter(treeFilter, FilterType.Application);
oBinding.expandToLevel(3);
},
And this should work straight away. Whatever you enter in the search box, it will populate the tree (up to 3 levels, but this can be changed) with your filter.
The onSearch function is of course executed on the livechange of the search field.

How does one nest Twilio Twiml <Play> inside <Gather> using Twilio\Twiml php

How would I nest Play inside Gather ?
I have the following Twiml xml:
$twiml = new Twiml();
$twiml->gather('gather',array());
$twiml->play('https://api.twilio.com/cowbell.mp3', array("loop" => 5));
$response = Response::make($twiml, 200);
$response->header('Content-Type', 'text/xml');
return $response;
required result:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather input="speech" action="/completed">
<Play loop="5">https://api.twilio.com/cowbell.mp3</Play>
</Gather>
Use this:
$twiml = new Twilio\Twiml();
$gather = $twiml->gather(array('input' => 'speech', 'action' => '/completed'));
$gather->play('https://api.twilio.com/cowbell.mp3', array("loop" => 5));
$response = Response::make($twiml, 200);
$response->header('Content-Type', 'text/xml');
return $response;

Savon ignores namespace_identifier attribute

Am trying to re-write written by savon body namespace - ins0
I have this client variable:
client = Savon.client(wsdl: "https://integcert.synxis.com/interface/Contracts/ChannelConnect2004.wsdl", log_level: :debug, log: true, pretty_print_xml: true, env_namespace: :soapenv, namespaces: {"xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:head": "http://htng.org/1.1/Header/","xmlns:ns": "http://www.opentravel.org/OTA/2003/05"}, convert_request_keys_to: :none, namespace_identifier: "ns", element_form_default: :qualified)
And when Am doing the request:
client.call(:ping, soap_header: { "head:HTNGHeader": { "head:From": { "head:Credential": { "head:userName": "******", "head:password":"*****" }}}}, message: {"ns:EchoData": "TestData"})
I've got this soap rq body:
<soapenv:Body>
<ins0:OTA_PingRQ>
<ns:EchoData>TestData</ns:EchoData>
</ins0:OTA_PingRQ>
</soapenv:Body>
Where this ins0 is came from?
Also when I tried to define client with namespace_identifier: nil parameter and did this kind of request:
client.call(:ping, soap_header: { "head:HTNGHeader": { "head:From": { "head:Credential": { "head:userName": "******", "head:password":"*****" }}}}, message: {"ns:OTA_PingRQ": {"ns:EchoData": "TestData"}})
I've got this soap rq body:
<soapenv:Body>
<OTA_PingRQ>
<ns:OTA_PingRQ>
<ns:EchoData>TestData</ns:EchoData>
</ns:OTA_PingRQ>
</OTA_PingRQ>
And the correct body that I want to have is:
<soapenv:Body>
<ns:OTA_PingRQ>
<ns:EchoData>TestData</ns:EchoData>
</ns:OTA_PingRQ>
</soapenv:Body>
Any ideas how to remove additional nested OTA_PingRQnode or replace ins0 namespace by the custom ?
Your idea with additional namespaces was logically correct, but it doesn't look like Savon really wants to use them everywhere. It prefers to use namespaces found in WSDL. I tried element_form_default: :qualified and element_form_default: :unqualified, but it still puts ins0 as a namespace for OTA_PingRQ node everytime. I thought if it wants ins0 then let's use ins0 then.
I looked at the list of available namespaces and found appropriate one for the header as well. Have no idea why Savon doesn't want to specify namespace for header nodes automatically, so we have to specify it manually as ins1.
Here is working version of configuration:
client = Savon.client(
wsdl: "https://integcert.synxis.com/interface/Contracts/ChannelConnect2004.wsdl",
log_level: :debug,
log: true,
pretty_print_xml: true,
namespace_identifier: :ins0,
element_form_default: :qualified,
soap_header: {
"ins1:HTNGHeader": {
"ins1:From": {
"ins1:Credential": {
"ins1:userName": "******",
"ins1:password":"*****"
}
}
}
}
)
client.call(:ping, message: {"EchoData": "TestData"})
Request:
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ins0="http://www.opentravel.org/OTA/2003/05" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ins1="http://htng.org/1.1/Header/">
<env:Header>
<ins1:HTNGHeader>
<ins1:From>
<ins1:Credential>
<ins1:userName>******</ins1:userName>
<ins1:password>*****</ins1:password>
</ins1:Credential>
</ins1:From>
</ins1:HTNGHeader>
</env:Header>
<env:Body>
<ins0:OTA_PingRQ>
<ins0:echoData>TestData</ins0:echoData>
</ins0:OTA_PingRQ>
</env:Body>
</env:Envelope>
Response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<OTA_PingRS xmlns="http://www.opentravel.org/OTA/2003/05" PrimaryLangID="en">
<Errors>
<Error Type="4" ShortText="Login failed"/>
</Errors>
<EchoData/>
</OTA_PingRS>
</soap:Body>
</soap:Envelope>
EDIT: Provided soap_header at the client configuration, because it's more efficient way.
There is another way to attach namespace_identifier also.
First set namespace_identifier: nil in Savon client.
basic_auth = [username, password]
client = Savon.client(
wsdl:"https://integcert.synxis.com/interface/ChannelConnect2004.wsdl",
namespace: {},
env_namespace: 'soapenv',
namespace_identifier: nil,
basic_auth: basic_auth,
logger: Rails.logger,
log: true,
soap_version: 2,
pretty_print_xml: true,
convert_request_keys_to: :camelcase,
namespaces: {
'xmlns:soap' => 'http://example.com/soap-envelope',
'xmlns:glob' => 'http://example.com/Global'
}
)
Now while calling the operation set message_tag
response = client.call(:operation_name, message_tag: 'glob:OTA_PingRQ', message: {"EchoData": "TestData"})
Output:
<soapenv:Body>
<glob:OTA_PingRQ>
<EchoData>TestData</ns:EchoData>
</glob:OTA_PingRQ>
</soapenv:Body>
For Outoput like:
<soapenv:Body>
<ns:OTA_PingRQ>
<ns:EchoData>TestData</ns:EchoData>
</ns:OTA_PingRQ>
</soapenv:Body>
client call will be same but in client config add **element_form_default: :qualified**

Plain text 400 error message in struts2 + Tomcat

I want an action to issue a 400 error HTTP response with an explanation of the problem in text/plain whenever a validation fails.
I can use org.apache.struts2.dispatcher.HttpHeaderResult to get the 400 error:
#Action(results = {
#Result(name = SUCCESS, type=ResultTypes.HTTP_HEADER,
params = { "status", "200" }),
#Result(name = INPUT, type=ResultTypes.HTTP_HEADER,
params = { "error", "400", "errorMessage", "${errorMessage}" })
})
#Override
public String execute() {
return INPUT;
}
public String getErrorMessage() {
return "There was an error";
}
The problem is that Tomcat "wraps" my response into an HTML page as follows:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 1214
Date: Sun, 20 Mar 2011 00:43:55 GMT
Connection: close
<html><head><title>Apache Tomcat/6.0.29 - Error report</title><style><!--
H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;}
H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;}
H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;}
BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;}
B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;}
P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}
A {color : black;} A.name {color : black;} HR {color : #525D76;}--></style> </head>
<body><h1>HTTP Status 400 - There was an error</h1><HR size="1" noshade="noshade">
<p><b>type</b> Status report</p><p><b>message</b> <u>There was an error</u></p>
<p><b>description</b> <u>The request sent by the client was syntactically incorrect (There was an error).</u></p>
<HR size="1" noshade="noshade"><h3>Apache Tomcat/6.0.29</h3></body></html>
While I would simply want something like:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=utf-8
Content-Length: 18
Date: Sun, 20 Mar 2011 00:43:55 GMT
Connection: close
There was an error
Thanks.
specify the page to use for response code using the error-page configuration in web.xml
<error-page>
<error-code>400</error-code>
<location>/error/400.html</location>
</error-page>
You could use:
- your action code -
if (errorOccurs)
return ERROR;
struts.xml:
<action name="..." class="...">
<result type="json"> <!-- success -->
<param name="root">...</param>
</result>
<result name="error" type="json"> <!-- error -->
<param name="root">...</param>
<param name="statusCode">500</param>
</result>
</action>

How to create custom soap fault message using spring web service

I'm trying to write a web service using spring web service library. I'm able to configure my endpoints successfully and it is working fine, but I'm facing some issues with the exception mappings.
I'm able to map exceptions using #SoapFault and SoapFaultAnnotationExceptionResolver but the wsdl definition is as follows
<xsd:schema elementFormDefault="qualified" targetNamespace="http://abc.com/soap/">
<xsd:complexType name="ServiceException">
<xsd:sequence>
<xsd:element name="message" nillable="true" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ValidationException">
<xsd:complexContent>
<xsd:extension base="tns:ServiceException">
<xsd:sequence/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="InternalException">
<xsd:complexContent>
<xsd:extension base="tns:ServiceException">
<xsd:sequence/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="AuthenticationException">
<xsd:complexContent>
<xsd:extension base="tns:ServiceException">
<xsd:sequence/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="LoginInput">
<xsd:sequence>
<xsd:element minOccurs="1" maxOccurs="1" name="UserName" nillable="false" type="xsd:string"/>
<xsd:element minOccurs="1" maxOccurs="1" name="PassWord" nillable="false" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="LoginOutput">
<xsd:sequence>
<xsd:element minOccurs="1" maxOccurs="1" name="ValidTo" nillable="false" type="xsd:dateTime"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="login" type="tns:LoginInput"/>
<xsd:element name="loginResponse" type="tns:LoginOutput"/>
<xsd:element name="ValidationException" type="tns:ValidationException"/>
<xsd:element name="InternalException" type="tns:InternalException"/>
<xsd:element name="AuthenticationException" type="tns:AuthenticationException"/>
</xsd:schema>
<message name="LoginRequest">
<part name="login" element="tns:login"/>
</message>
<message name="LoginResponse">
<part name="loginResponse" element="tns:loginResponse"/>
</message>
<message name="ValidationException">
<part name="ValidationException" element="tns:ValidationException"/>
</message>
<message name="InternalException">
<part name="InternalException" element="tns:InternalException"/>
</message>
<message name="AuthenticationException">
<part name="AuthenticationException" element="tns:AuthenticationException"/>
</message>
<portType name="ServicePortType">
<operation name="Login">
<input message="tns:LoginRequest"/>
<output message="tns:LoginResponse"/>
<fault name="ValidationException" message="tns:ValidationException"/>
<fault name="InternalException" message="tns:InternalException"/>
<fault name="AuthenticationException" message="tns:AuthenticationException"/>
</operation>
</portType>
<binding name="ServiceBinding" type="tns:ServicePortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="Login">
<soap:operation soapAction="urn://Service#Login"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
<fault name="ValidationException">
<soap:fault name="ValidationException" use="literal"/>
</fault>
<fault name="InternalException">
<soap:fault name="InternalException" use="literal"/>
</fault>
<fault name="AuthenticationException">
<soap:fault name="AuthenticationException" use="literal"/>
</fault>
</operation>
</binding>
How can I write a exception handling for this service definition?
Thank you
After some more search I found this from spring source forum.
SoapMessage response = (SoapMessage) messageContext.getResponse();
SoapBody soapBody = response.getSoapBody();
SoapFault soapFault =
soapBody.addClientOrSenderFault(ex.getMessage(), Locale.ENGLISH);
SoapFaultDetail faultDetail = soapFault.addFaultDetail();
Result result = faultDetail.getResult();
// My detail XML object
InvalidArgumentFault fault = new InvalidArgumentFault();
fault.setErrorCode("Custom Error Code");
fault.setOpsMessage("This is the ops message");
fault.setSystemMessage("This is the system message");
// Marshal the detail. We have to use the ObjectFactory which isn't
// marshaller agnostic because the detail element doesn't have an
// XmlRootElement tag as required by JAXB.
ObjectFactory of = new ObjectFactory();
mMarshaller.marshal(of.createInvalidArgumentFault( fault), result);
UPDATED
This a complete sample implementations I'm using,
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.xml.transform.Result;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.oxm.Marshaller;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.AbstractEndpointExceptionResolver;
import org.springframework.ws.soap.SoapBody;
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.SoapMessage;
import org.apache.commons.lang.StringUtils;
public class CustomSoapFaultDetailAnnotationExceptionResolver extends
AbstractEndpointExceptionResolver implements ApplicationContextAware {
private static Log log = LogFactory
.getLog(CustomSoapFaultDetailAnnotationExceptionResolver.class);
private Collection<Marshaller> marshallers;
private Map<Class<? extends Object>, Marshaller> marshallerMap = new HashMap<Class<? extends Object>, Marshaller>();
public CustomSoapFaultDetailAnnotationExceptionResolver() {
setWarnLogCategory(getClass().getCanonicalName());
}
#Override
protected boolean resolveExceptionInternal(MessageContext messageContext,
Object endpoint, Exception ex) {
boolean resolved = false;
try {
CustomSoapFaultDetails annotation = ex.getClass().getAnnotation(
CustomSoapFaultDetails.class);
if (annotation != null) {
Method m = ex.getClass().getMethod("getFaultInfo",
new Class[] {});
Object fault = m.invoke(ex, new Object[] {});
SoapMessage response = (SoapMessage) messageContext
.getResponse();
SoapBody soapBody = response.getSoapBody();
SoapFault soapFault = soapBody
.addClientOrSenderFault(
StringUtils.isBlank(ex.getMessage()) ? "server exception"
: ex.getMessage(), Locale.ENGLISH);
SoapFaultDetail faultDetail = soapFault.addFaultDetail();
Result result = faultDetail.getResult();
if (marshallerMap.containsKey(fault.getClass())) {
marshallerMap.get(fault.getClass()).marshal(fault, result);
resolved = true;
} else {
for (Marshaller marshaller : marshallers) {
try {
marshaller.marshal(fault, result);
marshallerMap.put(fault.getClass(), marshaller);
resolved = true;
break;
} catch (Exception e) {
// Ignore error
}
}
}
}
} catch (Exception e) {
log.error(e.toString(), e);
}
return resolved;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.marshallers = applicationContext.getBeansOfType(Marshaller.class)
.values();
}
}
I have successfully used SOAP Spring-WS to create a web service. Now I was doing the fault implementation in this SOAP service and I was able to do it with a class that I created like the following,
public class ServiceSoapFaultMappingExceptionResolver extends SoapFaultMappingExceptionResolver
and inside the following overridden function,
#Override
protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
//code for adding fault details
SoapFaultDefinition soapFaultDefinition = new SoapFaultDefinition();
String ENVELOPE_NAMESPACE_URI = "http://schemas.xmlsoap.org/soap/envelope/";
// soapFaultDefinition.setFaultStringOrReason("--" + ex);
// soapFaultDefinition.setLocale(Locale.ENGLISH);
QName CLIENT_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI,"5003", "e");
soapFaultDefinition.setFaultCode(CLIENT_FAULT_NAME);
setDefaultFault(soapFaultDefinition);
Result result = fault.addFaultDetail().getResult();
// marshal
try {
JAXBContext.newInstance(ExceptionListType.class).createMarshaller().marshal(exceptionList, result);
}
Please this answer for the original solution.

Resources