Quantcast
Channel: SCN : Blog List - SMP Developer Center
Viewing all 370 articles
Browse latest View live

SMP 3 Security - SAML2 Authentication

$
0
0

SMP 3 Security - Configuration of SAML2 Authentication

 

Topics

 

Introduction

 

SAML support in SMP3 got already introduced in SMP 3 SP05 as a new feature. In the last months I was involved in several customer projects were we implemented SAML authentication.

 

By using SAML you have a great flexibility, because you can use any authentication method (as long as your IDP is supporting it) independent of SMP's predefined security modules.

 

Let's start with a small introduction to SAML. The Security Assertion Markup Language is a defined standard way for authenticating clients. The authentication is performed by a system called "Identity Provider" (IdP). This system is creating "assertion tickets" for the concrete resources the client is requesting. These resource servers are named "Service Providers" (SP). One assertion ticket is always valid for one service provider.


When we now request some resources from a Service Provider we have to authenticate against the IdP first. Because the Service Provider is not authenticating the user (it is "only" validating the assertion ticket coming from IdP), the authentication method is not limited to security mechanism provided by this Service Provider.

 

SAML Process Flow with SMP

 

The SAP Mobile Platform is your Service Provider and is forwarding the user to the IdP for authentication. All communication is performed always between client and SP and between client and IdP, thus there is no direct connection between your SMP and your IdP required. But the client (e.g. the mobile app) needs to have access to the SMP server as well as to the Identity Provider.

 

Following picture should give a basic idea of the client/server communication which is performed if SAML is used as authentication provider.

 

SMP3 Security - SAML2.png

Description of Steps

 

1) Connecting to SMP

The mobile device is connecting to SMP. It does not matter if this is a AppCID registration (first time app execution) or a request to get (or update) data from backend.

 

 

2) Redirecting to IdP

SMP checks the incoming request and searches for a valid session. (A SMP session is identified by cookies X-SMP-SESSID and X-SMP-SESSIDSSO). Because no session is identified, SMP is calling the associated Security Profile. The security profile contains the SAML2 Authentication Provider. SMP is now sending a Redirect (to specified IdP address) to the mobile app. Beside this Redirect the response contains the SAMLRequest object (base64 encoded)

 

 

3) Asking IdP for Authentication

The mobile app is following the redirect and thus contacting the IdP and providing the SAMLRequest.

 

 

4) Responding to AuthRequest

The IdP checks if there is already a valid security (IdP) session. If yes then there would be no need for the client to authenticate again. If no, IdP will ask the user for identifying and authenticating himself.

 

 

5) Identifying and Authenticating the user

This step depends on the security setup on the IdP. Oftne the IdP is displaying a webpage where the user has to authenticate, e.g. by supplying user and password, or a certificate. This step can also be repeated, e.g. if the IdP requests multi factor authentication. So in a first step user has to provide user and password and in a next step a One Time Password (OTP) that got send to the user by using another communication channel (e.g. SMS).

 

 

6) Responding with SAMLResponse

The final response from IdP contains the SAMLResponse object. One part of this SAMLResponse contains the SAML Assertion Ticket and also the destination service provider address is mentioned here.

 

 

7) Posting SAMLReponse to SMP

The mobile app is using the received destination url to send a POST request to sMP (to address ="https://<smp3server : https port>/SAML2/SSO/POST"). SMP's Assertion Consumer Service is listening on this address for the SAML response.

 

 

8) Redireting to originally requested resource

The Assertion Consumer Service is validating the SAMLResponse (check if siganture of IdP is trusted), will then create a new SMP session and send a last redirect to the mobile app. This redirect is pointing to the originally requested resource (from step 1)

 

 

9) Calling the originally requested resource

Same request than in step 1 gets sent again, but this time the session information (SMP session cookies) are attached.

 

 

10) SMP is returning the requested data

SMP Session check is successful and thus SMP will return the requested resource

 

 

SMP SAML2 Configuration

 

  • Generate a Security Provider key pair in Management Cockpit.
  • Use „Generate Key Pair“ functionality in Cockpit
  • Choose a unique Local Provider Name for your SMP (Name of Service Provider)
  • The base url is the hostname and port of the SMP3 load balancer or SMP server

 

image48.png

 

Your SMP server is serving as Local Service Provider.

Click on Get Metadata to receive the metadata document which has to be imported into the IdP…

NOTE: This local SP metadata will need to be imported and configured to the IDP. This will have to be done for each unique SMP installation.

 

In tab Trusted Identity Provider click on „New“ and import your IdP metadata document. This is an XML file that you can receive from your IdP.

 

image49.png

image50.png

Create a new security profile and add SAML2 as Authentication Provider

 

image51.png

 

Note: The Identity Provider Name must match the Trusted Identity Provider name (configured in last step)

 

Testing SAML Authentication with SMP3

 

SSOCircle is providing a free IDP that can be used to test SMP’s SAML flow..

 

  1. Go to https://idp.ssocircle.com/sso/UI/Login?gx_charset=UTF-8 and register
  2. Login or Register
  3. Choose Manage Metadata > SSOCircle Public IDP Metadata.
  4. Save IDP metadata as xml file
  5. Import IDP metadata into Management Cockpit > Settings > SAML > Trusted IDP
  6. Export SMP Service Provider metadata (Settings > SAML > Local Service Provider > Get Metadata)
  7. In SSOcircle Metadata webpage click on „Add new Service Provider“

image53.png

 

8. Create new app in Management Cockpit and assign (new) SAML security provider

(where Authentication Provider set to SAML2 with IDP Name: http://idp.ssocircle.com

See: http://scn.sap.com/docs/DOC-62329 for more information

image51.png

 

Testing SAML Authentication in Browser REST Client

 

SAML can be tested inside the browser, because the browser understand the redirects and can keep cookies in his session…

We want to create a new registration on SMP server and want to use SAML (as described in the previous sections) for authentication.

 

Open registration URL in Chrome:

https://<smp3 server>:<https port>/odata/applications/latest/<appid>/Connections

Concrete sample url could look like this:
https://dewdfwssp2888.dhcp.wdf.sap.corp:8081/odata/applications/latest/com.sap.mit.samltest/Connections

 

This will redirect you to IdP Login Page.

  image54.png

 

After login, IDP will redirect back to SMP (you should see HTTP 501)

 

image55.png

Now we got a SMP session already, because we did not send a POST Request to SMP, SMP responds here with an HTTP501. So let us send a concrete POST request from a REST client. Because we now have a SMP3 session we should be able to create a new registration.

 

Open a REST client (e.g. POSTMAN) and send the POST registration request (because we have now a security context/session there is no need to provide any credentials and SMP server should response with HTTP 201)

 

POST: https://<smp3 server>:<https port>/odata/applications/latest/<app id>/Connections

 

Header:

Content-Type: application/json

 

Body:

{"DeviceType":"Android"}

 

image56.png

 

You are now successful onboarded with SMP by using SAML…

 

 

Backend Connection

 

When choosing SAML for authentication you might also have to think about a single sign on solution for connecting to the backend. Because of the large diversity of backend systems there is no solution available that is working for every backend.

 

Possible SSO scenarios (especially in connection with SAML) are:

 

 

 

Logo.png


How to Consume Multiple ODATA Services in Native Android(Online) App Using SMP 3.0

$
0
0

Hi Folks,

 

Before proceeding on this blog, i would like to thank Claudia Pacheco for her wonderful self explanatory blog How to consume OData Services in online Mode (Android) which really helps me in achieving my goal.

 

 

As i was proceeding on my app development, i faced a requirement to consume multiple OData services from a single data source. As a newbie on SMP and Android side i faced few challenges in starting to achieve it and tried to find some useful code but hard luck. Later on i got the success. So, thought to share how i implemented hoping it may become useful to other newbie like me.

 

Prerequisites

 

  • SMP SDK 3.5+ & Android Studio installed on machine
  • Added the MAF resources and completed the on boarding of users on SMP 3.0 using MAF logon UI /Core libraries
  • Have set the primary back-end point url and created the multiple back-end connections for multiple OData.

 

Here i'll take an example of read and create operation with two different OData services.

 

Steps to Achieve

 

Step 1 : Creating the MultipleOpenListner class which implements the OpenListner as "multipleOpenListner.java"

 

public class multipleOpenListner implements OpenListner{
//creating multiple listener instance for multiple back-end connections
public static leOpenListner Instance = null, readInstance, createInstance ;
private final CountDownLatch latch = new CountDownLatch(1);
OnlineODataStore store;
Exception error;
public static multipleOpenListner getInstance(String ch){
switch(ch){
case "read":if(readInstance == null){
readInstance = new multipleOpenListner();
Instance = readInstance;
}
break;
case "create":if(createInstance == null){
createInstance = new multipleOpenListner();
Instance = createInstance;
}
break;  }
return Instance;
}
@Override:
public void storeOpened(OnlineODataStore store){
this.store = store;
latch.countdown();
}
@Override:
public void storeOpenError(ODataException e){
this.error = e;
latch.countDown();
}
public synchronized boolean finished(){
return (store != null || error!=null);
}
public synchronized Exception getError(){
return error;
}
public synchronized OnlineODataStore getStore(){
return store;
}
public void waitForCompletion(){
try{
if(!latch.await(30, TimeUnit.Seconds)){
throw new IllegalStateException("Open listener was not called within 30 seconds");
}
else if(!finished()){
throw new IllegalStateException("Open listener is not in finished state after having completed successfully");
}
}
catch(InterruptedException e){
throw new IllegalStateException("Open Listener waiting for result was interrupted");
}
}
}

 

Step 2 : Opening OnlineODataStore under the class "OnlineManager.java"

 

public class OnlineManager {   public static final String TAG = OnlineManager.class.getSimpleName();   public static boolean openOnlineStore(Context context, String conCase) throws ODataException{   multipleOpenListner openListener = multipleOpenListner.getInstance(conCase);   if (openListener.getStore()==null) {  LogonCoreContext lgCtx = LogonCore.getInstance().getLogonContext();  IManagerConfigurator configurator = LogonUIFacade.getInstance().getLogonConfigurator(context);  HttpConversationManager manager = new HttpConversationManager(context);  configurator.configure(manager);   //XCSRFTokenRequestFilter implements IRequestFilter  //Request filter that is allowed to preprocess the request before sending   XCSRFTokenRequestFilter requestFilter =  XCSRFTokenRequestFilter.getInstance(lgCtx);  XCSRFTokenResponseFilter responseFilter =  XCSRFTokenResponseFilter.getInstance(context, requestFilter);  manager.addFilter(requestFilter);  manager.addFilter(responseFilter);   try {  String endPointURL ="";   switch(conCase){   case "read": endPointURL = lgCtx.getAppEndPointUrl();   break;   case "create": endPointURL = "http://server:8080/backEnd_ConnectionName";   break;   default : break;  }  URL url = new URL(endPointURL);   // Method to open a new online store asynchronously   OnlineODataStore.open(context, url, manager, openListener, null);  openListener.waitForCompletion();   if (openListener.getError() != null) {   throw openListener.getError();  }  } catch (Exception e) {   // throw e;   Log.e(TAG, e.getLocalizedMessage(), e);  }   //Check if OnlineODataStore opened successfully   OnlineODataStore store = openListener.getStore();   if (store != null) {   return true;  } else {   return false;  }  } else {   return true;  }   //End   }
// Method to read
public static ArrayList read(String conCase) throws ODataException{  ArrayList<String> arrList = new ArrayList<String>();  multipleOpenListner openListener = multipleOpenListner.getInstance(conCase);  OnlineODataStore store = openListener.getStore();   if (store!=null){     ODataProperty property;  ODataPropMap properties;   try {   //Executor method for reading an Entity set synchronously   ODataResponseSingle resp = store.executeReadEntitySet("Entity",null);   //Get the response payload   ODataEntitySet feed = (ODataEntitySet) resp.getPayload();   //Get the list of ODataEntity   List<ODataEntity> entities = feed.getEntities();   //Loop to retrieve the information from the response   for (ODataEntity entity: entities){  properties = entity.getProperties();  property = properties.get("Property_Name");  String propName = property.getValue().toString();  arrList.add(propName);  }  } catch (Exception e) {  Log.e(TAG, e.getLocalizedMessage(), e);  }  }   return arrList;
}
// Method to create
public static void create(UIListener uiListener, String conCase) throws ODataException{   multipleOpenListner openListener = multipleOpenListner.getInstance(conCase);  OnlineODataStore store = openListener.getStore();   if(store==null) return;   try{  ODataEntity newEntity = createEntity(store);  RequestListener requestListener = new RequestListener( Operation.Create.getValue(),uiListener);   //Scheduling method for creating an Entity asynchronously   store.scheduleCreateEntity(newEntity, "cms_Data",  requestListener, null);  }   catch (Exception e){  e.printStackTrace();  }
}
public static ODataEntity createEntity(OnlineODataStore store){  ODataEntity createData = null;   if(store!= null){   //If available, it will populates those properties of an OData Entity which are defined by the allocation mode   createData = new ODataEntityDefaultImpl("Model.cms_Data");   leadData.getProperties().put("Full_Name", new ODataPropertyDefaultImpl("\"Full_Name\"","Deepak Sharma"));   leadData.getProperties().put("Contact_Number", new ODataPropertyDefaultImpl("\"Contact_Number\"","9810234567"));    createData.setResourcePath("cms_Data", "cms_Data");
}
return createData;
}
}

 

 

Step 3 : Call openOnlineStore method from your MAFLogon Activity class.


try {  OnlineManager.openOnlineStore(this,"read");
} catch (ODataException e) {  e.printStackTrace();
}



Step 4 : Call read and create methods from activity class wherever required with openOnlineStore method.


try {   OnlineManager.openOnlineStore(this,"create");   OnlineManager.create(this, "create");
} catch (ODataException e) {  e.printStackTrace();
}




Hope it helps!!!

New Integration Gateway Features in SMP 3.0 SP08

$
0
0

As Sami announced in his blogSAP Mobile Platform 3.0 (SP08) had been released for download in SAP service marketplace since June, I will just take this opportunity to recap what are new in SP8 for integration gateway.

 

1. Unified Admin UI

We have been listening to you! One of the feedback on integration gateway was the separated admin cockpit from SMP admin page. Which are confusing for end users who need to switch back and forth the admin URLs for SMP and integration gateway. In SP8, we now have a unified admin UI. A new tab called OData Services is added to SMP Admin page. This is the same integration gateway admin page where you can configure OData services, manage backend destinations and monitoring logging. Except that you don't need to do that in a separated URL anymore. Good stuff!

Unified UI.jpg


2. Deep Insert for REST

REST is one of the most popular data sources we see in the market. We released full CRUD support for REST in SP7. With SP8 we now have support of deep insert for REST. That means you can create related entities in one request. For example, you can create a sales order and a sales order item together and at the same time, and for REST, not only for JDBC data source that you had seen in SP7.

 

3. Delta Token on Complex OData Model

Delta handling is key for improving mobile app performance. Delta token handling was available with custom script (Java Script or Groovy) in SP5. With SP8 we are expanding the delta handling to complex OData model with parent-child relationship. Please note at this release the delta handling is still restricted to parent level entity but not child level entity. And as a side note, in TechEd 2015 Las Vegas, I will be hosting a 2 hrs hands-on session with SAP RIG colleagues on delta handling for mobile app development. Please stop by if you want to learn more on integration gateway and delta handling. We are looking forward to seeing you there.

 

Those are some main features for Integration Gateway in SMP 3.0 SP8. We are working on some pretty cool features for the upcoming SP9. So again, stay tuned... and thanks for your attention!

 

Related blogs:

Pitfalls I Faced While Consuming Service In SMP Integration Gateway

$
0
0

Hello Coders,

 

     On a fine morning few weeks back when my manager called me up and said  'Hey Vishnu ,how about implementing SMP 3.0 in our network??'.Do you want to know what i was thinking ?  'Hell yeah!!!! this is what we call an opportunity'.

 

There were certain things that pulled me back.

  • No Consultants in my organisation to guide me
  • No previous experience in setting up a server and configuring it to make it work
  • And the fact that i was all alone for this task

 

 

Ok,let's say i didn't bother about the above mentioned points,because i have already set my mind to go for it.

 

Gods grace server set-up was done and i would say 'perfecto' and there weren't any issues.

 

I started of by following a document of Jitendra Kansal  SMP 3.0: An End to End guide to create an OData service for a given SAP Gateway data source

 

Even though the tutorial was using a SAP Delivered service,for some reason i decided to use the service that i created.This decision drove me to dig more into SMP IGW and made me hunt for answers all over google,scn and wherever i could.

 

After following every steps my output was so disappointing.

 

smp_systemadmin.JPG

 

 

I didn't understand a single word what my Browser was trying to tell me.

 

There were some interesting stuff i noticed while i was registering my service.Even though my service was under a NAMESPACE it wasn't shown in the Gateway Cockpit during registration.

 

 

service_loc.JPG

 

 

search_service.JPG

 

 

 

After getting inputs from Pavel Penaz i started troubleshooting process in IGW.

 

pavel1.PNG

 

 

pavel2.PNG

 

 

 

After performing the above steps i could retrieve some error in the Gateway Cockpit.

 

error_log_migo1.JPG

 

 

'(Function call failed; could not find the function Z_MFW_MIGO_UI5_PO_LIST)'.



I Dint understand why it was searching for the FM in HUB System,but before digging more into that let my explain how the systems were setup in my corporate network.


1)Front Server (GW_CORE,IWBEP,IWFND)

2)Backend ECC 6.0

 

i.e we have a HUB Deployment.

 

  • I have created an odata service in Frontend where it internally calls the an RFC Function in Backend.

 

 

At this point Bjoern Woppmann was looped into the discussion.And gave as a exhaustive explanation as below.

 

bjoer.JPG

 

 

 

And After reading Capabilities Matrix: Which SAP product to use to expose your business data as OData service for UI consumption

most the confusions i had were gone ...  

 

 

 

All the above were based on the discussion Error while calling the entity set in SMP 3.0 SP07(via IGW) that i had in this community.I tried to consolidate it into a single blog so that anybody who is trying to step on to SMP 3.0 would feel helpfull(i guess so).

 

For more on Consuming service with IGW , it would be nice for every OData Developer to have an insight on Custom Coding.Carlos Roggan have a great Blog for every Beginners. Integration Gateway: REST data source, overview of Blogs

 

Happy Coding

Cheers,

Vishnu

SAP Mobile Platform SDK 3.0 SP09 - What is new ?

$
0
0

We are pleased to announce the release of SDK SP09 to service market place. The major focus for this release was to provide enhancements, improve stability and lay the frame work for new features.

 

The new or changed features for the different components of the SDK are the following.

 

Native SDK

 

The application settings in the initial versions of  LogonCore component could be accessed from ConnectionData property for iOS and Windows. For the Android, only some settings were available through dedicated properties and the ConnectionData property did not exist. In SP08, the ApplicationSettings property of the LogonCore instance replaced the ConnectionData property on the RegistrationContext for accessing server application settings.

In SP09, the ConnectionData property has been enhanced to support the V2 registration service provided in SAP Mobile Platform Server version 3.0 SP08.

 

We have also updated the usage guides for supportability with detailed examples of how to implement logging and end-to-end tracing for all supported platforms.

 

Hybrid SDK


The following are some of the enhancements and updates to the hybrid SDK.

 

  • Support for Apache Cordova CLI 5.1.1 for Hybrid SDK (Kapsel) applications.
  • The Push plugin has been enhanced to support management of push notifications through SAP Push Hub.
  • The Feature Restriction policy is now supported for use with Windows applications, in addition to Android and iOS.
  • The X.509 certificate provider interface has been updated to reflect changes to the interfaces, new sample provider projects, support for authentication without SAP Mobile Platform, and support for a Windows interface.
  • Changes to networking support affect Windows applications that use the SAP.Net.Http networking component

 

SAP Fiori client packaging*


We are also introducing a new feature to support the SAP Fiori client packaging . Customers who have already installed a set of Fiori Apps (for example, CRM) can make these applications available in a packaged form suitable for installing on a mobile device and built using the Cordova and Hybrid SDK (Kapsel). The CLI packager tool  is a node.js script bundled with the Hybrid SDK and it allows users to create a prepackaged Fiori application by obtaining assets from a SAP Fiori Front-End Server (FES).


*For the packager to work , the SAP cloud build service from Mobile Secure is needed as a prerequisite. This is currently not generally available at the time of writing this blog.

 

Agentry Toolkit

 

The following are the updates to the Agentry toolkit


  • Agentry applications now fully support the Secure Sockets Layer (SSL) standard, including Alternate names in SSL certificates ,Wildcards in common names in SSL Certificates,Download of certificate chain
  • Agentry editor changes to support properties like screen set header style , list tile view row style
  • Documentation updates for Open UI and Data API

Resolution for error: Vital Product Data registry is in use...

$
0
0

When downgrading from Mobile SDK SP09 to SP08 to check some partner's code, I encountered the following error message: 

 

"The Vital Product Data registry is in use by another installer.  Please start this installer after the other installation is finished."

Screen Shot 2015-07-21 at 11.12.46 AM.png

Resolution

Check if this file exists:  "~/Installshield/Universal/SMPClient/instance.running", remove it, and try installer again.

SAP HANA Cloud Platform mobile services 1.2 released

$
0
0

Hi there.

It's time to talk about new features, since as of today we have released version 1.2 of HCPms.

 

Sometimes it's not easy to weight the importance of a feature and give it the right visibility, and for this release I was really struggling on deciding which feature I would like to present first. I have decided for a developer centric one. A feature that might look simple, or obvious but in the end it greatly eases the life of developers. The feature I have in mind is SCIM support for HTTP Basic authentication.

 

SCIM Support (HTTP Basic Auth)

You may wonder about this, because you thought that BASIC auth was possible before this release and you where right. You can configure your App in the way that it accepts BASIC auth headers (or challenge you once you request a resource) against SAP ID service - which is you default IdP.

With SCIM (SCIM webpage) support you can expose the HTTP challenge that is configured directly on you OData Service. This enables you to expose the users that are configured on your services URL to be authenticated on the mobile device.

Actually there are two different version of SCIM available now. One is provided by HCP itself and the other is a mobile services specific SCIM.

The SCIM provided by HCP (and Cloud Connector) has a different scope, since it is valid for all App on you account - so XS or JAVA applications are affected as well, while the mobile specific SCIM service is only valid within mobile services and can be configured for each application differently.

 

So basically, if you would like to expose an on-Premise LDAP to your HCP account - you would use the HCP SCIM. If you want to reuse your users on a specific OData Service, then you would choose the mobile service specific SCIM.

This is (unfortunately) not available for trial landscape.

 

Preview Landscape

Many customers expresses their concerns about the loss of control of the lifecycle of their system - which is in this case HCPms. SAP controls the lifecycle, installing patches, releases, changing API and so forth. While this is one of the beauties of an as-a-Service offering, we take those thoughts serious. And with this release we offer each customer access to a preview landscape. This landscape can be requested via a self-service and is intended to be used for regression testing. The preview landscape will receive the newest release on RTC date (Release to Customers). Currently we plan to provide a minimum of thirty days preview period before we deploy the new version to the productive landscape and update all our customer landscape. During this preview period we encourage our customer to use the SAP Cloud Support to notify us about any incompatibility they found during their tests.

 

Mobile Web App Support

No, this does not mean you can host web applications in mobile services. There are other services on HCP that do this. Support for mobile web applications does mean that we now support a new type of App configurations, called "Web". This allows you to proxy existing web hosted applications - either on the web or through the Cloud Connector for on-premise web apps (like a specific portal page).

The Web app does two things for you. First it exposes an on-premise web app and makes it available to your mobile browser on the device without the need to start a VPN connection on the device or using a special application like the SAP Fiori Client. Second, it can add authentication in front of your webpage as it would challenge your mobile browser with a login page that uses the authentication that is configured on your App - SAP ID service for example.

 

And this does work for on-premise Fiori applications as well!!

 

Administration User Experience Improvements

 

We did a log of small changes in the UI for the Admin Cockpit to improve the user experience for the users of HCPms - which are developers and administrator. It's no fun to list all the small changes and tweaks we did. I would rather suggest that you take a look yourself. One thing I want to disclose here, though.

If you visit the Admin Cockpit the first time you'll see this:

 

Whatsnew.png

GLAS Auditing

This is probably not the thing your are waiting for, but it's essential for license compliance anyway. We do support the standard SAP Global Licensing Auditing Service. This allows our customers to keep track of their used licenses easily and report them back to SAP. Well, as I said, not a thing a developer wants to bother with.

 

That's it for now. Stay tuned and

 

 

Have Fun,

Martin

Integrated Identity in HCP - Authorization and Relevance Filtering in Olingo OData Web Services - Part 1

$
0
0



Identity integration is just as important as Data integration.


I am working on a longer series of articles describing end-to-end HCP mobile application development, but I wanted to take the opportunity to post a quick solution to a problem I have been researching for some time: How can you effectively capture and incorporate a user's identity in your Olingo OData web services?

 

Integrated Identity Management services are a cornerstone of the HANA Cloud Platform.  These services can take many forms.  You can use any SAML 2.0 Identity Provider (IdP) as your user base. The IdP can be SAP's Cloud Identity Tennant, a commercial Cloud identity provider such as Microsoft Azure.  It can also integrate with your own on-premise Identity Provider such as AD FS or Shibboleth.  HCP's IdP Trust configuration is fairly straightforward to set up if you are familiar with SAML and an integration can include passing custom Group membership information along with other user attributes into all HCP applications.

 

In this article, I will give you a quick glimpse into how you can access identity information in an Olingo web service that you might deploy in HCP.

HCP Identity.png

Apache Olingo is a handy open source framework for developing RESTful OData web services. Olingo supports several modes for defining the data model of a web service. I find the JPA mode easy to work with -- Olingo's JPA API allows your choice of either defining a web service object model from scratch by defining your own JPA Java objects or by using Eclipse to automatically generate a JPA model from a set of source SQL tables.

 

Defining a data model, though, only addresses a portion of the practical challenge of creating a new web service.  Securing the web service is an essential element too, and that often requires integrating user identity tightly into the security implementation.

 

For example, let's say I wanted to create a web service supporting a messaging application. In order to secure the messages from prying eyes, I might leverage the user's identity to filter the messages down to only those messages the logged-in user is authorized to view. Commonly used OData training samples, such as ESPM or Northwind conveniently overlook such scenarios.

 

It turns out that it is relatively easy to add such intrinsic "relevance" filtering to an Olingo entity collection.  Let's look at how it works, but let's first elaborate a bit on our use case so that what we construct will make the most sense.

 

Here's a simple set of rules describing the elements and behavior of our application:

 

  1. A User can send a Message to one or more other Users.
  2. Messages are grouped into Conversations.
  3. Each Conversation has a list of Users that are members.
  4. A User may be a member of any number of Conversations.
  5. Only members of a Conversation are allowed to view that Conversation's Messages.
  6. A User can only view the Conversations that they are a member of.
  7. When A Message is created, the Message sender is assigned automatically by the web service. (In other words, a User cannot "send" a Message impersonating another User.)
  8. Users may create new Conversations on demand.
  9. A User may add an additional user to any Conversation they belong to.

 

This list of rules isn't exhaustive by any stretch, but it defines how we would like this web service to behave well enough that what follows should make sense.

 

Items one through four can be expressed by this entity relationship diagram:

 

HCP Messaging.png

Starting from this ERD, it is easy to use Olingo's JPA API to define Java objects that will manifest this structure in a persistent SQL database and simultaneously create corresponding OData Entity Collections.

 

Items five, six, and seven all express a need for the web service to use information about the logged-in user to either filter the data to what's authorized or to ensure information integrity.


So, how do we efficiently obtain user information in an Olingo JPA web service running in HCP?


The first step is to obtain the name of the authenticated user.  Olingo's JPA code saves the HttpServletRequest object corresponding to each inbound request in a way that fairly easy to retrieve:

 

import org.apache.olingo.odata2.jpa.processor.core.ODataJPAContextImpl;
import org.apache.olingo.odata2.api.processor.ODataContext;
import javax.servlet.http.HttpServletRequest;    .    .    .  ODataContext ctx = ODataJPAContextImpl.getContextInThreadLocal();  HttpServletRequest r = (HttpServletRequest) ctx.getParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT);  String user = r.getRemoteUser();  if (user != null) {      MessageUser u = IdentityInteraction.verifyUser(r, em);      logger.debug( "username from HttpServletRequest '"+u.getUsername()+"'" );  }  else {      logger.error("Assertion error: There is no authenticated user defined in the HttpServletRequest -- " +          "check your web.xml application configuration");  }

That will return the username, but we'd like to be able to access other metadata about that user too -- what about their first name or last name? -- or their e-mail address?  HCP provides some helper classes to access those user attributes.


import com.sap.security.um.user.UnsupportedUserAttributeException;
import com.sap.security.um.user.User;
import com.sap.security.um.user.UserProvider;
import javax.naming.InitialContext;
.
.
.
 InitialContext ctx;  try {      ctx = new InitialContext();      UserProvider userProvider;      userProvider = (UserProvider) ctx.lookup("java:comp/env/user/Provider");      User user = null;      if (request.getUserPrincipal() != null) {          nameId = request.getUserPrincipal().getName();          user = userProvider.getUser(nameId);          if ( user != null) {              try {                  email = user.getAttribute("email");              } catch (UnsupportedUserAttributeException e) {                 // no error              }              try {                  lastName = user.getAttribute("lastname");              } catch (UnsupportedUserAttributeException e) {                  // no error              }  try {    firstName = user.getAttribute("firstname");   } catch (UnsupportedUserAttributeException e) {  // no error    }    }  }    } catch (NamingException e1) {    logger.error("NamingException insde IntentityInteraction housekeeping: " + e1.getMessage());    e1.printStackTrace();    } catch (PersistenceException e2) {  logger.error("PersistenceException insde IntentityInteraction housekeeping: " + e2.getMessage());  e2.printStackTrace();   }

 

In order to take advantage of this lookup, your application's web.xml file must include this resource reference in the web-app definition:

 

  <resource-ref>    <res-ref-name>user/Provider</res-ref-name>    <res-type>com.sap.security.um.user.UserProvider</res-type>  </resource-ref>


Summary


The Java code shown here can be used to look up user information in any Olingo JPA web service running in HCP.  If you are running in a different container, it will likely have its own way to lookup user attributes.


I am working on a complete working sample based on these code snippets.  Until that's available, hopefully this will help anyone trying to solve this same problem in their own Olingo projects. In a following article, I'll show you exactly where to insert this code into your Olingo JPA web service and how to add extra relevance and security checks into each OData Entity Collection.


SMP 3 Security - SAPSSO2

$
0
0

SMP 3 Security - Configuration of SAPSSO2 Generator

 

Topics

 

Introduction

 

In Service Pack 8 of SMP 3 Server a new SSO mechanism got introduced. SMP is now able to directly use SAP Logon Tickets to authenticate against a backend system. SAP Logon Tickets (also named SAPSSO2 or MYSAPSSO2 cookies) are produced by SMP for an authenticated user and attached to requests going to backend systems.

 

The new Authentication Provider is called "SAPSSO2 Generator" and can only be used in combination with a "real" authentication provider, such as HTTP/HTTP Authentication, LDAP, SAML.

 

Unfortunately, there is no user mapping in SMP available. That means that the username which got authenticated in SMP must exist also in the backend system.

 

 

SAPSSO2 Process Flow with SMP

 

SMP will authenticate the user based on a "real" authentication provider. The second (or the last) authentication provider is the SAPSSO2 Generator which creates the MYSAPSSO2 cookie based on the name of the authenticated user. The SAP Logon Ticket will be attached as header to all subsequent requests to the backend system.

 

SMP3 Security - SAPSSO2.png

 

Description of Steps

 

1) The hybrid app or native app is sending a data request to SMP. The request has to contain the information that are required by SMP for proper authentication (e.g. user and password for basic authentication)

 

2) SMP is authenticating the user based on the chosen Authentication Provider.

 

3) Additionally, the authentication provider "SAPSSO2 Generator" is creating a SAPSSO2 credential based on a principal name (name of the authenticated user). This credential is stored inside SMP's session context.

 

4) SMP will attach the stored credential as MYSAPSSO2 cookie to the request (because SSO2 has been chosen as SSO mechanism) and forward it to the backend system.

 

5) The backend system will validate the received MYSAPSSO2 cookie (Trust has to be defined between backend system and SMP). The backend system will establish a user session if the issuer (SMP) is trusted and if the user is existing in the user base.

 

6) The backend system is responding with the requested data.

 

7) SMP will forward the response (without the MYSAPSSO2 cookie) to the client.

 

 

SMP SAPSSO2 Configuration

 

In the following I will described how to use the SAPSSO2 Generator. I will use a SMP 3 SP08 Server and a Netweaver Gateway as backend system.

 

Preparation

 

Before we start configuring SMP we need to create a keypair that can be used later to sign SAP logon tickets. Very important here is the requirement that this key has to be a DSA (Digital Signature Algorithm) key (RSA will not work!).

 

I will use OpenSSL for creating a self signed certificate:

 

1. Generate DSA parameters

openssl dsaparam -out dsaparam.pem 2048

SPSSO2_02_Create_DSAKey.png

2. Create a new DSA key based on the parameters

openssl gendsa -out smp_sso2.pem dsaparam.pem

SPSSO2_01_Create_DSAParam.png

3. Create a self signed certificate. The common name should match the SID of your system, e.g SMP

openssl req -x509 -new -key smp_sso2.pem -out smp_sso2.cer

SPSSO2_03_DSACertificate.png

The outcome here is the certificate (public part) which we have to import later in our backend system.

 

4. Create a keypair (PKCS12 keystore) so that we can import this keypair into SMP's keystore. (Define a password for this keystore) The attribute -name defines the alias of the keypair inside this keystore.

 

openssl pkcs12 -export -in smp_sso2.cer -name smp_sso2 -inkey smp_sso2.pem -out smp_sso2.p12

SPSSO2_04_PKCS12.png

 

SMP Configuration

 

0. Open SMP Management Cockpit, click on "Settings" then "Certificates" and choose "Import". Import here the created p12 file as PKCS#12 file.

SPSSO2_05_0_Import_keypair_SMP.png

 

1. Open SMP configuration and create a new sample application (in my case com.sap.mit.sapsso2test)

2. In backend tab choose the end point. This should be a resource url on the backend which accepts SAP logon tickets. In my case this is a sample odata service on netweaver gateway.

3. Choose "SSO2" as SSO mechanism

SPSSO2_05_App_settings.png

4. Switch to tab Authentication and create a new authentication provider. In my case I will use a System Login for authenticating a user called "marvin".

SPSSO2_06_system_login.png

5. After that add "SAPSSO2 Generator" as second authentication provider.

  • IssuerSID will be the SID of your SMP. It should match the common name of your certificate (in my case SMP).
  • IssuerClient is part of the logon ticket, so we have to specify one, e.g. 000
  • RecipientSID is the SID of our backendsystem (My Gateway has SID MH1)
  • RecipientClient is the targetted client of backend system
  • CertificateAlias is the alias of our DSA keypair that we want to use for signing our sap logon tickets

SPSSO2_07_SAPSSO2_Settings.png

SPSSO2_08_SAPSSO2_Detailed.png

6. Click on "Test Settings" to validate the SAPSSO2 Generator settings. You should get the following message. If you get an error here, SSO cookie creation might fail later on...

SPSSO2_09_0_Test_sec.png

SPSSO2_09_1_sec_provider.png

7. Save all changes.

 

Backend Configuration

 

We have to establish TRUST between the backend system and the SMP system, so that logon tickets issued by SMP are trusted by the backend system.

 

1. Open transaction STRUSTSSO2 and click on "Import Certificate" to import our certificate.

 

SPSSO2_09_Gateway_strustsso2.png

2. Choose the smp_sso2.cer certificate and add this certificate to the certificate list. And after that click on "Add to ACL".

SPSSO2_10_Gateway_strustsso2.png

3. System ID has to match the value you provided inside the certificate as well as defined inside SMP SAPSSO2 Generator settings, in my case "SMP".

SPSSO2_11_Gateway_strustsso2.png

Info: If you are accessing several clients on your neweaver you have to logon on each client and execute "Add to ACL" here.

 

4. Save all changes.

 

 

Testing SAPSSO2 Scenario

 

Now send a GET REST request to test if we can receive some data.

 

In my case I am sending a GET request to the base url of SMP (after a successful registration). The response HTTP 200 is ok and gateway is responding with the requested data through SMP.

SPSSO2_13_GET_req.png

If we trace requests on the backend, we can see that the MYSAPSSO2 cookie got attached

SPSSO2_14_backend.png

Because the MYSAPSSO2 cookie is base 64 encoded, you can decode the cookie to partly see the content. You can see that the provided values (like SID, client but also username and certificate information) are contained...

SPSSO2_15_sapsso2_decoded.png

 

Troubleshooting

 

No matched SSO credentials is found for not allowAnonymousAccess endpoint

 

2015 08 09 01:56:22#+0200#ERROR#com.sap.mobile.platform.server.proxy.core.handler.DirectProxy##marvin#http-bio-8080-exec-6##b14f4cc4-1f50-443e-9e42-8641b5429b3f#com.sap.mit.sapsso2test#4f154ee6-a137-41b1-a585-3b9737bb0430#RequestResponse### Exception caught while trying to set  credentials for anonymous access com.sap.mobile.platform.server.proxy.core.handler.exception.AnonymousAccessException: No matched SSO credentials is found for not allowAnonymousAccess endpoint [com.sap.mit.sapsso2test].

 

This error is telling you that SMP was not able to produce a MYSAPSSO2 credential, that means that there is no credential available that could be attached by SMP. SMP will now block the request and respond with an HTTP 403 Fordbidden error.

 

I had some cases where this was related to the use of a wrong certificate type. Certificate needs to be DSA encrypted. If you increase the security logging component to DEBUG you can find this log entry:

 

2015 08 09 01:56:22#+0200#DEBUG#com.sap.mobile.platform.server.foundation.security.providers.sso2generation.SAPSSO2GenerationLoginModule###http-bio-8080-exec-6##b14f4cc4-1f50-443e-9e42-8641b5429b3f#com.sap.mit.sapsso2test#4f154ee6-a137-41b1-a585-3b9737bb0430#RequestResponse###The algorithm of private key must be DSA. |

 

Solution

 

Follow the steps as mentioned above to create a DSA keypair

 

 

 

SAP_MIT_Logo.png

How to implement $expand scenario with soap data source

$
0
0

$Expand is supported in soap data source when a web service has an operation which returns 2 structures and there has to be a primary key, foreign key relationship between them. There has to be provision to create a navigation property between these 2 entities in odata model

 

Steps from Design Time tool

  1. Create an OData Service Implementation Project
  2. Create an OData Model for 2 entities Header and Items.
    Define navigation from Header to Items as below. This model fetches collection of header and the items associated with that header. So the web service must have an operation which can return collection of header and collection of items. PROP1 is a primary key in ZKRAN_HDR entity set and PROP1 is a foreign key in ZKRAN_ITEM entity set. In the association defined PROP1 of header entity will be a principal key and PROP1 of item entity will be a dependent key.

image1.PNG

Image8.PNG

 

   3. Right click on odatasvc and choose Select data source

 

   4. Select the HDR entitySet and choose the read CRUD operation. Select the data source as SOAP Service. Note: query operation can also be done

Image2.PNG

  5. Specify the wsdl file and choose the correct operation from the list of soap operations and click on finish.

Image3.PNG

 

   6. Right click on Query and select Define Custom Code

Image4.png

   7. Select the script type as either javascript or groovy script

Image5.PNG

  8. This being a read operation do a request mapping as below

 

Image6.PNG

 

  9. Do response mapping as below which maps only properties of header entity set since data source is added for header entity. The response obtained  from web service contains the items associated with the header based on PROP1. But the items are mapped using custom code

 

Image7.PNG

 

   10. In processResponseXML function the entire response from web service is fetched. This is parsed in order to get the list of hashmaps of items. Another list of hashmap must be created whose key will be Items entity set name and value will be previously created list of hashmap


function processResponseXML(message) {
  importPackage(java.util);  importPackage(java.lang);  var payload = message.getBody().toString();  var tokens = payload.split("(?=<)|(?<=>)");  var list = new ArrayList();  var innerMap = new LinkedHashMap();  var items = new ArrayList();  var foundETItems = false;  for (var i = 0; i < tokens.length; i++) {   if (tokens[i].contains("<ET_ITEMS>")) {    log.logErrors(LogMessage.TechnicalError, "Found ET_ITEMS");    foundETItems = true;    var itemMap = new LinkedHashMap();    // Iterate each item    for (var j = i+1; j < tokens.length; j++) {     if(tokens[j].contains("</ET_ITEMS>")) {      log.logErrors(LogMessage.TechnicalError, "Completed ET_ITEMS");      break;     }     if(tokens[j].contains("<item>")) {      continue;     }     if(tokens[j].contains("</item>")) {      items.add(itemMap);      itemMap = new LinkedHashMap();      continue;     }     itemMap.put(tokens[j].substring(1,tokens[j].length()-1), tokens[j+1]);     log.logErrors(LogMessage.TechnicalError, "For entry "+tokens[j].substring(1,tokens[j].length()-1)+" added value: "+tokens[j+1]);     j = j + 2;    }   }  }  if(foundETItems) {   innerMap.put("ZKRAN_ITEMSet", items);   list.add(innerMap);  }  // Set the list of hashMaps to a header  message.setHeader("MappingOutput", list);  return message;
}

 

  11. In processResponseData message body will have data which is the output from mapping. Data from message header and message body are combined to a new list of hashmap.

 

function processResponseData(message) {
importPackage(java.util);
 importPackage(com.sap.gateway.ip.core.customdev.logging);
 // Get the data from the previously set header
 var data = message.getHeaders().get("MappingOutput"); 
 log.logErrors(LogMessage.TechnicalError, data);
 var body = message.getBody();
 var newData = null;
 var newList = new ArrayList();
for (var i = 0; i < data.size(); i++) {  newData = new LinkedHashMap();  var dyData = data.get(i);  var mappedData = body.get(i);  newData.putAll(mappedData);  newData.putAll(dyData);  newList.add(newData);
 }
 log.logErrors(LogMessage.TechnicalError, newList);
 message.setBody(newList);
 return message;
}

 

Note: the entity to be expanded is fetched in processResponseData function and the entities which are associated with the main entity are fetched from processResponseXML function.

 

The request payload looks like

 

<soapenv:Body>
<urn:ZKRAN_TEST_EXPAND>      
<IV_HDR_ID>AA</IV_HDR_ID>   
</urn:ZKRAN_TEST_EXPAND>

</soapenv:Body>

 

The response payload looks like

 

<n0:ZKRAN_TEST_EXPANDResponse xmlns:n0="urn:sap-com:document:sap:rfc:functions">

                <ES_HDR>

                                  <PROP1>AA</PROP1>

                                  <PROP2>HelloAA</PROP2>

                                  <PROP3>BlahAA</PROP3>

                  </ES_HDR>

                  <ET_ITEMS>

                                  <item>

                                                  <PROP1>AA</PROP1>

                                                  <ITPROP2>AA1</ITPROP2>

                                                  <ITPROP3>Good Day</ITPROP3>

                                  </item>

                                  <item>

                                                  <PROP1>AA</PROP1>

                                                  <ITPROP2>AA2</ITPROP2>

                                                  <ITPROP3>Hello</ITPROP3>

                                  </item>

                  </ET_ITEMS>

  </n0:ZKRAN_TEST_EXPANDResponse>

 

   12. Right Click on Project and select Generate and Deploy Integration Content. This will deploy the bundle.

  Now fire an OData Request is https://localhost:8083/gateway/odata/SAP/SOAP;v=1/ZKRAN_HDRSet('AA')?$expand=ZKRAN_ITEMSet on the browser and response will give collection of headers and collection of items associated with header AA.

 

Note: If the odata request is https://localhost:8083/gateway/odata/SAP/SOAP;v=1/Products(1)?$expand=Category,Suppliers

 

  1. Create a list of hashmap of attributes of category.
  2. Create a list of hashmap of attributes of Suppliers.
  3. Create a list of hashmap with key as CategoryEntitySet name and value is output from 1 and another hashmap entry with key as SupplierEntitySet name and value is output from 2.
  4. Create a list of hashmap of attributes of product and hashmap fetched from the list created in 3.

 

If Product has the below fields

        


Product_id


Product_Name


1


Pepsi


2


Amul Ice Cream

 

If Category has below fields

 

      


Category_Id


Product_id


Category_Description


10


1


Beverages


20


1


Desserts

 

 

If Supplier has below fields

 

    


Supplier_Id


Product_id


Supplier_Description


100


1


ABC


200


2


XYZ

 

$Expand output based on above mentioned steps look like

 

  1. {Category_Id=10,Product_id=1,Category_Description=Beverages},
    { Category_Id=20,Product_id=1,Category_Description=Desserts}
  2. {Supplier_Id=100,Product_id=1,Supplier_Description=ABC}
  3. {CategorySet=[{Category_Id=10,Product_id=1,Category_Description=Beverages},
    { Category_Id=20,Product_id=1,Category_Description=Desserts }]},
    {SupplierSet=[{Supplier_Id=100,Product_id=1,Supplier_Description=ABC}]}
  4. [{Product_id=1,Product_Name=Pepsi, CategorySet=[{Category_Id=10,Product_id=1,Category_Description=Beverages},
    { Category_Id=20,Product_id=1,Category_Description=Desserts }], SupplierSet=[{Supplier_Id=100,Product_id=1,Supplier_Description=ABC}]}]

OfflineStore differences from OnlineStore 'Technical Cache'

$
0
0

The SODataStore model, with the Offline and Online variants, was designed so that MobileSDK developers could program their application's model in a consistent fashion, regardless of the specific online or offline requirements of the application.  Developers will still need to set-up and configure the store(s) in their application correctly for their use case.  But, the CRUD operations against an OData service provider can all be made using the <SODataStoreAsync> protocol methods.

 

We recently rolled out a feature in the SODataOnlineStore named 'Technical Cache', and I've heard a bit of confusion about how/if this cache storage relates to the SODataOfflineStore.  Here are some notes on how the two are different, and how you can think about use cases for the Technical Cache.

 

SODataOfflineStore

  • Supports CRUD operations, with OData query and $filter methods, with SODataStoreAsync protocol
  • Uses Ultralite relational database, but does not expose SQL interface
  • Database is filled according to 'defining requests' queries during setup
  • Database is synchronized with OData service provider using flush: and refresh: methods on SODataOfflineStore

 

The SODataOfflineStore is an API layer, that wraps the Ultralite database that is synchronized with the back end, using the flush: and refresh: methods.  The SODataOfflineStore wrapper essentially allows you to treat the Ultralite database like an OData backend.  You use the same SODataStoreAsync methods to CRUD against the Ultralite database, as you do against the OData backend, with the SODataOnlineStore.  (I.e.:  both the Offline and Online stores implement the SODataStoreAsync protocol).

 

Direct SQL access to the Ultralite db is not provided.  To query or modify the database, use standard OData query and $filter methods, as if you were sending requests to the backend service provider directly.

 

SODataOnlineStore Technical Cache

  • Supports READ operations, with OData query and $filter methods, with SODataStoreAsync protocol using the 'cacheResponse:' delegate methods
  • Stored like a dictionary, where 'key' :  <OData query string>, 'value' :  <last server response payload>
  • Cache is filled and over-written for a particular OData query when server response is returned
  • Does NOT support CUD operations
  • Can be configured to only return from cache, without making request to server, by switching mode from 'active' to 'passive'

 

The Technical Cache described here is ONLY relevant for the SODataOnlineStore.  It is designed to optimize the response time for frequently used GET queries.  By enabling the feature, an additional callback is activated when making the request, which returns the previous copy of the response from the service provider.  This can be immediately displayed to the end user, so that the UI is very responsive.  When the server response arrives over the network, a second callback is activated, so that the UI can be updated with the current data.

 

You can think of the Technical Cache like a dictionary:  the query string is the key, and the response payload is the value.  To retrieve content of Technical Cache, simply make a regular READ request on the SODataOnlineStore, in either active or passive mode.


You should not attempt to 'query' or 'filter' against a payload in the Technical Cache, or to modify the content.  It will be over-written by a successful server request.  It does NOT support CUD operations!

 

Use Case Example

To take an example use case:  let's say we have a Table View in our UI that is always visible to the end user.  We want the UI to be filled with data as quickly as possible, even though we are making a request to the server.  The Facebook newsfeed or other social feeds are good use cases for this type of behavior.

 

Let's say we were implementing Facebook's newsfeed, with a hypothetical OData service.  You would have a query for the main Table View with something like:

/odata/Articles('myuserid')?$top=30&$orderby=Date

We want to get the server updates ASAP, and we don't have requirements for local CUD with synchronization.  So, it makes sense to use the SODataOnlineStore.

 

This exact query is repeated, every time that the application comes to the foreground.  We can improve the refresh time for the Table View UI, by enabling the Technical Cache on SODataOnlineStore, so that the the SODataOnlineStore delegate:

  1. First, returns the cached response from the last successful server request
  2. Later, returns the response from the server, when that comes over the network
  3. The cached value for the query in the Technical Cache is overwritten by this server response

 

With this pattern, we implement a handler in the cacheResponse: delegate method to refresh the UI after step (1).  Then, we implement a similar hander in the serverResponse: delegate method to refresh the UI after step (2).  Step (3) is done automatically by the SODataOnlineStore, without developer intervention.

 

Variants

You can alter this sequence by switching from 'active' mode to 'passive mode':  in 'passive' mode, step (2) does not happen, and the SODataOnlineStore only returns the content of the Technical Cache; a server request is never made when in passive mode.  This behavior may be correct for Master Data queries, where the data is not expected to change on the server.  You may switch from 'active' to 'passive' mode, and back, at any time.

Handling DateTime from frontEnd with OData in Native Android App using SMP 3.0

$
0
0

Hi Folks,

 

I was developing native android application with SMP SDK SP06 using SMP 3.0 SP05 with the backend MS SQL Server. While developing the application, i need to insert the DateTime using ODATA in non-SAP backend (SQL Server Database) in which i struggled but finally cracked. So, thought to share my experiences through this blog which may also give you some dramatic feeling !!!

 

I knew the format of inserting DateTime in backend using OData as i had already done this in my SAP Fiori/UI5 applications. So, i directly began with the known format of inserting DateTime in backend using OData services. And the known format was 'yyyyMMdd', so i converted the date in the same format and executed the query to insert date in my table. But no record was inserted and my Http conversation with the backEnd was null and response was 400 bad request.

 

After getting this error i changed the format to 'yyyyMMddT:hh:mm:ss' and 'yyyyMMddT:hh:mm:ss.zzz'  which return me 500 internal server error. So, now i thought to test it with rest client first. I performed the PUT request with the rest client and tried to update the existing date in the table. And from the Rest Client, first i used the format 'yyyyMMddT:hh:mm:ss' to update table and it give my 400 Bad request error. After getting this error, i changed my format to 'yyyyMMdd' to update date and this time i got the status code of 204 i.e., of success. After getting the success response i checked into my table, and in table also it was successfully updated.

 

So, now again i tried to insert/update date in table with 'yyyyMMdd' format from my fronEnd but every time it was giving me the same null result and was not inserting/updating the date. Then i searched on google and by reading some of the links, i checked my data type of column which was correct i.e., DateTime.

I also tried to use Java util class and Java SQL date time methods to pass date into table but hard luck and could not succeed.

 

While searching on google, somewhere i read that OData v2.0 has some issues with the dateTime format which was resolved in OData v4.0, so i also updated the version of OData to 4.0 which also did not help me in inserting date into table. Along with this i also tried different different format like DataTime/DateTime Offset/ Calendar to insert/update date in my table but no luck.

 

Till now i have wasted my two days in searching and investigating that why the date is not getting insert/update? In the mean while i also posted a discussion on SCN How to insert datetime through ODATA in Native Android App using SMP 3.0 about my issue and i got some quick responses from JK but i could not get success. Now i started feeling pressure and getting tensed as it was very important for me to crack this issue as it was on client's top priority. So, every single minute i was thinking about the issue that why the date is not inserting or getting updated?

 

 

Then, suddenly i got a click in my mind and realized that when i was reading the data from back-end/performing the read(GET) operation, the format in which i was getting Date was unique and that foramt was 'Gregorian Calendar'.

 

So, now i converted my date into the Gregorian Calendar format and tried to insert and update the date from the front-end. And this time my Http Conversation was not null i.e., my front-End successfully communicated with my back-end. and return me the status code of '204'.  To cross check, i checked my table. Entry was successfully inserted with date time.

 

Hence, my 3 days struggle comes to an end. So thought to share my experience with you and writing this blog so that it may help the newbies like me or anyone performing the same action facing the same issue might save his precious time.

 

Below is the code which helped me.

 

// For inserting/Updating date from Front-end 
DateFormat df = new SimpleDateFormat("yyyy/MM/dd"); // Defining the Format of date
Date dateFirst = null;
try
 {
dateFirst = df.parse("20140912"); // Parsing the date into above defined format
} 
catch(Exception e) {  e.printStackTrace();
}
Calendar curCal = new GregorianCalendar();
curCal.setTime(dateFirst);
createBean.setDate(curCal);    // Stting the date to the date property of Bean Class
//Then we can get the date the property from Bean Class at the time of ScheduleCreate by writing below line of code
ODataEntityName.getProperties().put("entityName", new ODataPropertyDefaultImpl("entityName", createBean.getDate());

For reading Date from Back-end.

 

property = properties.get(date);
try {   if (property !=null) {   Calendar temp = new GregorianCalendar();  temp = (GregorianCalendar) property.getValue();   int tempDate = temp.get(Calendar.DATE);   int tempYear = temp.get(Calendar.YEAR);    tempMonth = temp.get(Calendar.MONTH);
String date = tempDate+"/"+tempMonth+"/"+tempYear; // Setting the format in which we wants to display the date
}
catch(Exception e){
e.printStackTrace();
}

 

cheers!!!

Deepak Sharma

Integration Gateway: Understanding REST data source [13]: $top

$
0
0

This  blog is about Integration Gateway in SAP Mobile Platform 3.0 (SMP).

 

When you create an OData service based on Integration Gateway (IGW), connecting to REST data source, and you deploy it to SMP, you can invoke a collection and you can apply a system query option like $select, it will work out of the box without any further scripting in your custom code.

 

But there’s no support for $top out of the box.

 

However, it can be achieved via manual coding in your script.

 

In one of my previous tutorials, I’ve explained how you can add $filter functionality to your OData service by adding the required implementation to your custom code.

In that tutorial, the procedure was to modify the URI of the REST service, before it gets called.

Thus, we added the code in the processRequestData() method.

The URI was modified in order to call the backend-REST-service with the (adapted) URI that includes the filter in the syntax that the REST-service supports.

So this procedure makes only sense if the backend REST service does support filtering.

 

In the present tutorial, we cannot apply the same procedure for the $top, because our backend-REST service doesn’t support a similar functionality.

 

Note:

if you’re using a backend-REST-service that does support a kind of $top functionality, then you would proceed similar like described in the filter-tutorial:
modify the URI in the processRequestData() method such that it complies to the requirements of the backend-REST-service.

 

In the present tutorial, we proceed differently:

we implement the $top manually in our script, since we cannot delegate to the backend-REST-service.

 

 

This tutorial is based on SMP SP08, but it should be working fine with other versions of SMP

 

Note: the code can be found attached to this blog.

 

 

 

Table of contents

 

1. Prerequisites

2. Preparation

3. Create a Service based on REST data source

   3.1. OData model

   3.2. Bind data source for query operation

   3.3. Custom code

   3.4. Test the query operation

4. Implement $top

   4.1. Overview

   4.2. Implement the $top

   4.3. Test the $top

5. Summary

6. Links

7. Appendix

 

 

 

1. Prerequisites

 

 

If you’re new to IGW and REST data source, I’d recommend that you go through some of my previous tutorials , explaining REST data source, QUERY  and READ  operation.

 

In any case, you should have checked the tutorial that uses an xml parser for manipulating the response payload:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/18/integration-gateway-understanding-rest-data-source-5-using-xml-parser

 

Please check the Links section at the bottom for more info.

 

Furthermore, you need:

 

  • Eclipse with SAP Mobile Platform Tools installed
  • SAP Mobile Platform
  • Basic knowledge about OData provisioning using the Integration Gateway component of SMP

 

 

2. Preparation

 

 

REST Service

 

For this tutorial, we’re using a REST service that is public, you only need to sign up, afterwards you can access it with your SCN user and password.

Please see the following document for details:

Getting started with the SAP Netweaver Gateway Service Consumption System

 

Finally, you should be able to access it via the following URL:

https://sapes1.sapdevcenter.com/sap/opu/rest/address/companies

 

 

Destination

 

In your SMP, you need to create an HTTP destination to the following URL:

https://sapes1.sapdevcenter.com/



destination.jpg

 

Please refer to the Appendix section for some hints about configuring the destination

 

 

 

3. Create the Service based on REST data source

 

 

3.1. OData Model


Define OData model

 

The backend REST service that we’re using provides the following sample data:

 

 

 

 

 

So for our OData model, we create the following Entity Type:

 

 

 

 

3.2. Bind data source for query operation

 

We bind the entityset to REST data source for the QUERY operation and provide the following relative URI:


/sap/opu/rest/address/companies

 

 

3.3. Custom Code

 

We create the custom code for the QUERY operation, choosing Groovy as language.

The generated file content has to be replaced with the following (note that this code works for SMP SP08)

 

 

import java.nio.charset.StandardCharsets

 

import javax.xml.parsers.DocumentBuilder

import javax.xml.parsers.DocumentBuilderFactory

import javax.xml.parsers.ParserConfigurationException

import javax.xml.transform.OutputKeys

import javax.xml.transform.Transformer

import javax.xml.transform.TransformerConfigurationException

import javax.xml.transform.TransformerException

import javax.xml.transform.TransformerFactory

import javax.xml.transform.dom.DOMSource

import javax.xml.transform.stream.StreamResult

 

import org.apache.olingo.odata2.api.uri.KeyPredicate

import org.apache.olingo.odata2.api.uri.NavigationSegment

import org.apache.olingo.odata2.api.uri.UriInfo

import org.w3c.dom.Document

import org.w3c.dom.Node

import org.w3c.dom.NodeList

import org.xml.sax.InputSource

import org.xml.sax.SAXException

 

import com.sap.gateway.ip.core.customdev.logging.ILogger

import com.sap.gateway.ip.core.customdev.logging.LogMessage

import com.sap.gateway.ip.core.customdev.util.Message

 

 

def Message processRequestData(message) {

 

       return message;

}

 

 

def Message processResponseData(message) {

    message = (Message)message;

    String bodyString = (String) message.getBody();

 

       /* CONVERT PAYLOAD */

    InputSource inputSource = new InputSource(new ByteArrayInputStream(

                                        bodyString.getBytes(StandardCharsets.UTF_8)));

    inputSource.setEncoding("UTF-8"); // REQUIRED, input has a BOM, can't be parsed

    Document document = loadXMLDoc(inputSource);

 

       // now do the refactoring: throw away useless nodes from backend payload

    document = refactorDocument(document);

 

       // convert the modified DOM back to string

    String structuredXmlString = toStringWithoutProlog(document, "UTF-8");

 

       /* FINALLY */

    message.setBody(structuredXmlString);

       return message;

}

 

 

def Document loadXMLDoc(InputSource source) {

    DocumentBuilder parser;

       try {

       parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();

    } catch (ParserConfigurationException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error while creating parser");

       returnnull;

    }

       // now parse

       try {

             return parser.parse(source);

    } catch (SAXException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error parsing source");

            returnnull;

    }

}

 

def Document refactorDocument(Document document){

       // find nodes

    Node rootElement = document.getFirstChild();

    Node asxValuesNode = rootElement.getFirstChild();

    Node proddataNode = asxValuesNode.getFirstChild();

    NodeList snwdNodeList = proddataNode.getChildNodes();

 

       //rename all nodes of the feed

    document.renameNode(proddataNode, proddataNode.getNamespaceURI(), "Companies");

       for(int i = 0; i < snwdNodeList.getLength(); i++){

       Node snwdNode = snwdNodeList.item(i);

       document.renameNode(snwdNode, snwdNode.getNamespaceURI(), "Company");

    }

 

       //replace node

    Node cloneNode = proddataNode.cloneNode(true);

    document.replaceChild(cloneNode, rootElement);

 

       return document;

}

 

/**

* Transforms the specified document into a String representation.

* Removes the xml-declaration (<?xml version="1.0" ?>)

* @param encoding should be UTF-8 in most cases

*/

def String toStringWithoutProlog(Document document, String encoding) {

       // Explicitly check this; otherwise this method returns just an XML Prolog

       if (document == null) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error: document is null.");

            returnnull;

    }

       TransformerFactory transformerFactory = TransformerFactory.newInstance();

       Transformer t = null;

       try {

       t = transformerFactory.newTransformer();

    } catch (TransformerConfigurationException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error creating Transformer");

             returnnull;

    }

 

    t.setOutputProperty(OutputKeys.METHOD, "xml");

    t.setOutputProperty(OutputKeys.INDENT, "yes");

    t.setOutputProperty(OutputKeys.ENCODING, encoding);

    t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

    t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

 

    OutputStream os = new ByteArrayOutputStream();

    OutputStreamWriter osw = null;

       try {

       osw = new OutputStreamWriter(os, encoding);

    } catch (UnsupportedEncodingException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error creating Writer");

             returnnull;

    }

    BufferedWriter bw = new BufferedWriter(osw);

       try {

       t.transform(new DOMSource(document), new StreamResult(bw));

    } catch (TransformerException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error during transformation");

            returnnull;

    }

 

       return os.toString();

}

 

 

 

3.4. Test the QUERY operation

 

Deploy the project and try it. The following request should give a valid response:


https://localhost:8083/gateway/odata/SAP/REST_DEMO_TEST;v=1/Companies

 

 

 

4. Implement $top

 

Until now, everything was preparation only.

Now that we have a working service, let’s have a closer look into the actual topic of this Blog:

Implementing the $top

 

 

4.1. Overview

 

The code the we've created up to here has already been explained in detail in the respective tutorial

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/18/integration-gateway-understanding-rest-data-source-5-using-xml-parser

 

What it does is:

 

  1. Get the payload string
  2. Parse the xml / convert to DOM
  3. Refactor the DOM
  4. Transform the DOM to String
  5. Set the converted payload string to the message object

 

 

This gives us a chance to hook in.

 

What do we want to do?

The “normal” QUERY operation provides a number of entries.

When adding the $top to the URL, we want to see only a reduced number of entries.

 

Example:

The result of the following URL is a list with 2 entries:

https://localhost:8083/gateway/odata/SAP/REST_DEMO_TEST;v=1/Companies?$top=2

These 2 entries are the first 2 entries of the “normal” QUERY.

 

How to implement it?

After step "3. Refactor the DOM", we have a well-structured list with all entries.

From this list we have to take the first 2 and remove all the rest.

 

The remaining steps 4 and 5 can remain untouched.

 

So our new script that supports $top will have the following sequence:

 

  1. Get the payload string
  2. Parse the xml / convert to DOM
  3. Refactor the DOM
  4. Apply $top
  5. Transform the DOM to String
  6. Set the converted payload string to the message object

 

 

4.2. Implement the $top

 

We create an additional method which will contain the implementation for handling the $top

 

First we have to retrieve the value of the $top expression that has been provided by the user.

If the user has invoked the following URL

https://localhost:8083/gateway/odata/SAP/REST_DEMO_TEST;v=1/Companies?$top=2

Then we need the value ‘2’

 

This is retrieved from the UriInfo object.

The UriInfo object can be accessed from the headers

The UriInfo instance can be asked for the $top number

It returns null if the user doesn’t specify the $top option.

This has to be considered in our code


UriInfo uriInfo = (UriInfo) message.getHeaders().get("UriInfo");

Integer topOption = uriInfo.getTop();

if(topOption == null){

       return;

}

int topNumber = topOption.intValue();

 

 

Now that we know how many entries (starting from the beginning) have to be displayed, we need to adapt the payload to it.

Our method receives the Document instance which is the DOM representation of the response of the backend-REST-service.

Since it is already refactored, it contains the full payload in the following structure:

 

 

<Companies>

    <Company>

          <ID>“1”</ID>

          <NAME>“SAP”</NAME>

          <STREET>“Dietmar-Hopp-Allee”</STREET>

           …

    </Company>

    <Company>

          <ID>“2”</ID>

          <NAME>“<companyName>”</NAME>

          <STREET>“<companyStreet>”</STREET>

           …

    </Company>

    …

</Companies>



The first node is the parent node for a list of (Company-) nodes that has to be modified according to the specified $top value.

So what we have to do is to delete all child nodes, but leave the first ones.

One possible solution is:

 

Check if the number of existing entries is higher the the $top

Delete the last node

Check again

Delete the last node

And so on

 

while(entitySetNode.getChildNodes().getLength() > topNumber){

       Node lastCompany = entitySetNode.getLastChild();

       entitySetNode.removeChild(lastCompany);

}



Furthermore, some details to consider:

 

$top=-1

What if the value that the user has given in the $top expression is invalid?

This is handled by the Olingo library, we don’t need to take care.

 

$top=0

This is a valid number, we return an empty list

Instead of deleting each child node one by one, we replace the current root node with an empty one

 

$top=9999

If the user specifies a number that is too high, then we can just return the full list.

It is up to the service implementor to decide if he wants to throw an exception for such a case.

 

 

And this is how our helper method looks like:

 

 

 

def applyTop(Document document, Message message){

    UriInfo uriInfo = (UriInfo) message.getHeaders().get("UriInfo");

    Integer topOption = uriInfo.getTop();

       if(topOption == null){

             return;

    }

 

       int topNumber = topOption.intValue();

 

    Node entitySetNode = document.getFirstChild();

    NodeList entities = entitySetNode.getChildNodes();

 

       if(topNumber > entities.getLength()){

             return;

    }elseif (topNumber == 0){

       document.replaceChild(document.createElement("Companies"), entitySetNode);

    }else{

            while(entitySetNode.getChildNodes().getLength() > topNumber){

            Node lastCompany = entitySetNode.getLastChild();

            entitySetNode.removeChild(lastCompany);

       }

    }

}




At the end, we have to call our new applyTop()-method.

We invoke it in the processRequestData(), after we’ve refactored the payload:

 

 


 

def Message processResponseData(message) {

    message = (Message)message;

    String bodyString = (String) message.getBody();

 

       /* CONVERT PAYLOAD */

    InputSource inputSource = new InputSource(new ByteArrayInputStream(

                                        bodyString.getBytes(StandardCharsets.UTF_8)));

    inputSource.setEncoding("UTF-8"); // REQUIRED, input has BOM, can't be parsed

    Document document = loadXMLDoc(inputSource);

 

       // now do the refactoring: throw away useless nodes from backend payload

    document = refactorDocument(document);

 

       // handle system query options

    applyTop(document, message);

 

       // convert the modified DOM back to string

    String structuredXmlString = toStringWithoutProlog(document, "UTF-8");

 

       /* FINALLY */

    message.setBody(structuredXmlString);

       return message;

}



4.3. Test the $top

 

Deploy the project and try the following URLS:

 


 

 


5. Summary

 

 

In this tutorial we’ve learned how to implement the system query option $top.

Since the backend REST service doesn’t support a kind of $top functionality, we had to go for the approach to fetch the full data from the backend and then remove the unneeded entries in our script.

From performance perspective, this is not ideal, but if the backend REST service doesn’t support it, it is the only way to achieve it.

 

 

 

6. Links

 

Installing SMP Toolkit:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/08/22/how-to-install-sap-mobile-platform-tools-for-integration-gateway-in-eclipse-kepler

 

Tutorial for OData provisioning in SMP:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/06/10/creating-an-odata-service-based-on-sap-gateway-soap-jdbc-and-jpa-data-sources-ba

 

Preparing Eclipse for Groovy scripting: http://scn.sap.com/docs/DOC-61719

 

Introduction in REST datasource part 1: Understanding the return structure in xml

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/10/understanding-rest-data-source-in-integration-gateway-1-query-very-simplified

 

Introduction in REST data source part 3: Implementing the QUERY operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/12/integration-gateway-understanding-rest-data-source-3-query--xml-standard

 

How to use XML parser to convert the payload of the REST service

Integration Gateway: Understanding REST data source [5]: using XML parser

 

Implement $filter in your OData service, based on REST data source

Integration Gateway: Understanding REST data source [4]: Filtering

 

Overview of all REST blogs

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/04/08/integration-gateway-rest-data-source-overview-of-blogs

 

The official documentation: http://help.sap.com/mobile-platform/




7. Appendix

 

Configuring the destination

 

 

The URL for the Destination:

https://sapes1.sapdevcenter.com

Since the URL is HTTPS, you need to download the certificate and import it into your SMP keyStore.

 

Test Connection:

For this Destination, it isn’t possible to do a “Test Connection”, as the server doesn’t send a valid response for the configured URL

As a workaround, you can proceed as follows:

Create a second destination, which is only used to test if the target host can be reached.

This second destination points to a URL that actually can send a valid response.

For example, enter the following URL as destination URL:

 

https://sapes1.sapdevcenter.com/sap/opu/rest/address/companies

 

As such, you use this second destination to do the “Test Connection”.

If this succeeds, then the first destination should be fine as well.

This first destination will be used to configure our OData service, that we create in this tutorial.

 

Proxy:

If you get an error message on connection test, you might consider the following:

You might need to enter proxy settings in your SMP:

https://localhost:8083/Admin/ -> Settings-> System

Note that you might need to restart the SMP server after changing the proxy settings.

 

 




Create Agentry applications using oData

$
0
0

Below are the steps to create Agentry applications using oData.

 

 

  1. Create Agentry Application in SMP

1.png

 

   2. Set as authentication as ‘No Authentication Challenge’

   3. Create Agentry Project in eclipse.

   4. Define System connection

 

2.png

   5. From Data Source Explorer create connection profile

 

5.png


   6. Enter oData URL , credentials and click finish



6.png

   7. Right click and connect

 

 

7.png

 

   8. Right click on Entity LIST and run Object Wizard for oData using Agentry Connector Studio.

 

8.png

 

 

 

81.png

  

   9. Uncheck boxes for Add , Edit & delete

 

9.png

 

  10. Check on GetStep

 

10.png


  11.After Finish, Object and Steps are automatically created

 

 

11.png

 

  12.Define Collection & read step

 

12.png

 

  13.Request & response created automatically

 

13.png

 

 

  14. Define server exchange steps in fetches

 

14.png


  15. Define screen

 


15.png

  16. Publish the project


16.png

 

  17. Check the deployment.

 

 

 

17.png

Registering Kapsel/Hybrid SAPUI5 Android App on SMP3.0 Using Cordova SQLLite Plugin(WebSQL DB)

$
0
0

Hi Folks,

 

Since last few days I was working on one Hybrid App development for Android platform with SMP 3.0 using SAPUI5 as a UI framework. I followed the great blog Getting Started with Kapsel - Part 1 by Daniel Van Leeuwen which helped me alot in making a kick start to my development with SMP3.0.

 

By following each and every step of the blog i was able to register my application on SMP3.0 and was able to call my SAPUI5 views(UI). But one thing which i noticed was that even after performing the registration process, whenever i reopen the app after closing, then also it was performing the registration process from the initial step again every time even if it is registered. So, after analyzing the code, i found that it was happening because applicationContext variable was initialized null which was global in scope and which needs to be global as what i think. So, that we can access its parameter across the application (like accessing the back-end point URL or ConnectionId).

 

So, every time we reopen the app, applicationContext variable is set to null and on clicking the register button it starts registering the app again, but every time the application connection id which i was getting was same and which was very obvious.

 

Below is my index.html file which i was using.

 

<!DOCTYPE html><html>    <head>        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>           <title>Kapsel Android app Logon on SMP 3.0 with UI5</title>         //- Loading from the publicly available server.  Only load the mobile lib "sap.m" and the "sap_bluecrystal" theme -->        <script src="https://sapui5.netweaver.ondemand.com/resources/sap-ui-core.js"        id="sap-ui-bootstrap"        data-sap-ui-libs="sap.m"        data-sap-ui-theme="sap_bluecrystal"        data-sap-ui-xx-bindingSyntax="complex"        data-sap-ui-resourceroots='{            "com.app": "./"        }'>        </script>           <script type="text/javascript" src="cordova.js"></script>
//- Code for registering the Application on SMP3.0-->           <script type="text/javascript">              applicationContext = null;                 window.onerror = onError;            function onError(msg, url, line) {                var idx = url.lastIndexOf("/");                var file = "unknown";                if (idx > -1) {                    file = url.substring(idx + 1);                }                alert("An error occurred in " + file + " (at line # " + line + "): " + msg);                return false; //suppressErrorAlert;         }                    function logonUnregisterSuccessCallback(result) {                alert("Successfully Unregistered");                applicationContext = null;                //sap.Logon.unlock(function () {},function (error) {});         }            /* Function called on Success */            function logonSuccessCallback(result) {                alert("Successfully Registered");                applicationContext = result;                    var oShell = new sap.m.Shell({                                           app : new sap.ui.core.ComponentContainer({name:"com.app"}),                                               });                   oShell.placeAt("content");    }    function register() {    alert("Application Context"+applicationContext);          if (applicationContext) {                    alert("Already Registered");                    return;                }                var appId = "com.sap.app"; // Change this to app id on server                           // Optional initial connection context                var context = {                    "serverHost": "SMP Server URL", //Place your SMP 3.0 server name here                    "https": "false",                    "serverPort": "8080",                    "user": "SMP UserId", //Since the application com.mycompany.logon uses the No Authentication Challenge provider, any user name and password will work.                    "password": "SMP Password", //once set can be changed by calling sap.Logon.changePassword()                    "communicatorId": "REST"                };                           sap.Logon.init(logonSuccessCallback, logonErrorCallback, appId, context);                       //sap.Logon.unlock(logonSuccessCallback, errorCallback);  //No need to call this as init shows the register/unlock screen when called.            }            function logonErrorCallback(error) {   //this method is called if the user cancels the registration.                console.log("An error occurred:  " + JSON.stringify(error));                if (device.platform == "Android") {  //Not supported on iOS                    navigator.app.exitApp();                }            }            function unRegister() {                if (applicationContext == null) {                    alert("Not Registered");                    return;                }                sap.Logon.core.deleteRegistration(logonUnregisterSuccessCallback, unRegisterErrorCallback);            }            function unRegisterErrorCallback(error) {                console.log("An error occurred:  " + JSON.stringify(error));            }           </script>    </head>    <body class="sapUiBody" id="content"/>        <button id="register" onclick="register()">Register</button>        <button id="unregister" onclick="unRegister()">Unregister</button>       </body></html>

But what i think and have also observed in my past native app development experience using SMP3.0, that once the application is registered on SMP3.0 it should not go for registration process again.

So, now i thought to implement the following 2 points in my app which are:-

1) Once the application is registered, registration process should not be repeated unless n until it is uninstalled or unregistered.

2) The registration process should be done automatically without any user interaction when the app is runned very first time after installation.

 

So, to implement above two functionality i thought to do it with WebSQL database which uses the SQL Lite DB of devices.

 

Below is the step by step process to do the same.

 

Prerequisites

1) Set up the development enviroment

2) Have completed all steps till Getting Started with Kapsel - Part 2 -- Logon


Step 1 :- Run the below command using the cmd

1) cd C:\Kapsel_Projects\LognDemo

2) cordova plugin add https://github.com/brodysoft/Cordova-SQLitePlugin


then at last 3) cordova -d prepare


Step 2 :- Create a new File as "register.js" for writing the code to register our application on SMP 3.0


var db;
applicationContext = null;
window.onerror = onError;
function onError(msg, url, line) {    var idx = url.lastIndexOf("/");    var file = "unknown";    if (idx > -1) {        file = url.substring(idx + 1);    }    alert("An error occurred in " + file + " (at line # " + line + "): " + msg);    return false; //suppressErrorAlert;
}  
function logonUnregisterSuccessCallback(result) {    alert("Successfully Unregistered");    applicationContext = null;    //sap.Logon.unlock(function () {},function (error) {});
}
function register() {        if (applicationContext) {  // if applicationContext exists i.e.,app is already registered call the Component of SAPUI5 already registered                  alert("Already Registered");                                 var oShell =new sap.m.Shell({app : new sap.ui.core.ComponentContainer({name:"com.app"}),          });          oShell.placeAt("content");              }        else{           // Else initialize the LOGON screen to register the application on SMP              var appId = "com.sap.app"; // Change this according to your app id on SMP server                          // Optional initial connection context              var context = {                  "serverHost": "SMP SERVER URL", // your SMP 3.0 server name/ IP address                  "https": "false",                  "serverPort": "8080",                  "user": "UserId", //SMP UserId                  "password": "password", //once set can be changed by calling sap.Logon.changePassword()                  "communicatorId": "REST"              };              sap.Logon.init(logonSuccessCallback, logonErrorCallback, appId, context);                      //sap.Logon.unlock(logonSuccessCallback, errorCallback);  //No need to call this as init shows the register/unlock screen when called.          }
}
function logonSuccessCallback(result) {    alert("Successfully Registered");    applicationContext = result;    // If application is registered successfully then insert the applicationConnection ID into APPDATA table and load the Component of SAPUI5    insert();  
var oShell =new sap.m.Shell({app : new sap.ui.core.ComponentContainer({name:"com.app"}),          });          oShell.placeAt("content");
}
function logonErrorCallback(error) {   //this method is called if the user cancels the registration.    console.log("An error occurred:  " + JSON.stringify(error));    if (device.platform == "Android") {  //Not supported on iOS        navigator.app.exitApp();    }
}
function unRegister() {    if (applicationContext == null) {        alert("Not Registered");        return;    }    sap.Logon.core.deleteRegistration(logonUnregisterSuccessCallback, unRegisterErrorCallback);
}
function unRegisterErrorCallback(error) {    console.log("An error occurred:  " + JSON.stringify(error));
}
// Function which is initially called when the cordova device is ready
function onDeviceReady() {         db = window.sqlitePlugin.openDatabase({name:"Database",version: "1.0", location:-1});         db.transaction(function(tx) {       
// Creating table to store the applicationConnection Id          tx.executeSql('CREATE TABLE IF NOT EXISTS APPDATA (id integer primary key, appId text)');
// Reading the table to get the application connection ID          tx.executeSql("SELECT appId from APPDATA;", [], function(tx, res) {       
// Calculating the number of rows in table          var len = res.rows.length;
// Checking whether application ID exists or not                      if(len>0){                           applicationContext = res.rows.item(0).appId;                         }
// Calling the register function to register the app               register();            },null);                       });      }
// Function to read the values from the SQLLite DB
function read(){  db.transaction(function(tx) {    tx.executeSql("SELECT appId from APPDATA;", [], function(tx, res) {        alert("tx"+res);          console.log("res.rows.length: " + res.rows.length + " -- should be 1");          appId = res.rows.item(0).appId;          alert("appId-->"+appId);       });        });
}
// Function to insert the data in SQLLite DB
function insert(){ // inserting the applicationConnection ID in APPDATA table  db.transaction(function(tx) {  tx.executeSql('CREATE TABLE IF NOT EXISTS APPDATA (id integer primary key, appId text)');  tx.executeSql("INSERT INTO APPDATA (appId) VALUES (?)", [applicationContext.applicationConnectionId], function(tx, res) {
 }, function(e) {          console.log("ERROR: " + e.message);        });
});
}

Step 3 :- Call the "register.js" and onDeviceReady function on index.html as below

 

<!DOCTYPE html><html>    <head>        <meta charset="utf-8" />        <meta name="format-detection" content="telephone=no" />        <meta name="msapplication-tap-highlight" content="no" />               <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />        <link rel="stylesheet" type="text/css" href="css/index.css" />        <link rel="stylesheet" type="text/css" href="util/style.css">        <script type="text/javascript" src="js/register.js"></script>                             <title>Kapsel Logon</title>               <script src="https://sapui5.netweaver.ondemand.com/resources/sap-ui-core.js"         id="sap-ui-bootstrap"        data-sap-ui-libs="sap.m"        data-sap-ui-theme="sap_bluecrystal"        data-sap-ui-xx-bindingSyntax="complex"        data-sap-ui-resourceroots='{            "com.app": "./"        }'>        </script>                // Calling the onDeviceReady function to initiate the registeration of Application on SMP 3.0-->        <script>        document.addEventListener("deviceready", onDeviceReady, false);       </script>            </head>    <body class="sapUiBody" id="content">               <script type="text/javascript" src="cordova.js"></script>    </body></html>

 

Step 4 :- Run the app using your ADT as your android application.


Note:- Here i have used the SAPUI5 resoures from the public server which take some time while launching the app and my also give the "Cordova timeout error".

 

In this way i implemented the above two functionalities. So, thought to share with you all which may help someone who want to try the same. Now, the registration will happen automatically if the application is not registered.


thanks & cheers

Deepak Sharma


Handling Partial Results with the OData SDK (Android)

$
0
0

This blog covers how to use the SMP OData SDK to handle OData services that limit the number of items sent in a response. It will explain how Android native apps can deal with these partial responses and what methods are required to get the rest of the results.

 

If mobile apps don’t restrict the amount of data they requests from the server, your OData services will be overloaded and their response time will be impacted. That’s why some services implement the server-side paging, also called “pagination”, provided by the OData protocol. More details about how to implement OData services using pagination can be found here: Mobile Application Development Platform - Integration

 

For simplicity purposes, this blog will use the Northwind OData service as an example: http://services.odata.org/V2/Northwind/Northwind.svc/

 

The Northwind example contains a collection called “Customers”:

skiptoken1.jpg

 

When a client app sends a GET request to get all customers, the service returns a partial response of 20 customers. At the end of the response, you will find an URL that includes $skiptoken, which allows the server to return the next set of customers.

<link rel="next" href="http://services.odata.org/V2/Northwind/Northwind.svc/Customers?$skiptoken='ERNSH'" />

skiptoken2.jpg

 

Let’s assume you are using HCPms and that the Android client app has already registered with the mobile services. The OData SDK provides two ways to access the data: online or offline store.

 

The offline store can be used to develop applications that require infrequent updates of back-end data and the connectivity may become unreliable. When the client app open an offline store that points to an OData service that uses pagination, the mobile services (in SMP 3.0 on premise or HCPms) does all the work automatically. Assuming there are 100 rows in the “Customers” entityset and the $skiptoken is set for 20; it automatically makes 5 calls (each call brings back 20 rows based on $skiptoken) to the backend to get all the 100 rows and populates the database. Once the offline store is opened, the client app will have access to all the data locally in the device.

 

The online store, on the other hands,is more suitable to develop applications that require up to date back-end data and have reliable connectivity. In this case, it’s up to the client app to handle the partial results. In a nutshell, the client app must check whether the getNextResourcePath returns an URL. If getNextResourcePath is null, it means there’s no more customers to fetch. Otherwise, the client app can continue to send GET request to the nextResourcePath URL to obtain the complete list of customers.

 

Synchronous

if you want to consolidate the partial results using synchronous calls, you can use the following code snippet as a starting point

 

Synchronous GET request
try {
    String nextResourcePath = collection;
   
while (nextResourcePath != null) {
        ODataRequestParamSingle request =
new ODataRequestParamSingleDefaultImpl();
        request.setMode(ODataRequestParamSingle.Mode.
Read);
        request.setResourcePath(collection);
        request.setCustomTag(customTag);

        ODataResponseSingle response = (ODataResponseSingle) store.executeRequest(request);
       
//Get the response payload
       
ODataEntitySet feed = (ODataEntitySet) response.getPayload();
        List<ODataEntity> entities = feed.getEntities();
       
// Store customers
       
customers.addAll(entities);
        nextResourcePath = feed.getNextResourcePath();
    }
}
catch (Exception e) {
   
throw new OnlineGenericException(e);
}

 

Asynchronous:

Another option is to display partial results and allow the user to control when it wants to download the next set of customers. In this example, the client app displayed the first 20 customers and provided with an icon to allow the user to get the next 20 items on the list:

skiptoken_screenshots.jpg

You can use the following code snippet as a starting point

1. Send an asynchronous GET request to get the list of customers.

 

Asynchronous GET request

/**
* Send GET request to the offline store
* @param
collection URL of the collection, for example “Customers”
* @param
customTag description of the request
* @param
requestListener request listener
* @throws OnlineGenericException
*/
public static void sendGETRequest(String collection, String customTag,

         ODataRequestListener requestListener) throws OnlineGenericException {

     OnlineStoreListener openListener = OnlineStoreListener.getInstance();
     OnlineODataStore store = openListener.getStore();

    
if (store!=null){
       
try {
           ODataRequestParamSingle request =
new ODataRequestParamSingleDefaultImpl();
           request.setMode(ODataRequestParamSingle.Mode.
Read);
           request.setResourcePath(collection);
           request.setCustomTag(customTag);

           store.scheduleRequest(request, requestListener);
        }
catch (Exception e) {
          
throw new OnlineGenericException(e);
        }
     }
}

 

2. Receive the partial response through a class that implements the ODataRequestListener interface. The ODataRequestListener interface defined several methods that are called upon reaching each stage of the request.

    1. requestStarted: Request has been started.
    2. requestServerResponse: Server response has been received
    3. requestFailed: OData request has been failed.
    4. requestFinished: OData request has been finished

 

In the requestServerResponse, we can store the list of customers (The first 20 customers) and the link to get the next set of customers by using getNextResourcePath method.

String nextURL = ((ODataEntitySet) response.getPayload()).getNextResourcePath();

 

Receive partial response - code snippet

@Override
public void requestServerResponse(ODataRequestExecution oDataRequestExecution) {
Log.d(
TAG, "requestServerResponse");
    
if (oDataRequestExecution!=null && oDataRequestExecution.getResponse() !=null) {
        String customTag = oDataRequestExecution.getRequest().getCustomTag();
       
//Parse the response
       
ODataResponseSingle response = (ODataResponseSingle) oDataRequestExecution.getResponse();

        //Get the http status code
       
Map<ODataResponse.Headers, String> headerMap = response.getHeaders();
        String code = headerMap.get(ODataResponse.Headers.
Code);
        Log.d(
TAG, "requestServerResponse - status code " + code);

        CustomerEntityCollection customerCollection =
CustomerEntityCollection.getInstance();


//Get the response payload
ODataEntitySet feed = (ODataEntitySet) response.getPayload();
//Store the URL required to get the next set of customers
customerCollection.setNextResourcePath(feed.getNextResourcePath());

//Get the list of ODataEntity
List<ODataEntity> entities = feed.getEntities();
customerCollection.setCustomersCache(entities);

notifySuccessToListener(
"success");

} else {
notifyErrorToListener(
new OnlineGenericException("no response"));

      }

}

 

Once the user click on the next icon, the client app will send a GET request to the nextResourcePath URL. When the nextResourcePath URL is null, you can hide the next icon or send a GET request to the customer collection to start over.

 

Assumptions for this exercise

 

Assuming you are using the HCPms trial account, you can use the following screenshot to configure your application configuration, which points to the Northwind OData service:

skiptoken3.jpg

Remember that your Android client app needs to onboard with HCPms before accessing any data. Here are some guidelines to MAF logon component:

How To... Setup MAF resources in Android Studio

Customizing MAF Logon Component in Android

Running Android apps on HCPms

 

Cheers

Claudia

Media Support in Native OData SDK ( iOS)

$
0
0

Earlier versions of the SMP SDK, supported OData related operations only for OData entities, feeds, properties, links, but not for downloading (GET) of resources such as media files. With the release of SDK SP 08, the SDK now supports media resource handling, for example creating and downloading of image files.  This blog walks you through the  relevant  APIs for this functionality, both for online and offline stores.

 

Let’s take a quick look at media element basics in Odata before delving into the details of the SDK. Any media element ( e.g. document, image or a video) consists of 2 related resources : the Media Link Entry (MLE) containing the structured data that describes the BLOB and the Media Resource (MR) that is the BLOB itself. For example, ‘ImageCollection’ can be an Odata feed whose entries  describe the structured data of the images  i.e. they are of type Media Link Entry and will link to the actual media resource.



In the service metadata document for service containing a feed such as the ImageCollection you will find:

<EntityType Name=“ImageCollection" m:HasStream="true">


The attribute  ‘HasStream’  used on an element to state that the Entity Type is describing a Media Link Entry in the associated OData service.


Consider a sample of an MLE entry below in an Odata feed.  The actual image (MR) is in the 'edit-media' link's href (ImageCollection('125')/$value). So, the MR is accessed through the MLE.


<entry>

<id>http://<server>:<port>/odata/ImageCollection('125')</id>

<titletype="text" />

<updated>2015-08-26T17:26:34Z</updated>

<author>

<name />

</author>

<linkrel="edit"title=“ImageCollection"href=“ImageCollection('125')" />

<linkrel="edit-media"type="application/octet-stream"href=“ImageCollection('125')/$value" />

<categoryterm="RMTSAMPLEFLIGHT.ImageCollection"scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />

<contenttype="application/octet-stream"src=“ImageCollection('125')/$value" />

<m:properties>

<d:mediaid>125</d:mediaid>

<d:MEDIANAME>MEDIA_125</d:MEDIANAME>

<d:CURRCODEm:null="true" />

<d:URLm:null="true" />

</m:properties>

</entry>

 

 

Throughout the blog , we will interchangeably refer to the MLE as media entities and the MR as the media stream.

 

 

Retrieving Media Elements when operating an Online Store


Now let us take  look at how the SDK APIs to read such media elements, first with the online store. Consider  a call from our  application to read  ‘ImageCollection’. The read request is executed  and we retrieve the entries in the requestServerResponse: delegate method. But this is only the set of MLE entries and not the MR associated with each entry. To retrieve the media stream, the SDK introduces a new method (in SODataStoreAsync.h ) :


- (id<SODataDownloadMediaExecution>) scheduleMediaDownload:(NSURL*)url delegate:(id<SODataDownloadMediaDelegate>)delegate;


There are also delegate methods provided by SDK to handle the download ( in SODataDownloadMediaDelegate.h). These delegates are very similar to the SODataRequestDelegate methods.


Media Stream download request example:

 

 

  NSURL* mediaURL = [entitymediaLink]; // entity whose MR we need to retrieve

[odataStorescheduleMediaDownload:mediaURL delegate:dataController]; // dataController implements SODataDownloadMediaDelegates

 

 

Delegates implementation example for Media Stream download:

 

- (void) mediaDownloadServerResponse:(id<SODataDownloadMediaExecution>)requestExecution result:(id<SODataDownloadMediaResult>)result

{

    SODataDownloadMediaResultDefault* mediaRes = result;

    NSInputStream* stream = [mediaRes inputStream];

   

    NSMutableData* data = [[NSMutableDataalloc] init];

    NSInteger len = 0;

    [stream open];

    while ([stream hasBytesAvailable]) {

        uint8_t buffer[25*1024];

        len = [(NSInputStream *)stream read:buffer maxLength:1024];

       

        if(len) {

            [data appendBytes: (constvoid *)buffer length:len];

        }

    }

    [stream close];

    UIImage *image = [[UIImagealloc] initWithData:data];

}

 

- (void) mediaDownloadFailed:(id<SODataDownloadMediaExecution>)requestExecution error:(NSError*)error

{

    NSLog(@" Media Download failed");

}

 

- (void) mediaDownloadStarted:(id<SODataDownloadMediaExecution>)requestExecution

{

    NSLog(@" Media Download started");

}

 

- (void) mediaDownloadCacheResponse:(id<SODataDownloadMediaExecution>)requestExecution result:(id<SODataDownloadMediaResult>)result

{

   NSLog(@" In Media Cache Response");

}

 

- (void) mediaDownloadFinished:(id<SODataDownloadMediaExecution>)requestExecution

{

    NSLog(@" Media Download finished");

}

Retrieving Media Elements when operating an Offline Store

 

When using an offline store, you can control which media elements to store in the offline store in one of the following ways:

 

  • In a defining request, specify the media resources to download.

 

- (void) addDefiningRequestWithName:(NSString*)name url:(NSString*) url retrieveStreams:(bool) retrieveStreams;

 

When you create a defining request, use the offline OData API to set a flag that triggers the download of media elements.When an offline store is opened for the first time, the media stream for all media entities that come from defining requests where 'retrieveStreams'is  true are downloaded to application on the device. Subsequently,when we want to access the stream belonging to an  entity (e.g to display it on a screen), we will continue to use sheduleMediaDownload: and the SODataDownloadMediaDelegates as with the online , but in this case the data will be retrieved from the local offline store in the device. When the app makes a refresh call ( the offline API used to  refresh data  with changes from the server), media streams from such defining requests are updated only if the media entity has changed.

 

 

  • Once a store is open, register a request to retrieve a particular media stream.


- (void) registerStreamRequestWithName:(NSString*)requestName resourcePath:(NSString*)resourcePath error:(NSError**) error; // register the stream

- (void) unregisterStreamRequestWithName:(NSString*)requestName error:(NSError**) error; // unregister the stream

 

For cases where the OData server has many media elements and it would be too much data to download it all to a device, we allow applications to select only specific media resources to download.  In this case, you would not set the retrieveStreams to falsein the defining requests, but would instead register a specific media stream request.Once a media resource request is registered, the first refresh call to the server  downloads the media stream, whether or not the associated media entity has changed. Subsequent refreshes update a media stream only if the associated media entity has been updated.

 

Here is a code snippet :

 

-(void)retrieveImage

{

[odataOfflineStore registerStreamRequestWithName:@"resourcereq" resourcePath:entity.resourcePath error:&err]; //The registered request must be the read link of the media entity (not the media stream).

[odataOfflineStore scheduleRefreshWithDelegate:offlineRefreshDelegate];

}

 

Create, Update and Delete operations on Media Resources

 

For a  POST operation, you provide the media data as the payload. This will create and return a new media entity that points to the data your provided in the payload.

 

-(void) addResource:(NSString *)imgName

{

      

    NSString* filePath = [[NSBundlemainBundle] pathForResource:imgName ofType:@"jpeg"]; //the image is from a file

    id<SODataPayload> imageToUpload = [[SODataUploadMediaDefaultalloc] initWithFileAtPath:filePath contentType:@"image/jpeg"];

    SODataRequestParamSingleDefault *reqMedia = [[SODataRequestParamSingleDefaultalloc] initWithMode:SODataRequestModeCreate

    resourcePath:[NSStringstringWithFormat:@“ImageCollection"]];

    reqMedia.payload = imageToUpload;

    [odataStorescheduleRequest:reqMedia delegate:requestDelegate];

}

 

 

For updating the stream, you do a PUT against the edit_media metadata property of the media entity and provide the media resource as the payload.

 

- (void)updateResource: (NSString *)updateImageName{

   

   NSString* filePath = [[NSBundlemainBundle] pathForResource:updateImageName ofType:@"jpeg"];

    id<SODataPayload> imageToUpload = [[SODataUploadMediaDefaultalloc] initWithFileAtPath:filePath contentType:@"image/jpeg"];

    SODataRequestParamSingleDefault *reqMedia = [[SODataRequestParamSingleDefaultalloc] initWithMode:SODataRequestModeUpdate

   resourcePath:[NSStringstringWithFormat:@"%@/$value", entity.resourcePath]];

    reqMedia.payload = imageToUpload;

    [odataStorescheduleRequest:reqMedia delegate:requestDelegate];

}

 

 

When you do a DELETE you are deleting both the media entity and the media data at the same time. i.e  The call below to delete the Entity ( MLE ) deletes the MR as well.

[odataStorescheduleDeleteEntity:entity delegate:requestDelegateoptions:nil];

 

 

Hope this was a short but useful introduction to media resource handling in the SMP SDK and gets you started with building iOS native apps with media support. Good Luck !

Different rewrite modes require different service root URLs in SMP and HCPms

$
0
0

One customer was having a big problem with duplication of records (Violation of PRIMARY KEY constraint), when trying to add any purchase request via their HCPms offline native app for Android. For some reason, the device was not receiving an acknowledge from the backend saying that the flushed data had been committed. So, when another operation in the offline store was performed and a flush was called again, the duplication error happened again, because, for the offline store, the first purchase request had not been committed to the backend yet.


The reason behind this issue was incorrect understanding of how to properly setup and code for offline OData. The documentation provided was somewhat vague and has been improved due to this finding. For now, you can refer to the HCPms documentation which is very well explained: SAP HANA Cloud Platform Mobile Services. Please, remember that some other error would also have happened for an online app.


 

Different rewrite modes require different service root URLs in SMP and HCPms. The customer had set the Rewrite Mode field of the connection to the backend configuration to the Rewrite URL on Backend value, but had set the serviceRoot option of the OData Offline Store Options as if they had set the Rewrite Mode field of the connection to the backend configuration to the Rewrite URL on HANA Mobile Server value. Remember that the serviceRoot property of an offline store options structure identifies the root of an OData service.

 

 

OK, so let's understand what is the correct value for each Rewrite Mode option:

 



  • No Rewriting: "request and response messages are not modified; SAP HANA Cloud Platform mobile services passes messages directly between clients and the back end".

 

NOTE: "To enable applications using an external back end to run offline, you must select one of the rewrite options."


In Java code, the store options would look like the following for the Rewrite URL on Backend option:


ODataOfflineStoreOptions options = new ODataOfflineStoreOptions();
...
options.host = "myserver";
options.port = "8080";
options.serviceRoot = "http://myserver:8080/sap/opu/odata/sap/service";
...

 

In Java code, the store options would look like the following for the Rewrite URL on HANA Mobile Server option:


ODataOfflineStoreOptions options = new ODataOfflineStoreOptions();

...

options.host = "myserver";

options.port = "8080";

options.serviceRoot = "http://myserver:8080/<ApplicationID>";

...

Weak ephemeral Diffie-Hellman public key

$
0
0

Problem: Chrome and Firefox recently updated and suddenly stopped allowing connections to your SMP3 Admin and possibly your applications and is giving you the error Server has a weak ephemeral Diffie-Hellman public key

 

 

WeakDHkey.png

 

This is an attempt by the browsers to protect you from connecting to a Server that is using outdated cipher settings which could lead to a recently published SSL vulnerability "logjam".

 

The ciphers being used by SMP3 SP08 and prior server versions are defaulting to obsolete choices.  I believe this is being updated for the SMP3 SP09 release.  However, in the meantime you can make a similar change to your server to update the ciphers using the following procedure.

 

The quickest fix is to just remove the TLS_DHE_RSA_WITH_AES_128_CBC_SHA from the default cihpers list.  This removes the one google is complaining about.  You can also just update the ciphers as indicated below to add support for some of the newer cihphers.  This won't hurt anything but I also don't know which ones are actually used or support by the browsers.

 

Solution:

  • Stop the SMP3 server
  • Edit the Server\confg_master\org.eclipse.gemini.web.tomcat\default-server.xml file
  • Find the ciphers line in each of the following Connector tags and replace the value with the ciphers below.
    • Connector smpConnectorName="oneWaySSL"
    • Connector smpConnectorName="AdminSSL"
    • Connector smpConnectorName="mutualSSL"

 

    • ciphers="TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA"
  • Save and restart the SMP3 server.  Now connections from Chrome and Firefox should no longer give that error.

 

The key is to remove the TLS_DHE_* ciphers.  This list contains probably more options that you will need but I leave it to you to determine which ones you want to support.

 

For Agentry clients be sure to test each device you will be using BEFORE making this change in production.  If your device does not support the newer ciphers it will probably fail to connect and you may need to either update your device or re-implement the obsolete cipher.

Integration Gateway: Understanding REST data source [14]: $skip

$
0
0

This  blog is about Integration Gateway (IGW) in SAP Mobile Platform 3.0 (SMP).

 

It shows how to implement the $skip capability in an OData service based on Integration Gateway.

 

In my previous Blog, I’ve already described how to implement the system query option $top.

Everything described there, motivation, prerequisites,  preparation etc, is valid here as well.

So let us skip all introductory text and go directly to the implementation.

 

 

 

Implement $skip

 

 

What do we want to achieve?

 

With the query option $skip, the user of an OData service can specify the number of entries that should be ignored at the beginning of a collection.

So if a user specifies $skip=n then our OData service has to return the list of entries starting at position n+1

This is specified by the OData document that can be found here (scroll down to section 4.4.)

 

Example

 

The following "normal" query delivers an amount of e.g. 46 companies, where the first company has the ID '1'

https://localhost:8083/gateway/odata/SAP/<yourService>/Companies

 

Now the following query with the system query option $skip=1 delivers an amount of 45 companies, where the first entry in the list has the ID '2'

https://localhost:8083/gateway/odata/SAP/<yourService>/Companies?$skip=1

 

 

How to achieve it?

 

Just like in the previous blog, we get the data from the backend REST service and we modify it such that it has the xml structure that is expected by the Integration Gateway framework.

In order to modify the structure, we parse the xml and create a Document object.

This instance of Document is used to apply the system query options in our little series of blogs.

After refining the Document (apply system query options), it is converted back to a string, which is then set as body to the Message object.

 

 

In order to apply the $orderby we proceed as follows:

 

1. First obtain the value of the $skip that is given by the user of the service

 

       UriInfo uriInfo = (UriInfo) message.getHeaders().get("UriInfo");

       Integer skipOption = uriInfo.getSkip();

 

 

2. Then remove this amount of entries from the collection, starting from the beginning

 

            // retrieve the list of entries

       Node entitySetNode = document.getFirstChild();

       NodeList entities = entitySetNode.getChildNodes();

            // and remove the first entries according to $skip

            int i = 1;

            while (i <= skipNumber) {

             Node firstEntry = entitySetNode.getFirstChild();

             entitySetNode.removeChild(firstEntry);

             i++;

       }

 

 

The implementation for $skip

 

I’ve added a few checks and created the method applySkip()

 

Note:

we don’t need to check for negative $skip value, because this is handled by the OData library.

 

The applySkip() method is invoked from within the callback method processResponseData()

 

 

def Document applySkip(Document document, Message message){

 

       // check the URI for the $skip option

    UriInfo uriInfo = (UriInfo) message.getHeaders().get("UriInfo");

    Integer skipOption = uriInfo.getSkip();

 

       if(skipOption == null){

            // if skipOption is null, then there's no $skip in the URI, so do nothing

             return document;

    }

 

    // $skip is used

       int skipNumber = skipOption.intValue();

 

       // retrieve the list of entries

    Node entitySetNode = document.getFirstChild();

    NodeList entities = entitySetNode.getChildNodes();

       int totalCount = entities.getLength();

 

       // check if the given value for $skip is higher than the number of entries

       if(skipNumber > totalCount){

            // error handling

       message.setBody("The given value for skip is too high");

       message.setHeader("camelhttpresponsecode", 400);

  

             returnnull;

    }

 

       // now remove the entries according to $skip

       int i = 1;

       while (i <= skipNumber) {

       Node firstEntry = entitySetNode.getFirstChild();

       entitySetNode.removeChild(firstEntry);

       i++;

    }

 

       return document;

}

 

 

Supporting error handling

 

This sample code also showcases how to do error-handling:

 

The error message is set as body to the Message-instance.

As we know, the IGW – framework expects that the body is an xml-structure that matches the OData model.

Therefore, we have to tell the FWK that there isn't any valid response payload, but instead, an error message has to be displayed.

This is achieved by setting an error-status-code to the respective header.

In the sample code, we’re setting status 400 which is BAD REQUEST

 

Additionally, we have to modify the processResponseData() method:

If the applySkip method returns null, then we don’t set the body to the Message instance, as this is already done.

 

 

Supporting multiple system query options

 

Usually, you’ll want to support both $top and $skip in your OData service.

In such case, you have to consider the following:

From the full list of entries, you have to FIRST apply the $skip, and only THEN apply the $top.

This makes a significant difference.

The OData V2 specification describes it here (See section 4.4 and the second example)

 

 

 

The complete custom script

 

The following sample code contains the full content of the custom script.

The content is the same as the script file attached to this blog.

 

import java.nio.charset.StandardCharsets

 

import javax.xml.parsers.DocumentBuilder

import javax.xml.parsers.DocumentBuilderFactory

import javax.xml.parsers.ParserConfigurationException

import javax.xml.transform.OutputKeys

import javax.xml.transform.Transformer

import javax.xml.transform.TransformerConfigurationException

import javax.xml.transform.TransformerException

import javax.xml.transform.TransformerFactory

import javax.xml.transform.dom.DOMSource

import javax.xml.transform.stream.StreamResult

 

import org.apache.olingo.odata2.api.uri.KeyPredicate

import org.apache.olingo.odata2.api.uri.NavigationSegment

import org.apache.olingo.odata2.api.uri.UriInfo

import org.w3c.dom.Document

import org.w3c.dom.Element;

import org.w3c.dom.Node

import org.w3c.dom.NodeList

import org.xml.sax.InputSource

import org.xml.sax.SAXException

 

import com.sap.gateway.ip.core.customdev.logging.ILogger

import com.sap.gateway.ip.core.customdev.logging.LogMessage

import com.sap.gateway.ip.core.customdev.util.Message

 

 

 

def Message processRequestData(message) {

 

       return message;

}

 

 

def Message processResponseData(message) {

       message = (Message)message;

       String bodyString = (String) message.getBody();

 

       /* CONVERT PAYLOAD */

    InputSource inputSource = new InputSource(

                                   new ByteArrayInputStream(

                                       bodyString.getBytes(StandardCharsets.UTF_8)));

    inputSource.setEncoding("UTF-8"); // required due to BOM

    Document document = loadXMLDoc(inputSource);

 

       // now do the refactoring: throw away useless nodes from backend payload

    document = refactorDocument(document);

 

       // handle system query options

    document = applySkip(document, message);

 

       if(document == null){ // react to error

             return message;

    }

 

       // convert the modified DOM back to string

    String structuredXmlString = toStringWithoutProlog(document, "UTF-8");

 

       /* FINALLY */

    message.setBody(structuredXmlString);

       return message;

}

 

 

def Document loadXMLDoc(InputSource source) {

    DocumentBuilder parser;

       try {

       parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();

    } catch (ParserConfigurationException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Failed to create parser");

             returnnull;

    }

       // now parse

       try {

             return parser.parse(source);

    } catch (SAXException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error while parsing source");

             returnnull;

    }

}

 

def Document refactorDocument(Document document){

       //find nodes

    Node rootElement = document.getFirstChild();

    Node asxValuesNode = rootElement.getFirstChild();

    Node proddataNode = asxValuesNode.getFirstChild();

    NodeList snwdNodeList = proddataNode.getChildNodes();

 

       //rename all nodes of the feed

    document.renameNode(proddataNode, proddataNode.getNamespaceURI(), "Companies");

       for(int i = 0; i < snwdNodeList.getLength(); i++){

        Node snwdNode = snwdNodeList.item(i);

        document.renameNode(snwdNode, snwdNode.getNamespaceURI(), "Company");

    }

 

       //replace node

    Node cloneNode = proddataNode.cloneNode(true);

    document.replaceChild(cloneNode, rootElement);

 

       return document;

}

 

/**

* Transforms the specified document into a String representation.

* Removes the xml-declaration (<?xml version="1.0" ?>)

* @param encoding should be UTF-8 in most cases

*/

def String toStringWithoutProlog(Document document, String encoding) {

       // Explicitly check this; otherwise this method returns just an XML Prolog

       if (document == null) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error: document is null.");

             returnnull;

    }

    TransformerFactory transformerFactory = TransformerFactory.newInstance();

    Transformer t = null;

       try {

       t = transformerFactory.newTransformer();

    } catch (TransformerConfigurationException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error creating Transformer");

             returnnull;

    }

 

       t.setOutputProperty(OutputKeys.METHOD, "xml");

       t.setOutputProperty(OutputKeys.INDENT, "yes");

       t.setOutputProperty(OutputKeys.ENCODING, encoding);

       t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

       t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

 

       OutputStream os = new ByteArrayOutputStream();

       OutputStreamWriter osw = null;

       try {

       osw = new OutputStreamWriter(os, encoding);

    } catch (UnsupportedEncodingException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error creating Writer");

             returnnull;

    }

    BufferedWriter bw = new BufferedWriter(osw);

       try {

       t.transform(new DOMSource(document), new StreamResult(bw));

    } catch (TransformerException e) {

       ((ILogger)log).logErrors(LogMessage.TechnicalError, "Error during transformation");

             returnnull;

    }

 

       return os.toString();

}

 

def Document applySkip(Document document, Message message){

 

       // check the URI for the $skip option

    UriInfo uriInfo = (UriInfo) message.getHeaders().get("UriInfo");

    Integer skipOption = uriInfo.getSkip();

 

       if(skipOption == null){

             // if skipOption is null, then there's no $skip in the URI, so do nothing

             return document;

    }

       // $skip is used

       int skipNumber = skipOption.intValue();

 

       // retrieve the list of entries

    Node entitySetNode = document.getFirstChild();

    NodeList entities = entitySetNode.getChildNodes();

       int totalCount = entities.getLength();

 

       // check if the given value for $skip is higher than the number of entries

       if(skipNumber > totalCount){

             // error handling

       message.setBody("The given value for skip is too high");

       message.setHeader("camelhttpresponsecode", 400);

             returnnull;

    }

 

       // now remove the entries according to $skip

       int i = 1;

       while (i <= skipNumber) {

       Node firstEntry = entitySetNode.getFirstChild();

       entitySetNode.removeChild(firstEntry);

             i++;

    }

 

       return document;

}

Viewing all 370 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>