Contributed By : Chethan Kumar SN (chethankumar.sn@in.ibm.com) and Vittal Pai (vittalpai@in.ibm.com)
With the release of MobileFirst Platform v7.1, one can now migrate any existing iOS app built for MobileServices on Bluemix to MobileFirst Platform with just a handful of simple steps.
To elucidate the process, lets look at how to migrate a simple Bluemix iOS app.
On the client side, the application stores a list of items and provides a way to add more items to the list. Each item can able to store Name, Store, Price and image of the product. The App's are protected by Custom Authenticator via AMA security service provided by bluemix.
On the server side, the App contains a JAX-RS class to store and manipulate the data. It also contains the server side AMA security implementation.
On BlueMix we have application with the following configuration:
Liberty Runtime : which used to run JAX-RS application on Bluemix
Advance Mobile Access service : which gives mobile application security and monitoring functionality
Push Service for iOS 8 : which provides the capability to use iOS Push features
Liberty Runtime
Liberty contains two projects with JAX-RS service (i.e Custom-oauth-java for Custom Authentication and LocalstoreAdapter for storing items). The service include the protected resource and the custom identity provider code. The liberty server is configured with TAI.
Trust Association Interface (TAI) is a service provider API that enables the integration of third-party security services with a Liberty profile server. For more info on TAI : click here
The custom identity provider authenticates a user by sending challenges to the client. However, custom identity providers do not communicate directly with clients. They send challenges and receive responses to the challenges by means of the Advanced Mobile Access service. When a custom identity provider successfully authenticates the user, it provides the user identity information to Advanced Mobile Access. For more information on custom authentication refer bluemix documentation : click here
The custom identity provider code is defined by two http API: /startAutorization and /handleChallengeAnswer
@GET@Path("/getAllItems")publicStringgetAllItems()throwsIOException{init();JsonArrayjsonArray=newJsonArray();for(Objectkey:props.keySet()){jsonArray.add(parser.parse(props.getProperty((String)key)).getAsJsonObject());}returnjsonArray.toString();}@PUT@Path("/addItem")publicvoidaddItem(StringitemJson)throwsIOException,URISyntaxException{try{init();intnewKey=props.keySet().size()+1;props.put(String.valueOf(newKey),itemJson);URLurl=this.getClass().getClassLoader().getResource("data.properties");Filefile=newFile(url.toURI().getPath());FileOutputStreamfoStream=newFileOutputStream(file);props.store(foStream,"saving new item");foStream.close();}catch(IOExceptionioe){ioe.printStackTrace();}}@POST@Path("/addAllItems")publicStringaddAllItems(StringitemsJson)throwsURISyntaxException,IOException{try{init();clearAllData();JsonArrayjsonArr=parser.parse(itemsJson).getAsJsonArray();for(inti=0;i<jsonArr.size();i++){props.put(String.valueOf(i+1),jsonArr.get(i).toString());}URLurl=this.getClass().getClassLoader().getResource(&"data.properties");Filefile=newFile(url.toURI().getPath());FileOutputStreamfoStream=newFileOutputStream(file);props.store(foStream,"saving new item");foStream.close();return"true";}catch(IOExceptionioe){ioe.printStackTrace();}return"false";}@DELETE@Path("/clearAll")publicStringclearAllData()throwsMissingConfigurationOptionException,URISyntaxException,IOException{init();props.clear();System.out.println("Size : "+props.size());URLurl=this.getClass().getClassLoader().getResource("data.properties");Filefile=newFile(url.toURI().getPath());FileOutputStreamfoStream=newFileOutputStream(file);props.store(foStream,"clearing all data");foStream.close();return"cleared";}
Add TAI Extension in the following path of server directory server/usr/extensions
TAI Extension Link : Download the extension.zip from here
Add TAI Security constraint in web.xml file for both the projects.
Bind the pushed application to Advance Mobile Access Service.
Register your client application in AMA dashboard. For more info refer documentation : click here
AMA provides Facebook, Google, or a custom identity provider to authenticate access to protected resources. Add Custom identity provider feature as it can be migrated to MFPF and specify the corresponding jax-rs custom authentication application url and realm name.
Add the following code inside didFinishLaunchingWithOptions function in Appdelegate of client application which will register the realm and initialize connection with Bluemix Application.
Configure Apple Push Notification service (APNs) which requires Apple Developer Account and Generate pl2 certificates. Documentation link : click here
Upload the generated pl2 certificate in Push service dashboard
Add the following code inside didFinishLaunchingWithOptions function in Appdelegate of client application which will register notifications in client app.
Add the following code inside didRegisterForRemoteNotificationsWithDeviceToken function in Appdelegate of client application which will register pushclient and subscribe to tag in client app.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IMFPushClient.sharedInstance().registerDeviceToken(deviceToken,completionHandler:{(response,error)->Voidiniferror!=nil{println("Error during device registration \(error.description)")}else{println("Response during device registration json: \(response.responseJson.description)")vartags=["parkstore"]IMFPushClient.sharedInstance().subscribeToTags(tags,completionHandler:{(response:IMFResponse!,err:NSError!)->Voidiniferr!=nil{println("There was an error while subscribing to tag")}else{println("Successfully subscribe to tag parkstore")}})}
Add the following function inside Appdelegate which triggers when push notification arrived in client app.
1
2
3
4
5
6
7
8
9
10
funcapplication(application:UIApplication,didReceiveRemoteNotificationuserInfo:[NSObject:AnyObject]){println("Got remote Notification. Data : \(userInfo.description)")letinfo=userInfoasNSDictionaryletdata=info.objectForKey("aps")?.objectForKey("alert")as!NSDictionaryletuserData=data.objectForKey("body")as!StringletalertView=UIAlertView(title:"WishList!",message:"\(userData)",delegate:nil,cancelButtonTitle:"OK")alertView.show()}}
Existing Bluemix Client Application
Add the following Code snippets to the existing Bluemix Client Application and name the application with same name which you have registered in Advance Mobile Access Dashboard.
Add the following code inside didFinishLaunchingWithOptions function in Appdelegate of client application which will register the realm and initialize connection with Bluemix Application.
Add the following code inside didFinishLaunchingWithOptions function in Appdelegate of client application which will register notifications in client app.
Add the following code inside didRegisterForRemoteNotificationsWithDeviceToken function in Appdelegate of client application which will register pushclient and subscribe to tag in client app.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
IMFPushClient.sharedInstance().registerDeviceToken(deviceToken,completionHandler:{(response,error)->Voidiniferror!=nil{println("Error during device registration \(error.description)")}else{println("Response during device registration json: \(response.responseJson.description)")vartags=["parkstore"]IMFPushClient.sharedInstance().subscribeToTags(tags,completionHandler:{(response:IMFResponse!,err:NSError!)->Voidiniferr!=nil{println("There was an error while subscribing to tag")}else{println("Successfully subscribe to tag parkstore")}})}
Add the following function inside Appdelegate which triggers when push notification arrived in client app.
1
2
3
4
5
6
7
8
9
funcapplication(application:UIApplication,didReceiveRemoteNotificationuserInfo:[NSObject:AnyObject]){println("Got remote Notification. Data : \(userInfo.description)")letinfo=userInfoasNSDictionaryletdata=info.objectForKey("aps")?.objectForKey("alert")as!NSDictionaryletuserData=data.objectForKey("body")as!StringletalertView=UIAlertView(title:"WishList!",message:"\(userData)",delegate:nil,cancelButtonTitle:"OK")alertView.show()}}
The following are the screenshots of client application.
Migration to On-Prem
Migration of Client Application
Migration of Client Application includes following two steps
Configuring Cocoapods
Client App Migration
Configuring Cocoapods
If CocoaPods has not been installed on a specific computer:
Follow the "Getting Started" guide for CocoaPods installation: http://guides.cocoapods.org/using/getting-started.html
Open "Terminal" at the installation location and run the "pod init" command
The following steps assume that the client application is working with CocoPods. If not, follow this "Using CocoaPods" documentation : click here.
In both cases, the instructions below explain how to edit the "Podfile" file.
Open the "Podfile" file located in the root of your XCode project in a favourite text editor.
Comment out or remove the existing content.
Add the following lines:
1
2
source 'https://github.rtp.raleigh.ibm.com/imflocalsdks/imf-client-sdk-specs.git'
pod 'IMFCompatibility'
Open "Terminal" at the location of "Podfile".
Verify that the XCode project is closed.
Run the "pod install" command.
Open the [MyProject].xcworkspace file in XCode. This file is located side by side with [MyProject].xcodeproj.
An usual CocoaPods-based project is managed as a workspace containing the application (the executable) and the library (all project dependencies brought by the CocoaPods manager).
In Xcode's Build Settings, search for "Other Linker Flags" and insert ${inherited} (if -ObjC is defined in this field, you can just delete it, since it is configured in the CocoaPod project).
All on-premise applications require the "worklight.plist" file to be present in the application resources. In the IBMMobileFirstPlatformFoundationNativeSDK pod we supply a file named sample.worklight.plist.
Locate the "sample.worklight.plist" file in the ‘IBMMobileFirstPlatformFoundationNativeSDK’ pod.
Copy this file to the parent (application) project and rename it to "worklight.plist".
Edit the "worklight.plist" file by setting the "application id" key to the name of your application deployed to the on-premise MFPF server
Migration of JAX-RS Application to JAVA Adapter
To migrate JAX-RS application to on-prem (MobileFirst Foundation) server we need to do the following steps for server:
Create MobileFirst Project --> Create native API app for iOS
Add two adapters for Custom Authentication and Localstore and migrate the JAX-RS code as shown in the following example.
Copy the JAX-RS BlueMix code and paste it in the newly created Localstore Java adapter JAX-RS file.
Add and remove the following changes in your adapter code.
remove /{tenantId}/
remove the @PathParam -> PathParam("tenantId") String deviceId and @PathParam("realmName") String realmName
Add scope to the all http api resource @OAuthSecurity (scope="customAuthRealm_3")
@GET@OAuthSecurity(scope="customAuthRealm_3")@Path("/getAllItems")publicStringgetAllItems()throwsMissingConfigurationOptionException{init();JsonArrayjsonArray=newJsonArray();for(Objectkey:props.keySet()){jsonArray.add(parser.parse(props.getProperty((String)key)).getAsJsonObject());}returnjsonArray.toString();}@PUT@OAuthSecurity(scope="customAuthRealm_3")@Path("/addItem")publicvoidaddItem(StringitemJson)throwsMissingConfigurationOptionException,URISyntaxException,IOException{try{init();intnewKey=props.keySet().size()+1;props.put(String.valueOf(newKey),itemJson);URLurl=this.getClass().getClassLoader().getResource("data.properties");Filefile=newFile(url.toURI().getPath());FileOutputStreamfoStream=newFileOutputStream(file);props.store(foStream,"saving new item");foStream.close();}catch(IOExceptionioe){ioe.printStackTrace();}}@POST@OAuthSecurity(scope="customAuthRealm_3")@Path("/addAllItems")publicStringaddAllItems(StringitemsJson)throwsMissingConfigurationOptionException,URISyntaxException,IOException{try{init();clearAllData();JsonArrayjsonArr=parser.parse(itemsJson).getAsJsonArray();for(inti=0;i<jsonArr.size();i++){props.put(String.valueOf(i+1),jsonArr.get(i).toString());}URLurl=this.getClass().getClassLoader().getResource("data.properties");Filefile=newFile(url.toURI().getPath());FileOutputStreamfoStream=newFileOutputStream(file);props.store(foStream,"saving new item");foStream.close();return&"true";}catch(IOExceptionioe){ioe.printStackTrace();}return&"false&";}@DELETE@OAuthSecurity(enabled=false)@Path("/clearAll")publicStringclearAllData()throwsMissingConfigurationOptionException,URISyntaxException,IOException{init();props.clear();System.out.println("Size : "+props.size());URLurl=this.getClass().getClassLoader().getResource("data.properties");Filefile=newFile(url.toURI().getPath());FileOutputStreamfoStream=newFileOutputStream(file);props.store(foStream,"clearing all data");foStream.close();return"cleared";}
Configuring Custom-OAuth
Add realm with same name you had on BlueMix and login module to the authenticationConfig.xml.
Create HTTP Push Adapter with following function code which will send the user push notification to the devices which is subscribed to tag "parkstore".
1
2
3
4
5
6
7
8
9
10
11
functionsendTagNotification(notificationText){varnotificationOptions={};notificationOptions.message={};notificationOptions.target={};notificationOptions.message.alert=notificationText;notificationOptions.target.tagNames=["parkstore"];WL.Server.sendMessage("ParkStoreMFP",notificationOptions);return{result:"Notification sent to users subscribed to the tag parkstore."};}
By performing above steps one can easily run iOS app built for Bluemix on MobileFirst Platform and following are the links to samples.
Inclusive terminology note: The Mobile First Platform team is making changes to support
the IBM® initiative to replace racially biased and other discriminatory language in our code and content
with more inclusive language. While IBM values the use of inclusive language, terms that are outside of
IBM's direct influence are sometimes required for the sake of maintaining user understanding. As other
industry leaders join IBM in embracing the use of inclusive language, IBM will continue to update the
documentation to reflect those changes.