A Web service consumer implements the MSenServiceConsumer
interface
and its three callback methods:
void MSenServiceConsumer
Used for receiving a response to a submitted request. The parameter aMessage represents the incoming message.
Note that the Web service consumer may set whether or not it wishes to receive complete SOAP envelopes (see the Setting complete server messages on/off section).
This function gets called when an error occurs. The parameter aErrorCode specifies the error and the parameter aMessage describes the error.
void MSenServiceConsumer
This function is called when the status of the connection changes. The parameter aStatus is a connection state indicator, which could be specified by the actual framework implementation. The following status codes (defined in SenServiceConnection.h) are possible for any installed framework:
KSenConnectionStatusNew: When the connection is created but not yet ready to use.
KSenConnectionStatusReady: The connection is initialized and ready to be used.
In addition, ID-WSF may notify about KSenConnectionStatusExpired. This is the case if the connection has expired.
Figure 6: Web service consumer implements the MSenServiceConsumer interface to use WSF API
Typically, the use of a Web service is done in seven phases:
Checking if Active Scheduler is started
Registering a service information
Creating a connection
Setting complete server messages on/off
Sending a request and receiving a response
Handling a response - XML Parsing
Closing a connection
See the following sections for detailed instructions on how to use the interfaces.
S60 SDK contains the example application using Web Service Framework called AddressBook.
Before using WSF, the Web service consumer should check that the active
scheduler is started. This can be done by calling CActiveScheduler::Current()
.
If the return value is NULL, the scheduler should have been started. Notice
that before starting the scheduler by calling CActiveScheduler::Start()
an
active scheduler object should have been instantiated. The created instance
should be installed by calling CActiveScheduler::Install(CActiveScheduler*
)
and some active object should have been created and added to the
active scheduler by calling CActiveScheduler::Add(CActive*)
.
At least one request function should also have been issued, otherwise the
thread will hang.
Service-related data is registered using Service Manager. Note that Service Manager is not framework-specific. In Basic Web Services, the consumer application’s only applicable operation is managing identity data. This can be used to make connection specific Basic Authentication (BASIC-AUTH) credentials available without later end-user interaction (otherwise the required authentication information is requested via notifier dialog prompts).
An ID-WSF consumer has to use Service Manager to register a service description and identity provider and finally associate these two together.
Service Manager is meant to be used before the developer creates a connection to a certain service.
It is highly recommended that the password (credential) is registered only
when the WSC application is started for the first time. Otherwise, if the
password is being changed by the end user at some point, that will be overwritten
byMSenServiceManager::RegisterIdentityProviderL()
passing
the password value. To accomplish this, the actual WSC application should
have this knowledge (i.e. the initial password being already registered) serialized
into some persistent storage (such as the file system or a database).
In ID-WSF’s case, the developer does the following sequence:
Instantiates Service Manager (CSenServiceManager
).
Instantiates an Identity Provider (CSenIdentityProvider
)
and registers it using the Service Manager instance. Note that if an IDP with
the same endpoint and provider ID exists, any properties, including the credentials
(such as password) are updated.
Associates the actual service to the identity provider using the appropriate service ID.
Figure 7: Registering ID-WSF
The following example describes the Service Manager usage in the ID-WSF case. iManager is an instance variable and will be destroyed in the destructor:
// Literals (usually found in a header file) _LIT8(KASEndPoint, "http://selma.ndhub.net:9080/tfs/IDPSSO_IDWSF"); _LIT8(KASContract, "urn:liberty:as:2004-04"); _LIT8(KASProviderID, "http://selma.ndhub.net:9080/tfs/"); _LIT8(KAuthzID, "testuser1"); _LIT8(KPassword, "testuser1"); _LIT8(KSPContract, "urn:nokia:test:addrbook:2004-09"); // Creating an instance of CSenServiceManager iManager = CSenServiceManager::NewL(); CSenIdentityProvider* idp = CSenIdentityProvider::NewLC( KASEndPoint, KASContract); // ProviderID must be unique idp->SetProviderID(KASProviderID); // Associate Forum Nokia's Addressbook service ID (contract) // to this Identity Provider idp->SetServiceID(KSPContract); // Set Liberty ID-WSF framework ID idp->SetFrameworkIdL(KDefaultIdWsfFrameworkID); // ------------------------------------------------------------------------ // The following username/password properties will be used for // authentication. Please note, that using advisory authentication // id of "IMEI" will result device ID to be directly fetced from phone. // ------------------------------------------------------------------------ // idp->SetUserInfo(KAuthzID, KNullDesC8, KPassword); // ------------------------------------------------------------------------ // The next, (commented) line below would cause password to be // prompted from user through dialog presented by Serene: // idp->SetUserInfo(KAuthzID, KNullDesC8, KNullDesC8); // ------------------------------------------------------------------------ // // Registering the IdP to the WSF TInt retVal = iManager->RegisterIdentityProviderL(*idp); if(retVal != KErrNone) { // Error occurred. If there was ProviderID conflict, the application // could of course unregister the conflicting IDP using same // ProviderID and re-attempt registrate its own. But generally, // ProviderIDs should be checked to be unique. } // Register this IDP to be authentication service, too retVal = iManager->RegisterServiceDescriptionL(*idp); CleanupStack::PopAndDestroy(); // idp
Liberty IDP consists of a unique provider ID and some endpoint. Any service that is known to trust certain IDP should have its unique service ID associated with this IDP. IDP may also require authentication credentials. In the above code, the last two API calls register this information about an IDP, which is trusted by an Address book service. Later, when a service connection to this identity consuming Address book service is requested (by telling its contract only) the WSF knows one endpoint more where to search for the identity provider. This means that the WSF IDP registry is shared among all service consumer applications.
Note that when registering an IDP, there is no need to specify a framework ID because it defaults to “ID-WSF”. Also, the default contract is "urn:liberty:as:2004-04" (Liberty authentication service) so there is no need to set it either. Register an authentication service description using the above identity provider instance.
After the last association call, the connection can be initialized.
In a basic WS case, the sequence is a bit different. Service Manager (and any of its functionalities) is not needed at all if the developer is not connecting to a service that is behind basic authentication. If basic authentication is required, the developer can perform the following steps to avoid the authentication dialog to be popped:
Instantiate Service Manager (CSenServiceManager
)
Instantiate an identity provider (CSenIdentityProvider
)
and register it using the Service Manager instance created in step1. Note
that if an IDP with the same endpoint exists, any properties, including credentials
(such as username and password) are overwritten.
Figure 8: Registering Basic WS
The following example describes the Service Manager usage in the Basic Web Service (WS-I) case. iManager is an instance variable and will be destroyed in the destructor:
// Creating an instance of CSenServiceManager CSenServiceManager* pManager = CSenServiceManager::NewLC(); // Creating an instance of IdP and registering it. Notice, that endpoint is the // endpoint to the actual Web Service Provider. CSenIdentityProvider* pIdp = CSenIdentityProvider::NewLC(KWSPEndPoint()); // Set BASIC-AUTH credentials pIdp->SetUserInfo(KUser, KUser, KPass); // Set FrameworkID (constant declared in SenServiceConnection.h) pIdp->SetFrameworkID(KDefaultBasicWebServicesFrameworkID); TInt error = pManager->RegisterIdentityProviderL(*pIdp); CleanupStack::PopAndDestroy(2); // pManager, pIdp
Each WS-I service has its unique authentication endpoint acting as an identity provider. Therefore, no provider ID needs to be specified.
A service connection has to be initialized before it can be used. The initialization
process starts by creating a service connection object. Getting a connection
is an asynchronous operation, meaning that the connection is ready when the
client’s SetStatus()
callback function gets called with
the status code KSenConnectionStatusReady. Note that actually no requests
have yet been sent to the WSP at this stage. This means that the Web service
consumer cannot rely on the existence of the Web service provider and therefore
the requests may fail. In ID-WSF, the connection may also expire.
Figure 9: Connection’s state
The creation of a connection is done by creating a CSenServiceConnection
object
using the provided CSenServiceConnection
or CSenServiceConnection
factory
methods. The client registers itself as an observer by passing a reference
to itself (*this) as the first parameter to the factory methods for the callback
functions presented in the MSenServiceConsumer
interface:.
virtual void HandleMessageL(const TDesC8& aMessage) = 0; virtual void HandleErrorL(const TInt aErrorCode, const TDesC8& aMessage) = 0; virtual void SetStatus(const TInt aStatus) = 0;
These functions are the callback functions for receiving the messages, errors and status data of the service connection. These functions are used in the connection creation phase and when sending messages to the WSP.
TheCSenServiceConnection
or CSenServiceConnection
factory
methods can be used in three ways by changing the second parameter:
By passing a contract (an URN) in a descriptor
By passing a reference to MSenServiceDescription
By passing a reference to a CSenServicePattern
object
In the connection creation phase (iConnection is instantiated), the execution goes the following way:
Instantiating CSenServiceConnection
. This creates
an asynchronous call to the WSF server and the execution is returned to the
calling application almost immediately.
The server processes the connection creation. It connects to the authentication service and requests the wanted service endpoint from the discovery service. In the client-side, the execution is in the application using WSF. Usually, the client application shows a progress bar to the user meaning that the server is processing the request.
When the server has finished the request, it calls the SetStatus()
callback
function with KSenConnectionStatusReady. The call means that the WSP endpoint
is discovered and that the credentials to the service are in valid state.
The client application can now start the transaction with the WSP using
the CSenServiceConnection
and CSenServiceConnection
functions. CSenServiceConnection
is asynchronous and therefore the response from the server is received through
the HandleMessageL()
callback function. CSenServiceConnection
is
synchronous and therefore the execution is blocked in the client-side until
the message is received from the WSP.
The cases between ID-WSF and Basic Web Services differ also a bit. This section describes the actions that are faced when using ID-WSF:
Instantiate a service description that is used to create the connection. The contract to be set to the service description must be the same as is associated to the IDP in the registration phase (see the ID-WSF section).
Create a service connection using the service description instantiated and initialized in step 1.
Figure 10: Creating Connection for ID-WSF
The following example describes the connection creation using the ID-WSF. iConnection is an instance variable and is destroyed in the destructor. This code makes it possible to connect to an ID-WSF service. When this code is executed for the first time, authentication and discovery services are connected and the user is therefore authenticated. When this code is executed again and the credentials received from the authentication and discovery services are valid, there is no need to connect to them again.
_LIT8(KSPContract, "urn:nokia:test:addrbook:2004-09"); // Instantiating the Service Description CSenXmlServiceDescription* pattern = CSenXmlServiceDescription::NewLC(); // Setting the contract. Notice that this contract must be the same that was // associated in the registration phase (ServiceType) pattern->SetContractL(KSPContract()); iConnection = CSenServiceConnection::NewL(*this, *pattern); CleanupStack::PopAndDestroy(); // pattern
The consumer is only required to know the contract of the service. WSF then attempts to resolve the endpoint to the identity providing the service and to utilize credentials to discover the service.
In the Basic Web Services case, the connection creation differs from the ID-WSF case only in a few places.
Figure 11: Creating Connection for WS-I
The following example describes the connection creation in the Basic Web Services case:
// Instantiates the SD used for creating the connection. // If the application is connecting to a service that is behind basic // authentication the KWSPEndPoint literal must be the // same that was used in the IDP registration. CSenXmlServiceDescription* pPattern = CSenXmlServiceDescription::NewLC(KWSPEndPoint(), KNullDesC8()); // This FrameworkID setting tells the WSF that a basic WS is connected. // KBasicWebServicesFrameworkID is the FrameworkID for Basic Web Services // as defined in SenServiceConnectin.h. pPattern->SetFrameworkIdL( KBasicWebServicesFrameworkID() ); // Construct the connection. Here this same class has also implemented // the required callback functions from MSenServiceConsumer interface. iConnection = CSenServiceConnection::NewL( *this, *pPattern ); CleanupStack::PopAndDestroy(); // pPattern
The WS-I framework is selected by setting the framework ID into the service description instance. The endpoint of the actual service is known in advance.
The callback functionality is the same as in the ID-WSF case. The only
difference is the speed of the connection initialization. Because the authentication
and discovery services are not connected, the SetStatusL()callback
function
call with KSenConnectionStatusReady is received immediately. This means that
the application can start the transaction with the WSP using the CSenServiceConnection
and CSenServiceConnection
-functions.
The Service Consumer sets the messages received to be complete (on) or incomplete (off). In the case of incomplete messages, only the actual response from the service is delivered to the service consumer.
Calling CSenServiceConnection
causes
the client to receive the whole SOAP message. Otherwise, the response only
contains the service-specific content (the SOAP message body).
Note: Calling this function must not be done before connection is initialized
(the observer's SetStatus()
has been called with value
KSenConnectionStatusReady). Calling this function should be done before sending
or submitting anything.
In WSF frameworks the default settings are:
In ID-WSF, the complete server messages is OFF.
In Basic Web Services, the default is ON.
Figure 12: Setting complete server messages on/off
The following example describes the setting complete server messages in on/off by the example of the ID-WSF.
_LIT8(KSPContract, "urn:nokia:test:addrbook:2004-09"); // Instantiating the Service Description CSenXmlServiceDescription* pattern = CSenXmlServiceDescription::NewLC(); // Setting the contract. Notice that this contract must be the same that was // associated in the registration phase (ServiceType) pattern->SetContractL(KSPContract()); iConnection = CSenServiceConnection::NewL(*this, *pattern); // Setting the flag in ON to receive the whole SOAP message TInt error = iConnection->CompleteServerMessagesOnOff(ETrue); CleanupStack::PopAndDestroy(); // pattern
After the connection has been established (the client application has received
a notification to the SetStatus()
function), a call to
the specific service can be done by using one of the two functions CSenServiceConnection
or CSenServiceConnection
.
If an error occurs, the client’s HandleErrorL()
function
is called. Finally, the connection should be released by deleting the connection
object.
It has two versions:
CServiceConnection::SendL(const TDesC8& aRequest)
CSenServiceConnection
Figure 13: Using the SendL() function
If CSenServiceConnectionSendL()
is used, the client
application’s callback function HandleMessageL()
gets called
after the services response can be delivered to the client.
The following example describes the sending request in asynchronous case:
_LIT8(KSPContract, "urn:nokia:test:addrbook:2004-09"); // Instantiating the Service Description CSenXmlServiceDescription* pattern = CSenXmlServiceDescription::NewLC(); // Setting the contract. Notice that this contract must be the same that was // associated in the registration phase (ServiceType) pattern->SetContractL(KSPContract()); iConnection = CSenServiceConnection::NewL(*this, *pattern); // Setting KRequest _LIT8(KRequest, "<ab:Query xmlns:ab=\"urn:nokia:test:addrbook:2004-09\"><ab:QueryItem><ab:Select>/ab:Card[containts[ab:N/ab:FAMILY,\'Bob\'] or containts[ab:N/ab:GIVEN,\'Bob\'] or containts[ab:TEL,\'Bob\']]</ab:Select></ab:QueryItem></ab:Query>"); iConnection->SendL(KRequest); CleanupStack::PopAndDestroy(); // pattern
It has two versions:
Figure 14: Using the SubmitL() function
In the case of CSenServiceConnection
, the
response is passed in the second parameter (aResponse) for which the necessary
memory is allocated. If the pointer points to allocated memory, the memory
is deleted. The user is responsible for the deletion of the parameter (the
change of ownership).
The following example describes the sending request in synchronous case:
_LIT8(KSPContract, "urn:nokia:test:addrbook:2004-09"); // Instantiating the Service Description CSenXmlServiceDescription* pattern = CSenXmlServiceDescription::NewLC(); // Setting the contract. Notice that this contract must be the same that was // associated in the registration phase (ServiceType) pattern->SetContractL(KSPContract()); iConnection = CSenServiceConnection::NewL(*this, *pattern); // Setting the request _LIT8(KRequest, "<ab:Query xmlns:ab=\"urn:nokia:test:addrbook:2004-09\"><ab:QueryItem><ab:Select>/ab:Card[containts[ab:N/ab:FAMILY,\'Bob\'] or containts[ab:N/ab:GIVEN,\'Bob\'] or containts[ab:TEL,\'Bob\']]</ab:Select></ab:QueryItem></ab:Query>"); HBufC8* pResponse = NULL; iConnection->SubmitL(KRequest, pResponse); if(pResponse) { CleanupStack::PushL(pResponse); iLog->Log( _L("Received response:") ); LogResultL( *pResponse ); CleanupStack::PopAndDestroy(); // pResponse } else { iLog->Log( _L(" Response NULL! ") ); } CleanupStack::PopAndDestroy(); // pattern
Once the service consumer has received the response from the service, the response may be handled using WSF’s helper classes. The response is a descriptor containing the SOAP message or at least its body (depending on the complete server messages setting, see the Setting complete server messages on/off section) returned from the service. The response may be parsed using either CSenSoapEnvelope (from the Service Connection library) or by using XML Extensions directly.
One thing to notice in XML parsing is the size of the document. CSenDomFragment (from the XML Extensions library) builds an in-memory DOM tree from the document. If this approach is too memory-consuming, then the parsing has to be done as in SAX. This is shown in the AddressBook example, that contains in S60 SDK.
Usually the user of this component (the XML client application) inherits CSenBaseFragment
and
overrides the necessary methods from MSenContentHandlerClient
.
The client also creates an instance of CSenXmlReader
. CSenBaseFragment
contains
the methods to set the created CSenXMLReader
and to start
the parsing. The receiver (the content handler) of the callbacks defined in MSenContentHandlerClient
can
also be switched (this can be called as delegating the parsing). The client
may also implement additional classes inherited from CSenBaseFragment
to
which the parsing may be delegated.
Note that by using CSenBaseFragment
, the attributes
in the XML document’s elements are not available but namespaces are. Instead,
by using CSenDomFragment
, the attributes (and also namespaces)
are available.
Each CSenBaseElement
object has an array of local namespaces
(which it owns). It also has one pointer, which points to the default namespace
in the namespace array. When an element is added, the namespaces are checked.
If its ancestors already have any of these namespaces in their local namespace
arrays, the child’s namespaces are removed. This way each namespace exists
only once.
When an element gets detached, its namespace is constructed as follows:
If the elements are:
<root xmlns=”namespace”> <a> <b> </b> </a> </root>
and a or b are detached, then these elements would have the default namespace “namespace”.
If the elements are:
<pre:element xmlns:pre=”something”> <pre:a> <b> </b> </pre:a> </pre:element>
and the a element gets detached, it will contain its prefix (“pre”). If b gets detached, it will not have a namespace.
CSenElement
checks the namespace declarations
of the child and its new parent. If the child does not have a default namespace
but the new parent has, the method applies (maps) the parent’s default namespace
to apply to this child.
Secondly, if the child has some other namespace declarations (which are found from the parent), the method removes its own (local allocation) and points to the parent’s namespace object instead.
The behaviour explained above is supposed to be done using a parent. For example, if someone calls the parent's AddElementL() giving a child as an argument, then the child element is added to the parent and SetParent() is also called as in the base element implementation:
CSenElement& CSenBaseElement::AddElementL(CSenElement& aElement) { User::LeaveIfError(iElements.Append(&aElement)); aElement.SetParent(this); return aElement; }
The SetParent call also prevents multiple declarations of the same namespace.
Figure 15: Using CSenDomFragment class
The following example describes how the client application constructs CSenDomFragment
and
parses an XML document. Then the contents of the elements are printed to the
console. The handling of the DOM tree is done in the ListNodesL() function.
ListNodesL() receives an array of elements, the console object and the current
level in the tree. ListNodesL() calls itself recursively for any element in
the array which has children. The parsing and calling ListNodesL() is done
in the TestBaseXmlParsing() function.
TInt TestBaseXmlParsing(CConsoleBase &aConsole) { TInt retVal(KErrNone); _LIT(KText, "\n\n\n\n\n------------------\nTesting\nCSenDomFragment\n"); _LIT(KPressKey, " PRESS ANY KEY...\n"); aConsole.Printf(KText); aConsole.Printf(KPressKey); aConsole.Getch(); // create new XML reader and push it to the cleanupstack CSenXmlReader* reader = CSenXmlReader::NewL(); CleanupStack::PushL(reader); //create a CSenDomFragment CSenDomFragment* pBase = CSenDomFragment::NewL(); CleanupStack::PushL(pBase); //the XML document to be parsed _LIT(KToBeParsed, "c:\\input.xml"); //open a filesession RFs fss; User::LeaveIfError(fss.Connect()); CleanupClosePushL(fss); //must set the content handler ´ reader->SetContentHandler(pBase); // and the reader pBase->SetReader(*reader); //do the parsing reader->ParseL(fss, KToBeParsed); //after the parsing, get the elements: RPointerArray<CSenElement>& array = pBase->AsElement().ElementsL(); //start the listing of the elements, first level is 1 ListNodesL(array, aConsole, 1); CleanupStack::PopAndDestroy(3); // pBase, fss, reader return retVal; } // The listing of the elements is done in ListNodesL function: void ListNodesL(RPointerArray<CSenElement>& aArray, CConsoleBase &aConsole, int aLevel) { _LIT(KNewLine, "\n"); _LIT(KNoContent,"NO CONTENT"); _LIT(KDepthFormatString, "lev%d: "); TInt size = aArray.Count(); for(TInt i=0; i<size; i++) { aConsole.Printf(KNewLine); //get an element CSenElement* pElement = aArray[i]; TBool hasActualContent = EFalse; if( pElement->HasContent() ) { //get the content of the element TPtrC8 content = pElement->Content(); //to a HBufC, notice "LC" HBufC *pContent = SenXmlUtils::ToUnicodeLC(content); //print current depth (“level”) aConsole.Printf(KDepthFormatString, aLevel); //trim possible whitespace characters pContent->Des().Trim(); //if there is content after the trimming if( pContent->Length() > 0) { hasActualContent = ETrue; aConsole.Printf(*pContent); } //destroy pContent CleanupStack::PopAndDestroy(pContent); } if( !hasActualContent) { aConsole.Printf(KNoContent); } //get the first child CSenElement* child = pElement->Child(0); //check if element has childs if( child ) { //get the child elements RPointerArray<CSenElement>& tree = pElement->ElementsL(); //list child elements by a recursive call ListNodesL(tree, aConsole, ++aLevel); } aLevel--; } //for loop ends }
By default, CSenDomFragment
instantiates a tree of new
element objects representing the parsed XML document. This is resource-consuming
and not a reasonable approach to most of the XML parsing objectives. Instead,
one should use Base fragments and concentrate on emergent sections of such
XML.
For example, if the document to be parsed is the following:
<?xml version="1.0"?> <root> <level1> Level1 value <level2 attrib="value"> Level2 value <level3>Level3 value</level3> </level2> <level2> Level2 value2 <level3> Level3 value <level4>Level4 value</level4> </level3> </level2> <level2>Level2 value3</level2> </level1> </root>
The output of the example when run in the emulator is shown in the Example run in the emulator figure below.
Figure 16: Example run in the emulator
CSenSoapMessage
inherits CSenSoapEnvelope
.
The only thing CSenSoapMessage
adds is the use of the SOAP
Security header. It has been implemented in CSenWsSecurityHeader
,
and an instance of it is as a member of CSenSoapMessage
.
The example below demonstrates the use of CSenWsSecurityHeader
by
finding out if there is a Security header.
A service consumer might use CSenSoapEnvelope
in parsing
the responses from the service. The consumer might not receive the whole Envelope
but just the Body (this can be set by calling CSenServiceConnection
).
In the example below, this has been taken into consideration.
One special case is the SOAP Fault. In the example below, a check is performed to see if the Envelope has a Fault element.
Figure 17: Using CSenSoapMessage class
In the TestSoapMessage function, CSenSoapMessage
is
constructed with some test data (a KSOAPMessage literal). After the parsing,
a check is made to see if a whole Envelope was made. In this case, the ProcessReceivedCompleteSoapMsgL()
function gets called. Otherwise, if there is only the Body, the ProcessReceivedSoapMsgBodyL()
function gets called.
void TestSoapMessage(CConsoleBase& aConsole) { //To test Envelope with a security header _LIT8( KSOAPMessage, "<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\"><Header><wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><UsernameToken><Username>name</Username></UsernameToken></wsse:Security></Header><Body ><TestRequest xmlns=\"Just:checking:and:testing\"><Test>testing</Test></TestRequest></Body></Envelope>" ); //first the message is parsed CSenXmlReader *pXmlReader = CSenXmlReader::NewL(); CleanupStack::PushL(pXmlReader); CSenSoapMessage* pMessage = CSenSoapMessage::NewL(); CleanupStack::PushL(pMessage); pMessage->SetReader(*pXmlReader); TRAPD(leaveCode, pMessage->ParseL(KSOAPMessage)); if(leaveCode!=KErrNone) { CleanupStack::PopAndDestroy(2); //pMessage, pXmlReader return; } //if there is a complete envelope then there are actual elements TInt elements = pMessage->AsElement().ElementsL().Count(); if( elements > 0 ) { aConsole.Printf(_L("Got complete SOAP envelope\n")); ProcessReceivedCompleteSoapMsgL(*pMessage, aConsole); } else //if not, then we have a detached SOAP body { ProcessReceivedSoapMsgBodyL(KSOAPMessage, aConsole, *pXmlReader); } CleanupStack::PopAndDestroy(2); //pMessage, pXmlReader } // The processing of the whole element starts by checking, if a security header // was received. Next check reveals, whether the message is a SOAP Fault, which // could be extracted from the SOAP Body. void ProcessReceivedCompleteSoapMsgL(CSenSoapMessage& aMessage, CConsoleBase &aConsole) { //There’s no other way to check for security header CSenElement& headers = aMessage.HeaderL(); //header element ProcessSecurityHeaderFromHeader(headers, aConsole); //First check if there is a Fault if(aMessage.IsFault()) { //includes SOAPFault // note: by calling aMessage.FaultL() the ownership would NOT be taken // CSenSoapFault* pFault = aMessage.FaultL(); //By calling DetachFaultL() the ownership IS taken: CSenSoapFault* pFault = aMessage.DetachFaultL(); CleanupStack::PushL(pFault); if(pFault) { //Process the SOAP Fault HBufC8* pFaultAsXml = pFault->AsXmlL(); CleanupStack::PushL(pFaultAsXml); pFaultAsXml->Length() ); TPtrC8 faultCode = pFault->FaultCode(); TPtrC8 faultString = pFault->FaultString(); TPtrC8 faultActor = pFault->FaultActor(); TPtrC8 detail = pFault->Detail(); //Do something with values of this SOAP Fault CleanupStack::PopAndDestroy(); // pFaultAsXml } CleanupStack::PopAndDestroy();//pFault } //Next process the SOAP Body MSenElement *pBody = aMessage.BodyL().AsElement(); HBufC* pUnicode = pBody->AsXmlUnicodeL(); //Do something with the Body delete pUnicode; }
Intelligent consumer applications could apply a similar logic in their HandleErrorL()
implementation.
If the completeServerMessages mode is off, only SOAP message body or SOAP
fault is received. Typically, applications only need to implement handling
for either of the modes, but both the options are processed in the example
above.
The processing of the Security header is done in ProcessSecurityHeaderFromHeader. CSenSoapMessage
does
not provide an easy way to get the Security header, so in the example below,
it is searched using the Security element’s namespace and name. Then, CSenWsSecurityHeader
is
constructed and the data is copied. Finally, the “username” token from the
Security element is created for the username “dummy”.
void ProcessSecurityHeaderFromHeader(CSenElement& headers, CConsoleBase &aConsole) { _LIT8(KSecurityName, "Security"); _LIT8(KSecurityXmlNs, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); _LIT8(KSecurityXmlNsPrefix, "wsse"); //Search for the Security element //Ownership of pSec is not transferred. NULL is equal to “not found”. CSenElement* pSec = headers.Element(KSecurityXmlNs, KSecurityName); if( pSec ) { CSenWsSecurityHeader *pSecH = CSenWsSecurityHeader::NewL(); CleanupStack::PushL(pSecH); //Copy the data pSecH->AsElement().CopyFromL(*pSec); HBufC8* pToken = NULL; CleanupStack::PushL(pToken); //Get the token for the given name pSecH->UsernameTokenL(_L8("dummy"), pToken); //Now the token would be processed. CleanupStack::PopAndDestroy(); //pToken CleanupStack::PopAndDestroy(); //pSecH } }
If only the Body of a SOAP message is processed, the ProcessReceivedMessageBodyL
function is executed. There the Body is parsed using CSenDomFragment
.
Then a check for a SOAP Fault is done.
void ProcessReceivedMessageBodyL(const TDesC8& aResponse, CConsoleBase &aConsole,CSenXmlReader& aXmlReader ) { //Parse the body CSenDomFragment* pDom = CSenDomFragment::NewL(); CleanupStack::PushL(pDom); pDom->SetReader(aXmlReader); TRAPD(error,pDom->ParseL(aResponse) ); if(error!=KErrNone) { CleanupStack::PopAndDestroy(); //pDom return; } _LIT8(KFaultName,"Fault" ); _LIT8(KQName, "soapenv:Fault"); //Check if there is a Fault //Note: ownership not transfered CSenElement *pFault = pDom->AsElement().Element(KFaultName); if( pFault ) //There is a fault { CSenSoapFault* pFault2 = CSenSoapFault::NewL( *pFault ); CleanupStack::PushL(pFault2); if(pFault2) { HBufC8* pFaultAsXml = pFault2->AsXmlL(); CleanupStack::PushL(pFaultAsXml); TPtrC8 faultCode = pFault2->FaultCode(); TPtrC8 faultString = pFault2->FaultString(); TPtrC8 faultActor = pFault2->FaultActor(); TPtrC8 detail = pFault2->Detail(); //Do something with the values of this SOAP Fault CleanupStack::PopAndDestroy(); // pFaultAsXml } CleanupStack::PopAndDestroy(); // pFault2 } CleanupStack::PopAndDestroy(); //pDom //Now SOAP Body could be processed. }
Closing a connection is done simply by deleting the connection object.
Some of the following possible error situations should be taken into consideration:
Cannot establish a connection.
A connection has been established, but the request fails.
Basic authentication fails in Basic Web Services.
Authentication and discovery errors in case of ID-WSF.
Expiration of an ID-WSF connection.
The Web service provider responds with SOAP Fault.
The consumer is notified of errors by calling the HandleErrorL()
function.
One thing to remember to do is the registration of the service and identity related data via Service Manager.
Using the XML Extensions library for parsing may cause a lot of overhead depending on the size of the XML document.
The Web Services API does not explicitly support any kind of extensions to it.