8 Service Consumption
8.4 Toolkit for iOS (GWPA)
8.4.3 Using the Proxy in your iOS Application
This section provides code snippets showing how to use the generated proxy in your application. The examples show proxy classes generated from the RMTSAMPLEFLIGHT service.
For further information, you can use the comments included in the Framework and generated proxy header files.
8.4.3.1 Importing the Service Proxy Classes
Explains details of what to consider when importing service proxy classes.
The service proxy files include the service proxy object and classes representing all the service entities and complex types.
To support proxies of multiple service versions, all the service classes include a suffix with the service technical version (if the version is unknown, V0 is used as a suffix). Nevertheless, you can keep the application code independent of a specific service version where it is not necessary. In this case, use the declerations header file generated with the service proxy, including the aliases for all the service class names.
It is recommended to import the service declarations header file and use the class name aliases without specifying a specific version to use the service proxy classes.
#import "RMTSAMPLEFLIGHTServiceDeclarations.h"
Note
If a version specific code is required, you must use the exact class name (including the version suffix) and import the service proxy file of the required version.
#import "RMTSAMPLEFLIGHTServiceV1.h"
8.4.3.2 Initiating the Service Proxy Object
Explains how to initiate the service proxy object.
The service class contains methods that allow you to obtain collections and specific items within these collections for all the entity sets in the service. The service class also contains all the function import methods that the service exposes.
1. To initiate the service proxy object, use the following command: RMTSAMPLEFLIGHTService *service = [[RMTSAMPLEFLIGHTService alloc] init];. The service init method expects the service document and metadata XMLs to be present in the </Services/Resources> folder.
2. There is also an option to retrieve the service document and metadata NSData during the runtime of the application and use them to initiate the service proxy object using the following command:
RMTSAMPLEFLIGHTService *service = [[RMTSAMPLEFLIGHTService alloc]
initWithServiceDocument:serviceDocResponse andMetadata:serviceMetadataResponse];.
3. The service URL used for calculating the service queries is taken from the service document data used to initiate the service proxy object. You can set this URL using the service's setServiceDocumentUrl:
(NSString *)baseUrl method. You must verify the service proxy object was initiated successfully before using it. if (service) { // work with the semantic proxy as needed }
Note
You must iniate the service proxy object in your application before initiating an entity class or using the entity class methods.
8.4.3.3 Executing Requests Using the Connectivity Helper Class and Authenticating Protocol
Explains how to use the connectivity helper class and authenticating protocol to execute requests.
The SDMConnectivityHelper is a helper class for creating and executing HTTP requests using the SAP OData Mobile Client SDK connectivity library.
1. To iniate the SDMConnectivityHelper object, use the following command: SDMConnectivityHelper
*connectivityHelper = [[SDMConnectivityHelper alloc] init];
2. To add user authentication information to send requests:
The SDMConnectivityHelper helper class does not add any user authentication information to the requests sent using its methods. To get a reference to the HTTP request object before sending it to the server, and to set the user credentials in the request object, you can implement the
SDMConnectivityHelperDelegate protocol and register as the helper class delegate.
- (void)onBeforeSend:(id<SDMRequesting>)request { request.username = @"<user>";
request.password = @"<password>";
}connectivityHelper.delegate = self;
Another option for performing user authentication, is to use the Authenticating protocol. The protocol provides a method for sending a synchronous HTTP GET request with the user authentication information provided when the class implementing the protocol is initiated.
The generated proxy provides the following Authenticating protocol implementations:
○ UsernamePasswordAuthenticator for Basic and Integrated/NTLM user authentication (with the SAP NetWeaver Gateway server, or with the Identity Provider server when using SAML2 protocol), which requires user name, password, and domain (the latter is optional).
It is also used for SUP authentication, passing the user name and password according to the specified authentication protocol (X.509, Portal SSO, and Basic) in the SUP server. For example, for the X.509 authentication protocol, the user name is the subject of the client certificate and the password is the base64 encoding of the certificate.
○ PortalAuthenticator for user authentication using SAP Portal (and forwarding the received SAP Logon Ticket).
○ CertificateAuthenticator for user authentication using the X.509 client certificate.
Once the request is successfully sent using the Authenticating object and a session cookie is returned from the server, the next requests do not require user authentication information. It is recommeded to configure the SAP NetWeaver Gateway to support this behavior.
Note
This is not relevant if you are using certificate authentication.
Therefore, it is recommended to perform the authenticated request using the Authenticating object one time in the user login process of the application, and then use the SDMConnectivityHelper object without adding the user credentials for each of the following requests using the SDMConnectivityHelperDelegate protocol implementation.
Moreover, the supplied UsernamePasswordAuthenticator and PortalAuthenticator implementations save the received user credentials on the device, and uses the saved credentials if no user credentials are provided when the authenticator object is initiated. If the authentication fails, the authenticator deletes the saved credentials. This behavior can be used for implementing Single Sign-On (SSO) in your application.
Note
You can check if there are user credentials already saved on the device using the isCredentialsSaved method of the KeychainHelper class.
An example of using the UsernamePasswordAuthenticator object for user authentication:
id<Authenticating> authenticator;
if ([KeychainHelper isCredentialsSaved]) {
authenticator = [[UsernamePasswordAuthenticator alloc] init]; // Use the saved credentials
} else {
authenticator = [[UsernamePasswordAuthenticator alloc]
initWithUsername:@"domain\user"
andPassword:@"password"];
}
RMTSAMPLEFLIGHTService *service = [[RMTSAMPLEFLIGHTService alloc] init];
// Execute the first request using the authenticator class for user authentication
NSData* serviceDocData = [authenticator
authenticateWithODataQuery:service.ServiceDocumentQuery error:nil];
if (serviceDocData) {
NSLog(@"User authenticated successfully");
}
When using the certificate authentication method, the certificate must be sent on each consecutive call.
Therefore, when the application loads, it reads the certificate file from the file bundle named
"client_certificate.pfx", adds it to the Keychain if it doesn't exist, and later on loads in from the keychain and sends it in each request.
Related Links
http://help.sap.com/saphelp_gateway20sp06/helpdata/en/89/ea6a0543dc4e13b20b3462f57d7404/
frameset.htm
8.4.3.4 Getting a Collection
Explains how to retrieve a collection query.
To get a collection, proceed as follows:
1. Get the collection query from the service object’s <Collection Name>Query property.
2. Execute an HTTP GET request to get the FlightCollection. For example, use the
executeBasicSyncRequestWithQuery:(ODataQuery *)aQuery method for executing a simple synchronous request. id<SDMRequesting> request = [connectivityHelper
executeBasicSyncRequestWithQuery: service.FlightCollectionQuery];
3. Get the response data and pass it to the service’s getFlightCollectionWithData:(NSData *)aData error:(NSError **)error method. NSData *itemsResponseData = request.responseData;
NSError* error = nil; NSMutableArray *flights = [service getFlightCollectionWithData:itemsResponseData error:&error];
The method returns an array of Flight objects.
if (!error) { // work with the Flight semantic object as expected for (Flight *flight in flights) { NSLog(@"carrid = %@",flight.carrid); NSLog(@"connid =
%@",flight.connid); NSLog(@"airportFrom = %@",flight.flightDetails.airportFrom); } }
8.4.3.5 Service Query Parameters
Introduces concept of service query parameters.
You use the ODataQuery object to add OData or custom query parameters to your query.
ODataQuery *query = service.FlightCollectionQuery;
[query addParameterWithKey:@"$top" andValue:@"10"];
The ODataQuery object also contains dedicated method for the $expand, $filter, $orderby and $select query parameters:
● (void)expand:(NSString *)expandStringValue;
● (void)filter:(NSString *)filterStringValue;
● (void)orderBy:(NSString *)orderByStringValue;
● (void)select:(NSString *)selectStringValue;
8.4.3.6 Getting the Collection Entry Using an Existing Entity Object
Explains one of two possible scenarios for getting a specific collection entry. This scenario explains how to use an existing entity object to get the collection.
In this scenario, you already have the entry object (for example, after performing a call to get a collection entries), but you need to perform another service call for this specific entry to get the values for all entity properties (or all navigation properties using expand). For example, when this entry was selected in the UI and should be displayed in the next application view.
The additional call is required because, to reduce the response payload volume, some services do not return values for all entity properties when the entry is part of a collection response.
To get the entry, proceed as follows:
1. Create the query for the desired Carrier entity using the baseUrl property of the entity object.ODataQuery
* carrierQuery = [[ODataQuery alloc] initWithURL:aCarrier.baseUrl];
2. Execute an HTTP GET request to get the entry data. For example, use the
executeBasicSyncRequestWithQuery:(ODataQuery *)aQuery method for executing a simple
synchronous request. id<SDMRequesting> request = [connectivityHelper executeBasicSyncRequestWithQuery:carrierQuery];
3. Get the response data and pass it to the service’s getCarrierCollectionEntryWithData:(NSData
*)aData error:(NSError **)error method. NSData *itemResponseData =
request.responseData; NSError* error = nil; Carrier *carrier = [service getCarrierCollectionEntryWithData:itemResponseData error:&error];
The method returns a Carrier object. (Make sure you use only this new updated entity object to represent this Carrier entity in your application).
if (!error) {
// work with the Carrier semantic object as expected NSLog(@"CARRNAME = %@", carrier.CARRNAME);
NSLog(@"URL = %@", carrier.URL);
}
Note
There is also an option to parse a Carrier entry data into a Carrier object using the
parseCarrierEntryWithData:(NSData *)aData error:(NSError **)error method of the Carrier class.
Carrier *carrier = [Carrier parseCarrierEntryWithData:itemResponseData error:&error];
To use entity class's parse<Entity Name>EntryWithData:(NSData *)aData error:(NSError
**)error or parse<Entity Name>EntriesWithData:(NSData *)aData error:(NSError **)error methods, you must first initiate the service object or call the loadEntitySchema:
(SDMODataServiceDocument *)aService class method.
8.4.3.7 Getting the Collection Entry Using Values for Key Properties
Explains one of two possible scenarios for getting a specific collection entry. This scenario explains how to use values for key properties to get the collection.
In this scenario, you do not have the entry object from a previous service call. Instead, you have the values of the key properties for the desired entity (for example, as input parameters from the UI).
To get the entry, proceed as follows:
Get the query for the desired CarrierCollection entry from the service proxy object:
○ Using typed parameters holding the values for the key properties.
ODataQuery *carrierQuery = [service
getCarrierCollectionEntryQueryTypedWithCarrid:@"AA"];
Pass only the values of the key properties and the method takes care of converting them to the appropriate URI format.
Note
This option is available only for OData-compliant services.
○ Using generic parameters holding the values for the key properties as they will appear in the query URL.
ODataQuery *carrierQuery = [service
getCarrierCollectionEntryQueryWithCarrid:@"'AA'"];
You must provide the values for the key properties to the service method above, exactly as they will appear in the query URL, in the correct format, according to the property types (as defined in the OData protocol: Abstract Type System
For example: the carrid key property of the Carrier entity is of type Edm.String, and therefore its value should be provided within single quotation marks, for example: 'AA'.
Related Links
Getting the Collection Entry Using an Existing Entity Object [page 175]
Explains one of two possible scenarios for getting a specific collection entry. This scenario explains how to use an existing entity object to get the collection.
8.4.3.8 Performing a Navigation Query
Explains how to perform a navigation query.
To perform a navigation query, proceed as follows:
1. Get the navigation query from the entity object’s <Navigation Name>Query property.
2. Execute an HTTP request using the query and get the response data.id<SDMRequesting> request = [connectivityHelper executeBasicSyncRequestWithQuery: aFlight.FlightCarrierQuery];
3. Use the entity object’s load<Navigation Name>WithData:(NSData *)aData error:(NSError
**)error method to load the response data into the entity object.NSData *carrierItemResponseData
= request.responseData; NSError* error = nil; [aFlight
loadFlightCarrierWithData:carrierItemResponseData error:&error]; Carrier
*firstCarrier = [aFlight.FlightCarrier objectAtIndex:0];
This feature is available only for entity objects retreived from the server that were not initiated using the basic init method (with no parameters).
8.4.3.9 Performing an Expand Query
Explains how to perform an expand query ($expand).
To perform an $expand query, proceed as follows:
1. Use the expand: method in the ODataQuery object to set the expand parameters.ODataQuery *query = [[ODataQuery alloc] initWithURL:aCarrier.baseUrl]; [query
expand:@"carrierFlights"];
2. Execute an HTTP request using the above query. id<SDMRequesting> request = [connectivityHelper executeBasicSyncRequestWithQuery:query];
3. Use the parseExpanded<Entity name>EntryWithData: method to parse the response data. Carrier
*item = [Carrier parseExpandedCarrierEntryWithData:request.responseData
andServiceDocument:service.sdmServiceDocument error:&error]; Flight *aflight = (Flight*)[item.carrierFlights objectAtIndex:0];
8.4.3.10 SAP Labels
Each entity or complex type class has a class method to get the SAP labels of the object’s properties.
You must first initiate the service object or call the loadLabels:(SDMODataServiceDocument *)aService class method.
RMTSAMPLEFLIGHTService *service = [[RMTSAMPLEFLIGHTService alloc] init];
NSLog(@"Flight Label for carrid = %@",[Flight getLabelForProperty:@"carrid"]);
NSLog(@"FlightDetails (complex type) Label for airportFrom = %@",[FlightDetails getLabelForProperty:@"airportFrom"]);
8.4.3.11 Performing Service Operations
This section describes how to perform a service operation (Function Import):
1. Get the service operation query from the service proxy object.
There are two possible options for getting the query:
○ Using typed objects holding the values for the service operation parameters.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
Pass only the values of the service operation parameters and the method takes care of converting them to the appropriate URI format.
Note
This option is available only for OData-compliant services.
○ Using a generic dictionary holding the service operation parameter names and values as NSString objects, as they will appear in the service operation query URL.
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
You must provide the values for the operation parameters in a dictionary exactly as they will appear in the query URL, in the correct format, according to the parameter types (as defined in the OData protocol).
For example, the cityTo parameter of the GetAvailableFlights service operation is of type Edm.String, and therefore its value should be provided within single quotation marks, for example: 'SAN FRANCISCO'.
2. Execute an HTTP request using the query and get the response data. For example, use the
executeBasicSyncRequestWithQuery:(ODataQuery *)aQuery method for executing a simple synchronous request.
id<SDMRequesting> request = [connectivityHelper executeBasicSyncRequestWithQuery:
functionQuery];
Note
The request must be executed using the HTTP method specified for this service operation in the service metadata (see also the code documentation of the appropriate service method used for getting the query in the previous step).
If the service operation is executed using an HTTP method other than GET (for example, POST or PUT methods), an X-CSRF token must be attached to the request. For more information, see the Cross Site Request Forgery (CSRF) Protection section.
3. Use the service proxy object’s get<Function Import Name>ResultWithData:(NSData *)aData error:(NSError **)error method to parse the response data into the appropriate object (according to the operation result type).
// work with the Flight semantic object as expected for (Flight *flight in availableFlights) {
NSLog(@"carrid = %@",flight.carrid); Cross Site Request Forgery (CSRF) Protection [page 185]
Most OData compliant SAP services are protected against Cross-Site Request Forgery (CSRF) attacks. This protection mechanism appends challenge tokens to each request and associates them with each user’s session.
Each token is unique per user session.
8.4.3.12 Performing Entity CUD Operations
All CUD calls must include an X-CSRF token in the request. To do this, you can use the SDMConnectivityHelper class and its getCSRFData and AddCSRFDataToRequest helper methods.
8.4.3.12.1 Performing Create or Update Operations
To perform Create or Update:
1. Initiate a new entity item instance and set its properties (or update the values of an existing item).
Before initiating a new entity item instance you must initiate the service object or call the loadEntitySchema:(SDMODataServiceDocument *)aService entity's class method.
Note
You can also set the entity item’s navigation properties to perform a deep insert operation.
2. Get the item XML representation using the service object’s getXMLForCreateRequest or getXMLForUpdateRequest methods.
3. For Update, get the Etag parameter from the item. The Etag parameter represents the time interval of the item modification.
4. Use the required SDMConnectivityHelper method: executeCreate[Sync/Async]RequestWithQuery, or executeUpdate[Sync/Async]RequestWithQuery.
5. Use the entity class' parse<Entity Name>EntryWithData:(NSData *)aData error:(NSError
**)error method to parse the response data into a new updated entity object.
Booking *newBooking = [[Booking alloc] init];
newBooking.carrid = @"<carrier_ID>";
newBooking.PASSNAME = @"<passenger_name>";
…Flight *associatedFlight = [[Flight alloc] init];
associatedFlight.connid = @"<flight_number>";
associatedFlight.carrid = @"<carrier_ID>";
…newBooking.bookedFlight = [NSMutableArray arrayWithObject:associatedFlight];
NSError *error = nil;
NSString *xml = [service getXMLForCreateRequest:newBooking error:&error];
CSRFData *csrf = [connectivityHelper
newBooking = [Booking parseBookingEntryWithData:request.responseData error:&error];
}
To create a new item in a collection to which you can navigate using the navigation property of the parent item, use the parent navigation property query as the request query (instead of the collection query used in the Create request), as follows:
NSError *error = nil;
NSString *xml = [service getXMLForCreateRequest:newBooking error:&error];
CSRFData *csrf = [connectivityHelper
getCSRFDataForServiceQuery:service.serviceDocumentQuery];
if (csrf) {
id<SDMRequesting> request = [connectivityHelper executeCreateAsyncRequestWithQuery:
aFlight.flightBookingsQuery andBody:xml andCSRFData:csrfData:csrf];
newBooking = [Booking parseBookingEntryWithData:request.responseData
error:&error];
}
Note
For an Update request, use the entry URL as the request query (instead of the collection query used in the Create request).
NSError *error = nil;
NSString *xml = [service getXMLForUpdateRequest:booking error:&error];
CSRFData *csrf = [connectivityHelper
getCSRFDataForServiceQuery:service.serviceDocumentQuery];
if (csrf) {
ODataQuery *query = [[ODataQuery alloc] initWithURL:booking.baseUrl];
id<SDMRequesting> request = [connectivityHelper executeUpdateSyncRequestWithQuery:
query andBody:xml andCSRFData:csrf andEtag: booking.etag];
booking = [Booking parseBookingEntryWithData:request.responseData error:&error];
} Related Links
Cross Site Request Forgery (CSRF) Protection [page 185]
Most OData compliant SAP services are protected against Cross-Site Request Forgery (CSRF) attacks. This protection mechanism appends challenge tokens to each request and associates them with each user’s session.
Each token is unique per user session.
8.4.3.12.2 Performing a Delete Operation
To perform Delete:
1. Get the baseUrl property value from the specific entity item instance you want to delete.
2. Initiate a new ODataQuery object.
3. Get the Etag parameter from the item. The Etag parameter represents the time interval of the item modification
4. Use the SDMConnectivityHelper’s executeDelete[Sync/Async]RequestWithQuery method.
ODataQuery *query = [[ODataQuery alloc] initWithURL:flight.baseUrl];
[connectivityHelper executeDeleteSyncRequestWithQuery:query andCSRFData:csrf andEtag: flight.etag];
8.4.3.13 Media Link Queries
Entities that support media links have two additional properties in their entity class:
● mediaLinkRead – Represents the information needed for reading the media link associated with the entity object.
● mediaLinkEdit – Represents the information needed for editing (updating or deleting) the media link associated with the entity object.
These properties of type MediaLink, include the query and the content type needed for reading or editing the media resource of the entity.
Note
This feature is available only for entity objects retreived from the server that were not initiated using the basic init method (with no parameters).
The MediaLink class can be also used for providing the required information for creating a media link associated with an existing entry.
The SDMConnectivityHelper class provides methods for performing all the media link CRUD operations (Read, Create, Update and Delete), synchronously or asynchronously, using the MediaLink objects.
Note
All CUD calls must include an X-CSRF token in the request. To do this, you can use the
SDMConnectivityHelper class and its getCSRFData and AddCSRFDataToRequest helper methods.
8.4.3.13.1 Performing a Read Media Link Query
To perform a Read Media Link query:
1. Get the entity object associated with the media link. For more information, see Getting the Collection Using an Existing Entity Object.
2. Get the Read media link query from the entity object’s mediaLinkRead property.
3. Execute an HTTP request using the query and get the response data.
id<SDMRequesting> request = [connectivityHelper executeBasicSyncRequestWithQuery:
aCarrier.mediaLinkRead.mediaLinkQuery];
4. Use the response data to display the media resource, according to the media link content type.
if ([aCarrier.mediaLinkRead.contentType isEqualToString:@"image/jpeg"]) { UIImage *image = [UIImage imageWithData: request.responseData];
mediaLinkImgView.image = image; //Display the image using a UIImageView control }
Related Links
Cross Site Request Forgery (CSRF) Protection [page 185]
Most OData compliant SAP services are protected against Cross-Site Request Forgery (CSRF) attacks. This protection mechanism appends challenge tokens to each request and associates them with each user’s session.
Each token is unique per user session.
Getting the Collection Entry Using an Existing Entity Object [page 175]
Getting the Collection Entry Using an Existing Entity Object [page 175]