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

Integration Gateway: Understanding REST data source [8]: READ - json

$
0
0

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

 

In my previous tutorial, I’ve explained that since SP06, the SMP also supports READ operation for REST data source.

That tutorial was based on xml, so today I’d like to give an example how to do the same for JSON payload.

We’ll do the payload modification using a JSON parser.

 

 

Why is this blog necessary?

In JSON, the expected structure that we have to provide in our Custom Code, is different than in case of XML.

The current example REST service doesn’t have a READ URL, so a filter has to be specified as "Relative URL

 

As usual, please find the relevant source files attached to this Blog.

 

 

 

 

Prerequisite

 

You should have gone through my tutorial regarding READ operation for REST data source:

 

 

 

Preparation

 

You might wish to read this document in order to enhance your development experience for Groovy

 

 

REST service


In this tutorial, we’re using the following REST service

http://sansad.co/api/legislators

 

Documentation can be found here:

http://sansad.co/

 

Destination


In SMP, go to the Gateway Management Cockpit and create Destination for the REST service

 

 

 

The "Connection Test" should be successful

 

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

 

Note that 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.

 

OData Model

 

 

Create Project

 

Create project of type SAP Mobile Platform OData Implementation Project.

 

 

Define OData Model


Within the OData Modeler, create one EntityType that looks as follows:

 

 

 

 

 

Bind data source


Bind the REST data source to the “Read” operation of our EntitySet “Customers”.

Specify the following “Relative URL”:

 

/api/legislators?mp_id={mp_id}

 

 

Please note:

The URL that is used by the REST service to read a single entry is, for example:

http://sansad.co/api/legislators?mp_id=2245RS

 

We can see that the REST service doesn’t address a single resource by a URI.

Instead, a filter parameter is used.

For us, it doesn’t matter. Since the filter parameter uses an ID, we can assume that the result will always be not more than ONE entry.

So we can safely use this URL in terms of a READ for our OData service implementation.

 

Custom Code

 

After finishing the binding wizard, we create the "Custom Code" script for Groovy language.

 

What do we have to do in the script?

 

The payload that is returned by the REST service looks as follows:

 

 

But the Integration Gateway Framework expects the following JSON structure:

 

 

          {

              "PropertyName1":"PropertyValue1",

              "PropertyName2":"PropertyValue2"

          }

 

 

 

 

The following is supported as well:

 

 

 

{

  "d":

        {

              "PropertyName1":"PropertyValue1",

              "PropertyName2":"PropertyValue2"

        }

}

 

 

In our concrete example project, we have to manipulate the REST response such that it looks like:

 

 

 

 

After we’ve modified the response string, we return it to the Integration Gateway Framework.

Which is done by setting the modified string as body to the “message” object that is passed to us as parameter.

 

 

Intermediate step

 

Before we start modifying the REST response, let’s do an intermediate step with a very simplified hard-coded response string.

The code looks as follows:

 

 

def Message processResponseData(message) {

  message.setBody(

"{" +

                              "\"mp_id\":\"Tazeen Fatma\"," +

                              "\"first_name\":\"Tazeen\"," +

                              "\"state\":\"Uttar Pradesh\"" +

                        "}");

 

  return message;

}

 

 

Whereas the following is also working:

 

 

def Message processResponseData(message) {

  message.setBody(

              "{" +

                    "\"d\":" +

                          "{" +

                                  "\"mp_id\":\"Tazeen Fatmaaaaaaaaa\"," +

                                  "\"first_name\":\"Tazeen\"," +

                                  "\"state\":\"Uttar Pradesh\"" +

                          "}"+

            "}");

 

  return message;

}

 

 

After generate, deploy, configure and run the service, you should see the result in the browser.

Check the Result-section below for info about how to run the service.

 

 

Note:

In this hard-coded response, we’re setting less properties than defined in our OData model. But this is OK, at runtime the remaining properties will be empty.

Remember: Our OData model can be bigger than the REST-service, but it must not be smaller (which would cause exceptions at runtime).

 

 

Implement the script

 

Now let’s do the generic implementation.

 

What we have to do:

 

As you can see from the screenshots above, we have to

 

  • Remove the “results” node
  • Remove the Array that is indicated by [ ]
  • Remove the last 2 nodes, "count" and "page"

 

Other than in the previous tutorial, we’ll be using a JSON parser.

Using the parser, we only have to traverse the node-tree until we reach the entry that carries the properties.

That’s it already.

Then only transform this entry back to string.

 

 

Note:

In this concrete example, I’ve reduced the amount of properties in the OData model.

Remember: it is not allowed that the OData model has less properties than the data source, which is the REST service.

Thus, we have to solve this problem, and we do it by removing the undesired properties from the REST response, before we pass it back to the Integration Gateway Framework

This can be easily done with the parser API.

 

 

In one of my previous tutorials, I’ve described in detail how to use the JSON parser.

 

We need to declare a dependency to the JSON parser library, which is available in SMP: com.google.gson

Open the MANIFEST.MF file and add the dependency:

 

 

 

 

Now you can implement the following code:

 

 

 

def Message processResponseData(message) {

    message = (Message)message;

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

 

       // convert payload

    String convertedPayload = convertPayload(restResponse, message);

    message.setBody(convertedPayload);

 

      return message;

}

 

def String convertPayload(String payload, Message message){

    JsonParser parser = new JsonParser();

 

    JsonElement topElement = parser.parse(payload);

    JsonObject resultsJsonObject = topElement.getAsJsonObject(); // {"results":[{...

 

    JsonElement resultsJsonElement = resultsJsonObject.get("results");

    JsonArray resultsArray = resultsJsonElement.getAsJsonArray();

 

    // since we're performing a READ operation, we have exactly one entry

       JsonObject entryObject = (JsonObject)resultsArray.get(0);

 

       // now need to remove some properties, because they aren't defined in our OData model

    entryObject = doRefactoringForEntry(entryObject, message);

 

      return entryObject.toString(); // transform to string and return

}

 

// our OData model doesn't contain all the properties that the REST service has.

// this is not allowed. So we remove the undesired properties from the REST payload,

// in order to match the smaller model

def JsonObject doRefactoringForEntry(JsonObject entryObject, Message message){

    entryObject.remove("age");

    entryObject.remove("attendance_percentage");

    entryObject.remove("debates");

    entryObject.remove("elected");

    entryObject.remove("in_office");

    entryObject.remove("private_bills");

    entryObject.remove("questions");

 

      return entryObject;

}

 

 

 

The relevant line of code is

 

      JsonObject entryObject = (JsonObject)resultsArray.get(0);

 

 

This is the entry that matches the structure that is expected by Integration Gateway. So we can basically just call its toString() method and we get what Integration Gateway needs.

Additionally to the requirement from Integration Gateway, we also remove some of the properties, as explained above.

 

Please note that error handling is completely missing in our sample code.

 

 

Result

 

After doing generate&deploy in our Eclipse project, change to your browser and open the Gateway Management Cockpit.

Assign the destination that we’ve created in the preparation step.

Invoke our OData service.

 

Note that we haven’t implemented the QUERY, so we have to directly invoke a URL for READ of a single entry:

 

Example for a READ URL for our OData service:

https://localhost:8083/gateway/odata/SAP/<your_service_name>;v=1/Customers('2245RS')

 

 

The result is

 

 

 

 

 

Links

 

 

The prerequisite tutorial that does the same like this blog, but for XML format:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-understanding-rest-data-source-7-read--xml

 

Introduction in REST data source part 3: Understanding the return structure in xml

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

 

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 2: Understanding the return structure in json

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

 

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

The official documentation

http://help.sap.com/smp306svr/#section6

 

Data Integration

http://help.sap.com/saphelp_smp306svr/helpdata/en/7c/20051470061014a27cf7962ebae5f8/frameset.htm


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

$
0
0

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

It is meant for those of you who have already created OData services based on Integration Gateway in SAP Mobile Platform 3.0 (SMP).

If you want your OData service to support filtering, you have to implement it in the custom script.

This tutorial explains how it is done using the ExpressionVisitor interface, which is provided by Olingo.

By the way, this tutorial is based on SMP SP05.

 

We’re not going to start from scratch. So in order to follow this tutorial, you need to have the project in place that is created in my previous tutorial.

 

 

 

Prerequisites

 

 

 

 

Preparation

 

The following steps were done in the previous tutorial:

 

  • Create implementation project
  • Define OData model
  • Generate custom code for Groovy
  • implement the processResponseData() method
  • Deploy it to SMP
  • Create/configure the destination
  • Test the query operation.

 

 

 

Filter Implementation

 

Overview:


  • Retrieve the Objects UriInfo and FilterExpression.
  • Create Visitor class that implements the ExpressionVisitor interface
  • Implement the 4 required methods
  • Invoke the visitor
  • Use the result to manipulate the relative URI

 

 

Step: Callback  method processRequestData()

 

Implementing the callback method processRequestData() that is provided by the Integration Gateway Framework and which is generated into our custom script.

 

We have to understand:

This method is called, before the REST service (= the data source) is invoked.

 

The order is:

The user calls our OData service in SMP. E.g. https://<host>/gateway/odata/SAP/<SERVICE>/StreetSet?$filter=streetType eq ’PEDESTRIAN’

The OData call is received by Integration Gateway.

Integration Gateway requires our help, therefore it passes the OData-URI (which was called by the user) into our custom script, in the processRequestData() method

Here, we have to change the URI, because the filter statement in OData language is not understood by the REST service.

After changing the URI, we give it back to Integration Gateway.

Then Integration Gateway can safely call the REST service and will get the filtered data.

 

At this point we need to check the documentation of our REST service:

http://www.gisgraphy.com/documentation/user-guide.htm#streetwebservice

 

For this tutorial, I’ve chosen the streettype for filtering.

Example filter:

&streettype=PEDESTRIAN

 

The full URL:

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41&streettype=PEDESTRIAN

 

Note:

Please ignore that this URL actually contains 3 filters.

The background is that when we've created the Eclipse project, we’ve configured following URL

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41 as REST service-URL to provide data.

So, since the first 2 filters belong to the base URL (as we've defined it), we have to ignore these first 2 filters in our tutorial

So we have the base URL:

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41

and we add one filter to it:

&streettype=PEDESTRIAN

 

 

Step: Object UriInfo

 

The Olingo API provides the interface UriInfo

 

We need it, because this object contains information about the URI, which includes system query options.

The part of the URI, in which we’re interested, is the filter statement.

 

 

Step: Object FilterExpression

 

The Olingo API provides the interface FilterExpression

We need it in order to parse the OData filter statement.

We obtain the filter statement by asking the UriInfo for the FilterExpression:

 

 

       FilterExpression filterExpression = uriInfo.getFilter();

 

 

The filterExpression object contains the actual filter that is specified in the URL of the invoked OData service on SMP.

This is the $filter parameter, as specified in OData.

 

Example:

 

If the full URL of the OData service invocation is as follows:

 

https://<SMPserver>/gateway/odata/SAP/<YOUR_SERVICE>/StreetSet?$filter=streetType eq ’PEDESTRIAN’

 

then the filterExpression object contains the information about

 

filter=streetType eq ’PEDESTRIAN’

 

If the full URL of the OData service invocation is as follows (just as an example):

 

https://<server>/gateway/odata/SAP/<YOUR_SERVICE>/StreetSet?$format=json

 

then the filterExpression object is null

So we have to explicitly handle the case that no filter is desired:

 

     if (filterExpression == null){

          return;

     }

 

The filterExpression object is required to invoke the ExpressionVisitor.

 

 

 

Step: Interface ExpressionVisitor

 

The Olingo API provides the interface ExpressionVisitor

 

This interface has to be implemented by us.

Here it is where the filter implementation is realized.

The ExpressionVisitor is required, because a filter statement can be complex.

The Olingo framework traverses all the filter statements in the OData service call and passes the segments to us in the different methods of the interface.

 

In our script, we create an inner class that implement the ExpressionVisitor interface:

 

 

     defclass RestExpressionVisitor implements ExpressionVisitor{

 

 

We let Eclipse add the methods which have to be implemented.

Most of them can be ignored.

 

In following steps, we'll have a closer look at the relevant methods.

 

visitProperty()


visitLiteral()


visitBinary()


visitFilterExpression()

 

 

They are invoked in this order.

 

 

Example:

 

What happens, if our OData service is invoked with $filter=streetType eq ’PEDESTRIAN’

 

The methods are invoked in the following order:

 

visitProperty()Here we have to deal with the property, e.g.streetType
visitLiteral()

Here we have to deal with the literal, e.g.

‘PEDESTRIAN’
visitBinary()

Here we have to deal with the operator, e.g.

eq
visitFilterExpression()Here we have to deal with our filterExpression, e.g.streetType eq ‘PEDESTRIAN’

 

 

Note:

These sample values on the right column depend on the implementation.

 

 

 

Second example:

What happens, if our OData service is invoked with $filter=streetType eq ’PEDESTRIAN’ and name eq ‘East Lake Street’

 

 

 

visitProperty()Deal with the property, e.g.streetType
visitLiteral()

Deal with the literal, e.g.

‘PEDESTRIAN’
visitBinary()

Deal with the operator, e.g.

eq
visitProperty()Deal with the second property, e.g.name
visitLiteral()Deal with the second literal, e.g.‘East Lake Street’
visitBinary()Deal with the operator, e.g.eq
visitBinary()Deal with the operator, e.g.and
visitFilterExpression()Deal with our filterExpression, e.g.streetType eq ‘PEDESTRIAN’ and name eq ‘East Lake Street’

 

 

Note:

Again, these sample values depend on the implementation

 

We can see that the visitor traverses all segments and finally calls for the final expression

 

 

Note that In this tutorial we cover only simple filter statement.

 

Now let’s implement the methods for our example

 

 

 

Step: Method visitProperty()

 

The method signature:

 

Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmTyped)

 

In this method, we get the possibility to analyze the property mentioned in the filter expression.

In our example:

 

$filter=streetType eq ’PEDESTRIAN’

 

Here, the text streetType is the name of a property (in the OData model).

 

When implementing the visitProperty() method, we can access the property as an object as follows:

 

             EdmProperty edmProperty = (EdmProperty) edmTyped;

 

And the name of the property:

 

             String propertyName = edmProperty.getName();

 

So what?

In our REST example service we really need this method.

Why?

Because the parameter, that is supported by the REST service is: streettype

(see docu)

but the name of the property (in both the OData model and the REST service) is: streetType

 

See it? They differ.

Without converting the OData property name to the REST parameter name, the filtering doesn't work.

 

But we can make use of the visitProperty() method in order to do a mapping from property name to parameter name

 

            String restParameterName = null;

            if(propertyName.equals("streetType")){ // we get the OData property name

             restParameterName = "streettype"; // we convert it to REST parameter name     

       }else{

             restParameterName = propertyName;

       }

 

 

Our method has to return the name of the parameter that is used by the REST service.

See the sample implementation of the method here:

 

@Override

     public Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmTyped) {

 

     if(! (edmTyped instanceof EdmProperty)){

          thrownew RuntimeException("Unexpected type");

     }

    

     EdmProperty edmProperty = (EdmProperty) edmTyped;

    

     String propertyName = null;

     try {

          propertyName = edmProperty.getName();

     } catch (EdmException e) {

          ((ILogger)log).logErrors(LogMessage.TechnicalError, "Failed to read property");

          thrownew RuntimeException("Unable to access name of property", e);

     }

    

     String restParameterName = null;

     if(propertyName.equals("streetType")){ //in the REST service, the param is different

          restParameterName = "streettype";

     }else{

          restParameterName = propertyName;

     }

    

     return restParameterName; // e.g. will lead to streettype=PEDESTRIAN

}

 

 

 

Step: Method visitLiteral()

 

The method signature:

 

Object visitLiteral(LiteralExpression literalExpression, EdmLiteral edmLiteral)

 

In this method, we get 2 objects, but we focus on the second only:

 

EdmLiteral can be used for obtaining the literal, which is the pure text of the filter.

 

What do we have to do?

As we’ve seen above, the syntax of our REST service for filtering is:

 

&<parametername>=<value>

 

 

Note that the value doesn’t require inverted comma.

-> here we have the difference to OData

 

So in our example the OData segment  ’PEDESTRIAN’

 

Has to be converted to the REST segment PEDESTRIAN

(without inverted commas)

 

This is easy to implement: just remove the inverted commas.

And it is even easier to implement, because the following statement returns the value without inverted comma

 

edmLiteral.getLiteral()

 

So our implementation looks as follows:

 

 

     @Override

     public Object visitLiteral(LiteralExpression literalExpression, EdmLiteral edmLiteral) {

          return edmLiteral.getLiteral();

     }

 

 

 

Step: Method visitBinary()

 

The method signature:

 

Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide)

 

In this method, we have to convert the operator.

 

As we’ve seen above, the syntax of our REST service for filtering is:

 

&<parametername>=<value>

 

 

The operator is: =

-> this is the difference to OData

 

So in our example the OData operator: eq

 

Has to be converted to the REST operator: =

 

This has to be done for all supported operators, so we have to care about each operator in a switch statement.

Note that Olingo provides an enum for the OData operators:

 

org.apache.olingo.odata2.api.uri.expression.BinaryOperator

 

We get it as parameter in the method.

At the end, we have to return the whole expression.

 

In our tutorial, let’s support only 2 operators (it depends of course on the chosen REST service), to allow the above service invocation.

 

public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide) {

     String operatorForREST = "";

                  

     switch(operator){

          case BinaryOperator.EQ:

               operatorForREST = "=";

               break;

          case BinaryOperator.AND:

               operatorForREST = "&";

               break;

          default:

               thrownew RuntimeException("Not supported: " + operator.toUriLiteral());

     }

                  

     return leftSide + operatorForREST + rightSide;

}

 

 

Step: Method visitFilterExpression()

 

The method signature:

 

Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression)

 

In this method, we have the chance to check the final filter expression.

The parameter expression contains our REST filter expression, as we've defined it in the previous visitor-methods.

E.g. streettype=PEDESTRIAN

 

What are we going to do here?

In a simple example, we would simply return the instance of expression.

Our REST sample service is a bit special, because our so-called base-URI already contains some parameters. As explained above, we use it simply to get some data. The fact that the REST service needs 2 parameters for geolocation, doesn’t disturb us.

It only forces us to do a little work in the visitFilterExpresission() method.

The reason is:

Our expression looks fine: streettype=PEDESTRIAN

But it has to be appended to the base URL, which already contains parameters:

 

http://services.gisgraphy.com/street/streetsearch?lat=52.52&lng=13.41

 

So before appending our expression, we have to insert a &

 

That’s all.

 

Our sample implementation:

 

public Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression) {

    

     if(expression instanceof String){

          String expressionAsString = (String)expression;

          if(! expressionAsString.startsWith("&")){

               expressionAsString = "&" + expressionAsString;

               return expressionAsString;

          }

     }


     return expression;

}

 

 

 

Step: Use ExpressionVisitor

 

In the above steps, we've created the RestExpressionVisitor class, that implements the required interface and the necessary methods.

Now we can instantiate and invoke it:

 

       RestExpressionVisitor visitor = new RestExpressionVisitor();

    Object restFilterObject = null;

       try {

          restFilterObject = filterExpression.accept(visitor);

    } catch (ExceptionVisitExpression e) {

          log.logErrors(LogMessage.TechnicalError, "Failed calling accept()");

                 return;

    } catch(ODataApplicationException oe){

          log.logErrors(LogMessage.TechnicalError, "Failed calling accept()");

                 return;

    }

 

 

The returned restFilterObject is the result of our effort: the REST-izised version of our OData-filter.

In our sample implementation, the resulting value is the following string:

 

&streettype=PEDESTRIAN&name=East Lake Street

 

What to do with it?

Remember that we’re implementing the callback method processRequestData(), where we’re supposed to manipulate the backend-URL, before it is called.

So we have to build the URI containing the filter string.

 

Usually, it is formed as follows:

 

<relativeURI> + ? + <filterString>

 

In our special case, the relativeURI already contains the ?

So we don’t need to add it.

We get the relativeURI string from the header, which is passed in the method signature in the message object.

In our example, the relative URI woud be:

 

/street/streetsearch?lat=41.89&lng=-87.64

 

The code looks as follows:

 

 

       String relativeUri = getRelativeUri(message);

       relativeUri = relativeUri + (String)restFilterObject;

 

 

and the code to obtain the relativeURI:

 

 

def String getRelativeUri(Message message){

     Map headers = message.getHeaders();

     Object object = headers.get("RelativeUri");

     if(object instanceof String){

          return (String)object;

     }

 

     returnnull;

}

 

 

 

The last step to do in the processRequestData() method is: to pass the new relativeUri to the Integration Gateway framework.

This is done by setting the header:

 

 

defvoid setRelativeUri(Message message, String newUri){

     message.setHeader("RelativeUri", newUri);

}

 

 

 

Final: Run

 

That’s it.

 

At least, that’s it for the filter implementation in the method processRequestData().

Which is the focus of this blog.

We haven’t talked about the processResponseData(), as this was the focus of my previous blog

Note that you also need to add the required import statements and also to add required bundles.

 

 

I’m pasting the full script below, and I'm also attaching the relevant files of my Eclipse project.

Note that the attached files are based on SP05. So if you use them for a different version, you have to adapt them, e.g. update the dependencies in the manifest file.

 

Please refer to my previous blogs for detailed information (see Links section).

 

 

 

Testing the filtering

 

After generate and deploy the designtime artifact and after configuring the service on SMP, you can try calling several URLs with different filters:

 

First test without filter:

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet

 

Try different streetType filters:

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=streetType eq ’PEDESTRIAN’

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=streetType eq ‘FOOTWAY’

 

And also name filters:

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=name eq ‘East Lake Street’

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=name eq ‘North Canal Street’

 

Try combined filter

https://<host>:8083/gateway/odata/SAP/<YOUR_SERVICE>;v=1/StreetSet?$filter=streetType eq ‘PEDESTRIAN’ and name eq ‘East Lake Street’

 

 

There should be different and meaningful response to these calls.

 

 

Full source code

 

 

import org.apache.olingo.odata2.api.edm.EdmException
import org.apache.olingo.odata2.api.edm.EdmLiteral
import org.apache.olingo.odata2.api.edm.EdmProperty
import org.apache.olingo.odata2.api.edm.EdmTyped
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.api.uri.UriInfo
import org.apache.olingo.odata2.api.uri.expression.BinaryExpression
import org.apache.olingo.odata2.api.uri.expression.BinaryOperator
import org.apache.olingo.odata2.api.uri.expression.ExceptionVisitExpression;
import org.apache.olingo.odata2.api.uri.expression.ExpressionVisitor
import org.apache.olingo.odata2.api.uri.expression.FilterExpression
import org.apache.olingo.odata2.api.uri.expression.LiteralExpression
import org.apache.olingo.odata2.api.uri.expression.MemberExpression
import org.apache.olingo.odata2.api.uri.expression.MethodExpression
import org.apache.olingo.odata2.api.uri.expression.MethodOperator
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression
import org.apache.olingo.odata2.api.uri.expression.OrderExpression
import org.apache.olingo.odata2.api.uri.expression.PropertyExpression
import org.apache.olingo.odata2.api.uri.expression.SortOrder
import org.apache.olingo.odata2.api.uri.expression.UnaryExpression
import org.apache.olingo.odata2.api.uri.expression.UnaryOperator
import com.sap.gateway.core.ip.component.commons.ODataExchangeHeaderProperty
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
/*
* ***************************
*  FRAMEWORK CALLBACK METHODS
* ***************************
*/
/**  Function processRequestData will be called before the request data is  handed over to the REST service and can be used for providing  filter capabilities. User can manipulate the request data here.
*/
def Message processRequestData(message) {    handleFilter(message);    return message;
}
/**  Function processResponseData will be called after the response data is received  from the REST service and is used to convert the REST response to OData  response using String APIs. User can manipulate the response data here.
*/
def Message processResponseData(message) {    convertResponse(message);    return message;
}
/*
* ***************************
*  RESPONSE HANDLING
* ***************************
*/
def void convertResponse(Message message){    String restResponse = message.getBody().toString();    /* CONVERT PAYLOAD */    int index = restResponse.indexOf("<result>");    if(index == -1){ // in case of empty payload        restResponse = "<StreetSet><Street><gid></gid></Street></StreetSet>"; // currently the only possibility for returning nothing    }else{        restResponse = restResponse.substring(index);        restResponse = restResponse.replaceAll("<result>", "<Street>");        restResponse = restResponse.replaceAll("</result>", "</Street>");        restResponse = restResponse.replaceAll("</results>", "</StreetSet>");        restResponse = "<StreetSet>" + restResponse;    }    message.setBody(restResponse);    message.setHeader("Content-Type", new String("xml"));
}
/*
* ***************************
*  REQUEST HANDLING
* ***************************
*/
/**
*     supporting $filter for one property only
* */
def void handleFilter(Message message){    //retrieve the FilterExpression    UriInfo uriInfo = getUriInfo(message);    FilterExpression filterExpression = uriInfo.getFilter();    if (filterExpression == null){        // this is the case if the service is invoked without $filter        return;    }    // Invoke the ExpressionVisitor    RestExpressionVisitor visitor = new RestExpressionVisitor();    Object restFilterObject = null;    try {        restFilterObject = filterExpression.accept(visitor);    } catch (ExceptionVisitExpression e) {        log.logErrors(LogMessage.TechnicalError, "Error while calling accept() on custom ExpressionVisitor");        return;    } catch(ODataApplicationException oe){        log.logErrors(LogMessage.TechnicalError, "Error while calling accept() on custom ExpressionVisitor");        return;    }    if(restFilterObject == null){        log.logErrors(LogMessage.TechnicalError, "Couldn't retrieve the converted filter expression for the backend REST service.");        return ;    }    // add the filter statement (converted to REST-format)    String relativeUri = getRelativeUri(message); // the REST service URL without hostname. In our example it is: /street/streetsearch?lat=52.52&lng=13.41    relativeUri = relativeUri + (String)restFilterObject; // we know that it is a String, because we've implemented it in the Visitor below    // encode    relativeUri = relativeUri.replaceAll(" ", "%20").replaceAll("'","%27");    // Finally, set the newly composed URI    setRelativeUri(message, relativeUri);
}
/**
* Implementation of ExpressionVisitor
*
* Supports combination of 2 filters
* e.g.
 * https://<host>/gateway/odata/SAP/<YOUR_SERVICE>/StreetSet?$filter=streetType eq ‘PEDESTRIAN’ and name eq ‘East Lake Street’
*
* */
def class RestExpressionVisitor implements ExpressionVisitor{    /*     * This implementation converts the OData-like operators (eq)     * to operators in the format, that's understood by the REST service     * */    @Override    public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide) {        String operatorForREST = "";                   switch(operator){            case BinaryOperator.EQ:                operatorForREST = "=";                break;            case BinaryOperator.AND:                operatorForREST = "&";                break;                default:                    throw new RuntimeException("This filter operator is not supported: " + operator.toUriLiteral());            }                   return leftSide + operatorForREST + rightSide;    }       @Override    public Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression) {           // we need to add & because the filter is not the first URL parameter        if(expression instanceof String){            String expressionAsString = (String)expression;            if(    ! expressionAsString.startsWith("&")){                expressionAsString = "&" + expressionAsString;                return expressionAsString;            }        }    }    @Override    public Object visitLiteral(LiteralExpression literalExpression, EdmLiteral edmLiteral) {        return edmLiteral.getLiteral();    }    @Override    public Object visitMember(MemberExpression arg0, Object arg1, Object arg2) {        return null;    }    @Override    public Object visitMethod(MethodExpression arg0, MethodOperator arg1, List<Object> arg2) {        return null;    }    @Override    public Object visitOrder(OrderExpression arg0, Object arg1, SortOrder arg2) {        return null;    }    @Override    public Object visitOrderByExpression(OrderByExpression arg0, String arg1,List<Object> arg2) {        return null;    }    /*     * We need to do a mapping from OData-property-name to REST-parameter-name     * */    @Override    public Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmTyped) {        if(! (edmTyped instanceof EdmProperty)){            throw new RuntimeException("Unexpected type");        }           EdmProperty edmProperty = (EdmProperty) edmTyped;        String propertyName = null;        try {            propertyName = edmProperty.getName();        } catch (EdmException e) {            ((ILogger)log).logErrors(LogMessage.TechnicalError, "EdmException occurred while accessing filter statment property");            throw new RuntimeException("Unable to access name of property", e);        }           String restParameterName = null;        if(propertyName.equals("streetType")){ // in the REST service, the filter parameter is spelled differently than the property name            restParameterName = "streettype";        }else{            restParameterName = propertyName;        }           return restParameterName; // in our example, this will at the end lead to the expression streettype=PEDESTRIAN    }    @Override    public Object visitUnary(UnaryExpression arg0, UnaryOperator arg1,Object arg2) {        return null;    }
}
/*
* ***************************
*  HELPER
* ***************************
*/
def String getRelativeUri(Message message){    Map headers = message.getHeaders();    Object object = headers.get("RelativeUri");    if(object instanceof String){        return (String)object;    }        return null;
}
def void setRelativeUri(Message message, String newUri){    message.setHeader("RelativeUri", newUri);
}
def UriInfo getUriInfo(Message message){    Map headers = message.getHeaders();    Object object = headers.get(ODataExchangeHeaderProperty.UriInfo.toString()); // need dependency to bundle com.sap.gw.rt.ip.commons.camel-commons    if(object instanceof UriInfo){        return (UriInfo)object;    }    return null;
}

 

 

 

Further Reading

 

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 datasource part 2: Understanding the return structure in json

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

 

Introduction in REST datasource part 3: converting xml payload of a REST service for usage in Integration Gateway

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

 

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 Integration Gateway:

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

 

Tutorial about scripting in Integration Gateway:

http://scn.sap.com/community/developer-center/mobility-platform/blog/2014/10/17/integration-gateway-using-custom-scripting-to-map-entity-sets-to-data-sources

SAP Mobile Platform SDK SP07 - What is new?

$
0
0

Great blog by Posted by Kiran Karunakaran in SAP for Mobile on March 13th announcing the release of the latest SAP Mobile Platform SDK. Read the blog and get all the details.

 

SAP Mobile SDK SP07 - What is new ?

 

Native SDK features

  • Technical cache (for Windows)
  • Certificate authentication directly with SAP Gateway in the MAF Logon Core (iOS, Android, and Windows)

 

SMP Hybrid SDK(Kapsel) features

  • Device and Platform support
  • Offline support for Windows
  • SAML support for Windows

 

Agentry Toolkit features

  • Open Scan SDK
  • Other Enhancements

 

Enjoy!

 

Jenny Lundberg

Mobile Platform Product Management

Sending batch request using SMP 3.0 SDK for Android

$
0
0

If you are wondering how to send batch request in Android using the SMP 3.0 SDK SP05+, then this blog is for you.

 

A batch request allows a mobile client to send multiple operations in a single HTTP request.  The following diagram represents the objects needed to create a batch request with SMP 3.0 native SDK for Android:

  • The ODataRequestParamBatch class represents a batch request and it can contain 1 or more batch items.
  • A batch item is represented by the ODataRequestBatchItem interface and the item can be single read request (ODataRequestParamSingle) or a change set (ODataRequestChangeSet).
  • A change set (ODataRequestChangeSet) can contain only CUD requests and their execution is atomic (either all requests executed successfully, or all failed).

BatchRequest.jpg

 

In order to use batch request, the mobile client need to:

 

1. Create a batch request

 

Code snippet - Create Batch Request (ODataRequestParamBatch)
ODataRequestParamBatch requestParamBatch = new ODataRequestParamBatchDefaultImpl();


2. Create the batch item payload for CREATE and UPDATE operations.


Code Snippet - Create Batch Item Payload (ODataEntity)

ODataEntity newEntity = new ODataEntityDefaultImpl("RMTSAMPLEFLIGHT.Travelagency");

newEntity.getProperties().put("agencynum", new ODataPropertyDefaultImpl("agencynum", "12345678"));

newEntity.getProperties().put("NAME", new ODataPropertyDefaultImpl("NAME", "Flight Center Inc"));

newEntity.getProperties().put("STREET", new ODataPropertyDefaultImpl("STREET", "123 Main street"));

  ...


3. Create a batch itemand add item to the batch request


3.1 Single READ request


Code Snippet - Create Batch Item: Single READ Request

// Create batch item

ODataRequestParamSingle batchItem = new ODataRequestParamSingleDefaultImpl();

batchItem.setResourcePath("TravelAgencies");

batchItem.setMode(ODataRequestParamSingle.Mode.Read);

batchItem.setCustomTag("something to identify the request");


// Add batch item to batch request

requestParamBatch.add(batchItem);


3.2 Change set with UPDATE operation


Code Snippet - Create Batch Item: Change Set

// Create batch item

ODataRequestParamSingle batchItem = new ODataRequestParamSingleDefaultImpl();

// Allocate OData Entity

batchItem.setResourcePath("TravelAgencies(‘12345678’)");

batchItem.setMode(ODataRequestParamSingle.Mode.Update);

batchItem.setCustomTag("something to identify the request");

batchItem.setPayload(newEntity);

// Add headers

Map<String, String> createHeaders = new HashMap<String, String>();

createHeaders.put("accept", "application/atom+xml");

createHeaders.put("content-type", "application/atom+xml");

batchItem.setOptions(createHeaders);

// Create change set

ODataRequestChangeSet changeSetItem = new ODataRequestChangeSetDefaultImpl();

// Add batch item to change set.

// You can add more batch items to the same change set as long as they are CUD operations

changeSetItem.add(batchItem);

 

// Add batch item to batch request

requestParamBatch.add(changeSetItem);


4. Send Batch request: Batch requests are submitted as a single HTTP post request to the batch endpoint of a service. The HTTP status code of a batch response is generally “202 Accepted” and it only indicates that the overall request has been accepted for processing, not that every operation successfully completed. There will be a status code returned for each part of the multipart batch request.


Code Snippet - Send Batch Request

// Send request synchronously

ODataResponse oDataResponse = odataStore.executeRequest(requestParamBatch);

// Check http status response for batch request.

// Status code should be "202 Accepted"

Map<ODataResponse.Headers, String> headerMap = oDataResponse.getHeaders();

if (headerMap != null) {

   String code = headerMap.get(ODataResponse.Headers.Code);

}

 

// Get batch response

if (oDataResponse instanceof ODataResponseBatchDefaultImpl) {

  ODataResponseBatch batchResponse = (ODataResponseBatch) oDataResponse;

  List<ODataResponseBatchItem> responses = batchResponse.getResponses();

  for (ODataResponseBatchItem response : responses) {

      // Check if batch item is a change set

      if (response instanceof ODataResponseChangeSetDefaultImpl) {

       ODataResponseChangeSetDefaultImpl changesetResponse = (ODataResponseChangeSetDefaultImpl) response;

       List<ODataResponseSingle> singles = changesetResponse.getResponses();

            for (ODataResponseSingle singleResponse : singles) {

              // Get Custom tag

              String customTag = singleResponse.getCustomTag();

                        // Get http status code for individual responses

              headerMap = singleResponse.getHeaders();

              String code = headerMap.get(ODataResponse.Headers.Code);

 

                        // Get individual response

              ODataPayload payload = singleResponse.getPayload();

                         if (payload != null) {

                                     if (payload instanceof ODataError) {

                             ODataError oError = (ODataError) payload;

                             String uiMessage = oError.getMessage();

                      } else {

                                                  // TODO do something with payload

}

              }

       }

    } else {

       // TODO Check if batch item is a single READ request

    }

   }

}


Questions? let me know,


I would also recommend looking at this document to understand how SAP Gateway deal with Batch requests

How To Batch Multiple Operations Into A Single Request


Claudia

Running Android apps on HCPms

$
0
0

In December 2014, SAP enabled the HANA Cloud Platform mobile services (HCPms) trial for all hanatrial accounts. HCPms is a real Mobile-as-a-Service offering and it shares some source-code baselines with SMP 3.0 on premise. In consequence, mobile apps written with the SMP 3.0 OData SDK can run on both HCPms and SMP 3.0 on premise with little or no change. In order to understand the differences and similarities visit this blog SAP HANA Cloud Platform mobile services released

 

If you haven’t done it yet, you can enable your HCPms trial with the steps described in this blogHow to enable HANA Cloud Platform Mobile Services Trial

 

The process of creating your application configuration is different.John Polus hosted a webinar on this topic Webinar: Getting Started with the HANA Cloud Platform Mobile Services (HCPms) and Using the HANA Cloud Connector to Access Backend Systemsand created two guides to help you go through this process.

  1. Installing and Configuring the HANA Cloud Connector for On-premise OData Access
  2. Creating Application Definitions in Hana Cloud Platform Mobile Services

 

The good news is all the Android material I have created for SMP 3.0 SDK for SMP3.0 on premise is also valid for HCPms. I have onboarded with HCPms and have accessed both online store and offline store using the projects and guides I published on Mobile Application Development Platform for Developers - Native Apps. No changes required.

 

In order to onboard with HCPms, you need to enter the values displayed in the following screenshot.

Host: hcpms-<your user>trial.hanatrial.ondemand.com

Login and Password: Your credentials

Port: 443

No need to check the "unsecure connection" checkbox, because HCPms is an HTTPS connection.

HCPms.jpg

 

Before you run the android apps, I would recommend testing first the configuration of your application definition from a REST Client. I normally use POSTMAN plugin for Chrome, but feel free to use your favorite rest client plugin.

 

If you enter the following values, you should receive a “201 created” response if the application configuration is set up correctly

 

 

URL

https://hcpms-<your user>trial.hanatrial.ondemand.com/odata/applications/latest/<your app id>/Connections

HTTP Operation

POST

HTTP Header: Content-Type

application/atom+xml

HTTP POST Body

<?xml version="1.0" encoding="UTF-8"?>

<entry xml:base="http://localhost:8080/odata/applications/latest/com.sap.flight.kapsel/Connections"

xmlns="http://www.w3.org/2005/Atom" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"

xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices">

  <content type="application/xml">

<m:properties>

</m:properties>

</content>

</entry>

Switch to Basic Auth Tab

 

Username

Your username

Password

Your password

Click the “Refresh Headers” button

 

 

HCPms-onboard.jpg

 

Cheers

Claudia

Onboarding users with Native OData SDK - SMP 3.0

$
0
0

Dear All,

 

From SMP 3.0 onward we have two ways for registering a user with SMP Server.

1. MAF Logon

2. Using OData SDK

 

A lot of people go for using the MAF Logon. MAF Logon comes with a pre defined UI Screen for Logon functionality so the developers do not need to do anything to onboard users. They can just import the MAF projects inside their IDE (Android Studio/Eclipse for Android)/ Xcode for iOS and just run the application.

You can learn how to onboard users using MAF Logon from this link

 

But there could be a requirement that you do not want to use the SAP Standard screen and have your own custom login screen, to maintain UI consistency all over the application. For this we can onboard users using OData SDK. The tutorial on how to onboard users using OData SDK can be accessed using this link

 

You might run into difficulties to authenticate users against any sort of Authentication mechanism that you use like HTTP/HTTPS etc but with NoAuthenticationLoginModule it will onboard your user but when you login to the Management Cockpit it will show the user registered as no_sec_identity.

 

Follow the below set of code to successfully onboard your user against an authentication mechanism that you have configured against your application id

 

Assuming that you have your own custom screen with a field for username and password and a Login Button you can add this set of code on the button click event of your Login Button

 

private LogonCore logonCore;  Define this as a global parameter at the start of your activity.

And within the button click event add this line of code

 

 

 

LogonCoreContext context = logonCore.getLogonContext();

try

{

context.setHost(smpHost);  //address of your SMP Server or relay server

context.setPort(smpPort);    // port of the SMP Server or port 80 if you are using the relay server

context.setHttps(false);       // Set to TRUE if you are using https

context.setFarmId("0");     //Set the FARM ID in case you are using the Relay Server

 

//

context.setResourcePath("ias_relay_server/client/rs_client.dll);   //User this if you are using the Relay Server

 

context.setBackendUser(smpUserId);               //The field that the user will input his username

context.setBackendPassword(smpPassword);   // The field that user will input his credentials

context.setSupUserName(smpUserId);

context.setSupPassword(smpPassword);

context.setChannel(Channel.REST);               //This is a very important parameter to add else you would not be able to pass these parameters

logonCore.register(context);

}

 

With this you can see your user is authenticated against the security provider and now you can use all functionalities like SSO, Role Mapping etc relevant to your security provider.

 

These are all with inputs from Claudia PachecoPavel PenazPrashanth Sutram

 

Hope this helps you all and saves all of your time to debug the error.

 

Thanks,

Regards,

Rakshit Doshi


2 Steps to Manage Mobile App Registration using Client Hub

$
0
0

Client Hub is a mobile application that comes with SAP Mobile Platform SDK. When we have multiple enterprise applications in the device, Client Hub app helps to share common credentials between the apps. Hence the user doesn't need to enter same credentials multiple times to login to multiple apps that improves user experience.

 

Client Hub app has to be installed in the device along with other business apps - when the user enter the credentials for the first time in an enterprise application, it will be stored in the Client Hub (data-vault). And all other apps access these credentials for registration with SAP Mobile Platform, ie. other applications will not prompt the user to enter credentials for login.

Only condition is, all applications including Client Hub has to be signed with same certificate. In this blog I am explaining how to do it.

 

IGWREST_15 Mar. 30 01.14.jpg

Step 1 - Signing apps with same certificate

The source code of the Client Hub app can be found in the SMP 3 SDK installation path..SAP\MobileSDK3\ClientHub. Here I am using Android as an example. Client hub app is available for iOS too. Client Hub app works only with MAF Logon -  It supports both Odata and Kapsel apps.

 

Import the Client Hub project to Android SDK. Then Right click on the project > Export.

IGWREST_7 Mar. 29 11.13.jpg

Click Next until you reach Keystore selection screen. Choose "Create new keystore", and provide keystore name and password. Click Next and enter the details as given below.

IGWREST_9 Mar. 29 11.16.jpgIGWREST_9 Mar. 29 11.17.jpg

Click Next, then click on Finish. It creates Client Hub apk file and keystore.

IGWREST_15 Mar. 30 01.52.jpg

In the business application create clienthub.properties file inside res/raw folder and provide connection details as given below.

Untitled.png

Inside Manifest.xml file include permission for clienthub access -  <uses-permission android:name="com.sap.mobile.clientHub.CLIENTHUB_ACCESS_PERMISSION"/>

 

Finally, right click on the business application and choose Export. Instead of choosing "Create new keystore" choose "Use existing keystore" and browse the keystore created in the previous step. Repeat this step for all the business apps, and create the apk files.

IGWREST_15 Mar. 30 01.51.jpg

 

Step 2 - Installation and Testing

Install all the enterprise apps including Client Hub app in a device and test.

 

#14 SMP3 OData SDK - Performance Tuning with Offline Store

$
0
0

15.pngHi everyone!


For those who have ever read any of my blogs, hello again..


Those previous blogs are for iOS native apps, but this one is for all the SMP3 OData SDK platforms. If you have gone through one of those platforms SDKs, you should have been on a good track of happy app development.


The common discussion while gaining development experience is about the offline store performance with large OData collections. Even though it is originally designed for fairly large volume for mobile devices, there are some tips you should be aware of -  hopefully this will help your development.

 

<updated>Mar 31, 2015</updated>



Problem: Your app with offline store has some performance issue with large volume of data. The initial creation of the store and the refresh time takes longer than you had expected.

 

Solution: Try out the several practical tuning tips below.

 

Discussion:

 

When you encounter the situation, here's the checklist of tips & remarks:

 

1. Measure the OData size and time

2. The log trace of offline component with DEBUG level to figure out the bottleneck

3. How OData is implemented – Server Side Paging

4. How OData is implemented – Delta Tracking

5. Possible performance improvement with Caching

 

Let's take a look at one by one.


1. Measure the OData size and time


Current web browser provides very useful tools built-in. By using developer tools, we can measure the precise data size easily, such as content size, network time..etc to gain solid idea what’s going on. Here's my example (by Chrome) capture fetching 10000 entities. The content size is 20.3MB and it gets compressed in 618KB size during the transfer, and it took 18.20 seconds during HTTP GET. (Latency is like a ping call)

chrome.png

If you compare the payload sizes which has the same amount of the entity numbers but double numbers of parameters in each entity, you'll see the total size of data transfer gets nearly double (of course). So you can examine how the properties of each entity can affect the data transfer size and time.


And - Payload in JSON format is lot smaller than XML.

json.png

xml.png

 

2. The log trace of offline component with DEBUG level to figure out the bottleneck


Bump up the trace level for offline component. Go to "LOGS" > "SETTINGS" in admin console.

logs_setting.png

Change the log level of the Offline component to "debug" and the trace "Enabled".

debug.png

Now the offline component is ready to show the useful info.

log.png

The info provides very fine details of the offline store, you'll get clear idea what's happening behind the scenes. The timestamp data also tells you where's the bottleneck is.

details.png

 

3. How OData is implemented – Server Side Paging

 

As already discussed here, your offline store would fail to initialize if the OData collection in the defining request is too large. In that case you need to implement $skiptoken (aka Server Side Paging). Most of the other OData producer frameworks should support $skiptoken too.

 

Once you implement it, in general, you should experiment a bit with the page sizes until you find a size that balances the backend performance and the response size. (For example, one of my personal experiment was 1000 entities in one page was slower than 5000.)

 

Coming soon: <SOON>How to Implement $skiptoken with TravelAgency table in SAP Gateway</SOON>

 

4. How OData is implemented – Delta Tracking


Delta Query implementation in the OData producer is effective way to boost up the overall end-to-end performance. A quick explanation from the offline store perspective is written here. Just like $skiptoken, most of the OData producers should support delta query, so please consult respective documentation on how to implement.


SAP Gateway: How to Implement Basic Delta Query Support in SAP Gateway


Integration Gateway:  How To Use the Custom Coding for JDBC Connections in Integration Gateway

 

5. Possible performance improvement with Caching


Together with Delta Tracking, Caching is also referred here:

 

"To minimize the load on the OData Producer and possibly improve performance, cache data that is shared by numerous users on SAP Mobile Platform Server. "


Essentially what you need to do is to tweak the parameters for the defining request by uploading .ini file to the SMP server. You'll find the params for cache is pretty optimized by default, so you might not see a real difference even if you use this feature - hence I described it as "possible" performance improvement.


One remark about the parameter is about "indexed_type". It is not for caching but it is used primarily for $orderby and for $filter clauses when executing requests from the client. So when your offline store deals with a high volume of data, your local $orderby or $filter query execution (remember - offline store CRUD operations get queued locally until the next flush or refresh, it acts like an OData producer) might get faster.

 


Okie that's all for now. I'll update this content whenever new & important info comes in!



See you in the next blog,

Ken


List of blogs



Oh ListSelection Step, thou art a heartless bit**!

$
0
0

One day, I would like to see the client code behind the list selection step. I imagine, there is some comment along the lines of "//I hate myself and I want to die".

 

Sounds a bit harsh? True, but what would life be without some complaining every now and then. Now, let me explain, why I think this line of comment might be in there somewhere...

 

The ListSelection step is pretty straight forward. You define the Screen Set, Screen, List Control (a field on the Screen) and what line to be selected within that List Control. This should be all you need for the list selection step to work, right? With that information, there is no way you (the client) could miss the UI control. Also, the ListSelection step definitely knows about the Screen Set and thus the Object displayed by that Screen Set. Why do I emphasize that fact? Because for some reason that totally escapes me, the displayed object of the Screen Set seems to be important. You might think that if that object is really so important, the ListSelection step could just pass the information on the displayed Object to the List Control, but no... we are forced to provide the Screen Sets object as target in the Action containing the ListSelection step! Why on god's green earth?!?

 

 

 

 

Okay, enough trolling for now... to make this a bit more productive, let me give you (and my future self) an example on how to make the list selection step work in a non-trivial situation. (Just copy the following lines and replace the definition names with your definition names to better understand what might be going wrong in your case.)

 

Assuming objects on the client look like this

  • MainObject
    • ScreenSetObjects
      • ScreenSetObject 1
      • ScreenSetObject 2
    • ListObjects
      • ListObject 1
      • ListObject 2

  

and our Screen (Set) definitions look like this

  • ScreenSet "ListScreenSet" displays Object "ScreenSetObject"
    • Detail Screen "ListScreen"
      • Field "ListControl" of type "List Tile View" on collection ":>Main Object>"ListObjects" Property" where "ListObjects" contains Objects of type "ListObject"
      • Action Button "NextRowButton" with Action "NextRowAction" and Target "???"

  

and the Action looks like this

  • Action "NextRowAction" for object "???"
    • List Selection Step "ListSelection"
      • Screen Set "ListScreenSet"
      • Screen "ListScreen"
      • List Control "ListControl"
      • Select Rows "Next Row"

 

what would we have to put in the gaps (???) to make it work?

  • the target of the "NextRowButton" has to be the exact object that is being displayed in the "ListScreenSet" (here it is one of the "ScreenSetObject" objects)
  • and consequently, the "for object" of the "NextRowAction" has to be set to "ScreenSetObject"

 

And that is how to use List Selection steps...

 

Daniel

 

 

(Based on SMP 3.0 SP7 PL1, WPF Client)

How to use LDAP Authentication with SMP 3

$
0
0

SAP Mobile Platform supports multiple built-in authentication providers that authenticate users. The SMP administrators can create security profile and assign the authentication providers using Management Cockpit.

 

In this example I am going to show you how to use LDAP to authenticate from your mobile application.


When I started, I did't find any existing LDAP setup, so I have configured one. If you don't have an existing LDAP Server you can follow by blog, it's quite easy using Apache Directory Studio: Configuring LDAP -  A Basic Tutorial

Configuring LDAP security profile

     1. Login to SMP Admin Cockpit.

     2. Click on Security Tab. Under Security Profiles click on New button. It will open Edit Security Profile window.

      IGWREST_25 Apr. 04 07.55.jpg

     3. On Name field enter "LDAP", then click on Add button.

         It will open Add Authentication Provider window.

      IGWREST_25 Apr. 04 09.18.jpg

     4. Choose Authentication providers as Directory Service (LDAP/AD) and provide the below details:

 

PropertyValue
Control FlagOptional
Server Typesunone5
Provider URLldap://localhost:10389
Security ProtocolLDAP
Bind DNuid=admin,ou=system
Bind Passwordsecret
Authentication Search Baseou=users,ou=system
Skip Role Lookuptrue
Role Member Attributesuniquemember
User Role Membership AttributesnsRoleDN
Default Search Baseou=users,ou=system


     Leave all other fields with default values.

     After entering the values the screen should look like below:

      config.jpg

     IGWREST_24 Apr. 03 14.07.jpg

     5. Click on Save.

 

Configuring Application

     1. Go to Applications panel.

     2. Click on New button. It will open New Application window.

     3. On ID field enter LDAPAuth and click on Save. It will open a new window.

      IGWREST_25 Apr. 04 08.26.jpg

     4. On field Endpoint enter http://services.odata.org/V2/Northwind/Northwind.svc/

     5. Under SSO Mechanisms, click on Add. Then click on Save.

      IGWREST_25 Apr. 04 08.38.jpg

     6. Click on Authentication Tab. For field Profile Name choose LDAP.

      IGWREST_25 Apr. 04 08.44.jpg

     7. Click on Save.

Authenticate using LDAP

     1. Run Post man rest client on Chrome and provide below values. Pass LDAP credentials to register.

       IGWREST_25 Apr. 04 09.04.jpg

     2. Click on on Send button. It will register the user on SMP server.

         Success response from Postman Rest Client:

      IGWREST_25 Apr. 04 09.08.jpg

On SMP Admin cockpit you can find the registered user.

    IGWREST_25 Apr. 04 09.10.jpg

To  register user from a mobile app there is no LDAP specific device code needed if you are using MAF Logon or LogonCore class or REST API.

 

CC:

SMP Developer Center

 

Regards, Midhun

SAP Technology RIG

Integration Gateway: REST data source, overview of Blogs

$
0
0

This blog post aims to collect all links to Blogs and Documents that provide tutorials and information about the REST data source for Integration Gateway in SAP Mobile Platform 3.0 (SMP).

Based on the component Integration Gateway in SMP, you can provide OData services, that expose data that is fetched from different data sources - like Database, JPA, and also REST-services.

In order to make use of a REST data source, the OData service developer has to write custom coding.

Which is explained in the following blogs.

 

 

Processing the response payload


The main task that has to be implemented in the script is to transform the payload of the REST service into a common data structure.

 

For better understanding and easier getting up to speed, the following blogs provide a very simple example with hard-coded payload

These blogs are based on the QUERY operation

 

REST service with xml payload

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

The same with JSON payload

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

 

The next step: not hard-coded, using string operations

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

 

Alternative: using parser to avoid string operations

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

The same with JSON parser

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

 

READ operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-understanding-rest-data-source-7-read--xml

The same with JSON

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-understanding-rest-data-source-8-read--json

 


Advanced topics


Implementing $filter

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/17/integration-gateway-understanding-rest-data-source-4-filtering

Integration Gateway: Understanding REST data source [9]: CREATE operation

$
0
0

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

In the previous tutorials, we’ve learned how to deal with REST services as data source for an OData service in SMP.

These tutorials were based on reading data, which is done via QUERY and READ operations.

 

Since SMP SP07, modifying operations are supported as well.

 

In this blog, we’ll have a look at the CREATE operation:

We’ll do the implementation in the script and in order to create data, we’ll execute a POST request from a browser-based REST client tool.

 

If you’re new to the topic, check the Links section below where you can find the tutorials which get you up to speed.

 

 

 

Prerequisites

 

I expect that you've gone through my previous tutorials, explaining REST data source – QUERY and READ operation – based on XML payload.

Please check the Links section for the relevant blogs.

The prerequisites are:

 

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

 

 

 

Preparation

 

REST Service

 

For this tutorial, we need a REST service that supports writing scenario. I’m using a service that is not public, I hope this blog will be useful nevertheless.

I’ll post an update if that service becomes public some day in future…

 

 

 

OData Model

 

Define OData model

 

The sample REST service that I’m using provides the following sample data:

 

 

So for my OData model, I create the following entity type:

 

 

 

 

Bind data source

 

For better testing, I create binding and scripts for QUERY and READ operation (please refer to my previous tutorials for details).

Then I deploy to SMP, create and assign destination and run the service.

Fine.

 

Now let’s do the binding for the CREATE operation.

 

How are we supposed to build this relative URL?

 

 

As in the previous tutorials, we wonder how the Request URL should look like.

The answer is again: this depends on the backend REST service.

 

Before implementing the OData service, I’ve checked how a POST request which is fired directly to the backend REST service should look like.

For my sample REST service, it looks as follows:

 

 

 

Request URL

the same URL as for the QUERY operation, the URL that lists the entry resources:

https://<host>:<port>/ rest/addressbook/companies



RequestBody

the same that is returned by a READ operation:


<asx:abap version="1.0">

  <asx:values>

    <COMPANY>

      <ID>40</ID>

      <NAME>Indian IT Trading Company</NAME>

      <STREET>Nariman Point</STREET>

      <POSTAL_CODE>400021</POSTAL_CODE>

      <CITY>Mumbai</CITY>

      <COUNTRY>IN</COUNTRY>

      <WEB_ADDRESS>http://www.it-trade.in</WEB_ADDRESS>

      <PHONE_NUMBER>4203876954</PHONE_NUMBER>

    </COMPANY>

  </asx:values>

</asx:abap>



 

 

Note: Request body

For my sample backend REST service, I don’t have to care about unique ID, as it is computed in the backend

 

 

What we learn from this exercise:

1)    The relative URL that we have to enter in the wizard is the same as the one that we enter for the QUERY operation.

       Note: this might be different for other REST services.

2)    The request body has to look like that

 

 

So now we can proceed in Eclipse, we do the “Select Data Source” -> REST -> CREATE -> Request URL:

 

 

 

 

 

Custom Code

 

Then we generate the Custom Code for Groovy.

 

For the CREATE operation, we have to implement both methods,

 

processRequestData()

 

and

 

processResponseData()

 

Why?

In the processRequestData() method, we have to adapt the POST request, before it is sent to the backend REST service

In the processResponseData() method, we have to adapt the response, because the OData specification requires that the POST operation should return the newly created entry in the response payload.

 

 

Implement processRequestData() method

 

After you’ve understood the QUERY and READ tutorials, you’ll guess what needs to be done in the processRequestData() method for the CREATE operation:

 

We get the OData request payload and we have to modify it such that it becomes a payload that is understood by the backend REST service.

 

What we get from the Integration Gateway Framework is not the full OData payload, it is only the data in the usual special structure.

What we have to return, is the full payload, as it is required by the backend REST service.

 

So everything sounds known to us: it is the same like the READ operation, just the other way round.

 

What we have to do:

 

1) The first thing we have to take care, is to set the Content-Type header.

It should be done before other tasks are done.

Why?

Because with this header we specify in which format we want to get the data from Integration Gateway.

If we write:

 

message.setHeader("Content-Type", "application/atom+xml");

 

Then we get the OData request payload as a String that contains an XML structure.

If we don’t set this header, we get a JSON structure.

 

 

2) Second, modify the request body

When our final OData service is used, a POST request will be executed and it will have the following request Body:

 

<entry xmlns="http://www.w3.org/2005/Atom"

          xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"

          xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"

          xml:base="https://localhost:8083/gateway/odata/<myNamespace>/<myService>/">

  <content type="application/xml">

    <m:properties>

      <d:ID>40</d:ID>

      <d:NAME>Indian IT Trading Company</d:NAME>

      <d:STREET>Nariman Point</d:STREET>

      <d:POSTAL_CODE>400021</d:POSTAL_CODE>

      <d:CITY>Mumbai</d:CITY>

      <d:COUNTRY>IN</d:COUNTRY>

      <d:WEB_ADDRESS>http://www.it-trade.in</d:WEB_ADDRESS>

      <d:PHONE_NUMBER>4203876954</d:PHONE_NUMBER>

    </m:properties>

  </content>

</entry>

 

 

Note:

As you know, we obtained this body by copy&pasting it from the response body of a READ request.

 

In our script, we get the relevant data structure as XML, which looks as follows

 

<Companies>

  <Company>

    <NAME>Indian IT Trading Company</NAME>

    <COUNTRY>IN</COUNTRY>

    <WEB_ADDRESS>http://www.it-trade.in</WEB_ADDRESS>

    <ID>40</ID>

    <CITY>Mumbai</CITY>

    <POSTAL_CODE>400021</POSTAL_CODE>

    <STREET>Nariman Point</STREET>

    <PHONE_NUMBER>4203876954</PHONE_NUMBER>

  </Company>

</Companies>

 

 

 

What we have to do is to transform it to the following XML-string, which is required by our backend REST service:

 

 

<asx:abap version="1.0">

  <asx:values>

    <COMPANY>

      <NAME>Indian IT Trading Company</NAME>

      <COUNTRY>IN</COUNTRY>

      <WEB_ADDRESS>http://www.it-trade.in</WEB_ADDRESS>

      <ID>40</ID>

      <CITY>Mumbai</CITY>

      <POSTAL_CODE>400021</POSTAL_CODE>

      <STREET>Nariman Point</STREET>

      <PHONE_NUMBER>4203876954</PHONE_NUMBER>

    </COMPANY>

  </asx:values>

</asx:abap>

 

 

 

And this is how my implementation looks like:

 

 

 

def Message processRequestData(message) {

       message.setHeader("Content-Type", "application/atom+xml");

       message.setHeader("x-requested-with", "XMLHTTPRequest");

  

            // convert OData request body to a string that the backend REST service understands

       String odataRequestBody = message.getBody(String.class);

  

       odataRequestBody = odataRequestBody.replaceAll("<Companies>", "<asx:values>");

       odataRequestBody = odataRequestBody.replaceAll("</Companies>", "</asx:values>");

       odataRequestBody = odataRequestBody.replaceAll("<Company>", "<COMPANY>");

       odataRequestBody = odataRequestBody.replaceAll("</Company>", "</COMPANY>");

       odataRequestBody = "<asx:abap version=\"1.0\">" + odataRequestBody +"</asx:abap>";

  

       message.setBody(odataRequestBody);

       return message;

}

 

 

 

Note:

If your backend requires additional handling, then here’s the right place to do it.

For example, my backend requires the x-requested-with header to be set.

 

Note:

I’m doing it with string operations, because I assume that it is easier to see what is happening there.

 

If you deploy your service after implementing the processRequestData() method, then your service will fail – however, the entry will be created in the backend, because the request is being sent and it is properly implemented.

 

But we have to implement the processResponseData() method as well, because we have to return the newly created entity.

In this tutorial we assume that the backend REST service returns the newly created entity.

If this is the case, we have to implement the processResponseData() method just the same as we did for the READ operation.

 

Since you can really just copy&paste the code from the READ script, I’m skipping the explanation here.

 

 

 

def Message processResponseData(message) {

  

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

  

       /* CONVERT JUST AS YOU DID IN READ operation */

       convertPayload(bodyString, message);

  

       return message;

}

 

 

After you’ve properly implemented the processResponseData() method, you can proceed with generate&deploy, then configure the service on SMP and invoke the service.

 

 

 

Result

 

In order to test the CREATE operation, we need to use a REST client tool.

 

Note:

Such REST clients are available e.g. as add-ons for Firefox (e.g. “RESTClient”) or Chrome (e.g. “Advanced Rest Client”) browsers

Please find below a screenshot of my POST request to the OData service created in the current tutorial.

 

 

 

 

 

 

 

Please find below the details of this request:

 

 

Header 1Header 2
Request URLhttps://<yourSMP>:8083/gateway/odata/<yourNameSpace>/<yourService>;v=1/<yourEntitySet>
HTTP verbPOST
x-csrf-token headeras provided by your SMP (see note below)
Content-Type header
application/atom+xml
Request body

<entry xmlns="http://www.w3.org/2005/Atom"

          xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"

          xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"

          xml:base="https://localhost:8083/gateway/odata/SAP/RESTPROJECT10_CREATE;v=1/">

  <content type="application/xml">

    <m:properties>

      <d:ID>40</d:ID>

      <d:NAME>Indian IT Trading Company</d:NAME>

      <d:STREET>Nariman Point</d:STREET>

      <d:POSTAL_CODE>400021</d:POSTAL_CODE>

      <d:CITY>Mumbai</d:CITY>

      <d:COUNTRY>IN</d:COUNTRY>

      <d:WEB_ADDRESS>http://www.it-trade.in</d:WEB_ADDRESS>

      <d:PHONE_NUMBER>4203876954</d:PHONE_NUMBER>

    </m:properties>

  </content>

</entry>

 

 

 

The screenshot also shows the response that we get after sending the request.

The HTTP status code should be 201

The response body should display the created entity

 

The backend REST service should as well display the newly created resource.

 

If you’ve got this response: congratulations, you’re through!

If not, I wish you quite some patience with trouble-shooting...

 

 

Note:

Regarding the x-csrf-token header:

I assume that you know how to obtain this token?

You have to first send a GET request to a valid URL, e.g. the EntitySet URL

This GET request should be executed with the header

x-csrf-token: fetch

(Name of the header is “x-csrf-token” and the value of the header is “fetch”)

 

After executing this request in your REST client, you should check the response headers.

You’ll find the header x-csrf-token with a value which is the token

Now copy the value and paste it into the request header, such that you have

x-csrf-token: <value of the token from the response>

 

Note:

Regarding fetching the header:

If you proceed as described above and don’t get the token then the reason is that it is always sent only once.

In order to force SMP to send it again, just logout from Gateway Management Cockpit and login again

 

 

 

Summary

 

In this tutorial, we’ve learned how to implement the CREATE operation in a Groovy script.

We’ve seen that there are no surprises:

  • The processResponseData() method is the same as in READ
  • The processRequestData() method is again the same, just the other way round
  • “Content-Type” header is required in the script.
  • The Relative URL in the Eclipse-Binding-Wizard is the same as for the QUERY

 

 

 

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 2: Understanding the return structure in json

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-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

 

Introduction in REST data source part 7: Implementing the READ operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-understanding-rest-data-source-7-read--xml

 

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

Integration Gateway: Understanding REST data source [10]: UPDATE operation

$
0
0

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

 

It is a follow-up of the previous blog, where I explained how to implement the CREATE operation for your OData service, based on a REST data source.

 

Since SMP SP07, the UPDATE operation is as well supported by Integration Gateway.

 

If you’re new to the topic, check the Links section below where you can find the tutorials which get you up to speed.

 

 

 

Prerequisites

 

I expect that you've gone through my previous tutorials, explaining REST data source – QUERY and READ and CREATE operation – based on XML payload.

Please check the Links section for the relevant blogs.

 

 

Furthermore, you need:

 

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

 

 

Preparation

 

REST Service

 

For this tutorial, we need a REST service that supports writing scenario. I’m using a service that is not public, I hope this blog will be useful nevertheless.

I’ll post an update if that service becomes public some day in future…

 

 

OData Model

 

The OData model for this tutorial is the same like in the previous tutorial:

 

 

 

 

 

 

 

You can simply continue with the project that was created for the previous tutorial.

 

Bind data source

 

The Relative URI is the same like for the READ operation

 

/sap/rest/odata/address/companies/{ID}

 

 

The reason is:

When doing an UPDATE operation directly on the backend REST service, we use the following URL:

https://<host>:<port>/rest/addressbook/companies/121

Since the number, which points to the company that has to be updated, is not fix, it has to be replaced by a variable.

The name of the variable has to be the name of the OData-property that identifies the company.

In our OData model, it is the key field, with name ID

That’s how we compose the last segment of the Relative URI, to be entered in the binding wizard in Eclipse:  /{ID}

 

 

 

 

 

 

Custom Code

 

Now generate the Custom Code for Groovy.

 

For the UPDATE operation, we have to implement only the processRequestData() method.

Why?

Because the OData specification doesn’t require that the UPDATE operation returns the payload of the modified resource.

Thus, since the response of our OData service will be empty for the UPDATE, we don’t have to provide anything in the processResponseData() method.

 

 

Implement processRequestData() method

 

Here I have some good news for you:

The implementation of the processRequestData() method of the UPDATE operation is exactly the same like for the CREATE operation.

Yes, you can just copy&paste the code (which of course leads to other questions, sure, how to avoid copy and paste).

 

So here’s again my implementation, based on string-operation (which looks better for demonstration)

 

 

 

def Message processRequestData(message) {

       message.setHeader("Content-Type", "application/atom+xml");

       message.setHeader("x-requested-with", "XMLHTTPRequest");

   

             // convert OData request body to a string that the backend REST service understands

       String odataRequestBody = message.getBody(String.class);

   

       odataRequestBody = odataRequestBody.replaceAll("<Companies>", "<asx:values>");

       odataRequestBody = odataRequestBody.replaceAll("</Companies>", "</asx:values>");

       odataRequestBody = odataRequestBody.replaceAll("<Company>", "<COMPANY>");

       odataRequestBody = odataRequestBody.replaceAll("</Company>", "</COMPANY>");

       odataRequestBody = "<asx:abap version=\"1.0\">" + odataRequestBody +"</asx:abap>";

   

       message.setBody(odataRequestBody);

       return message;

}

 

 

 

Result

 

In order to test the UPDATE operation, proceed as described in the previous tutorial.

The response status code should be 204 and the response body should be empty, as can be seen in the screenshot below.

 

 

 

 

 

 

Summary

 

In this tutorial, we’ve learned how to implement the UPDATE operation in a Groovy script.

We’ve seen that it’s easy:

The processRequestData() method is the same as in CREATE

 

 

 

 

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 2: Understanding the return structure in json

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-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

 

Introduction in REST data source part 7: Implementing the READ operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-understanding-rest-data-source-7-read--xml

 

Introduction in REST data source part 9: Implementing the CREATE operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/04/21/integration-gateway-understanding-rest-data-source-9-create-operation

 

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/

Integration Gateway: Understanding REST data source [11]: DELETE operation

$
0
0

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

It is a follow-up of the previous blog, where I explained how to implement the CREATE operation for your OData service, based on a REST data source.

Since SMP SP07, DELETE operation is supported by Integration Gateway.

 

If you’re new to the topic, check the Links section below where you can find the tutorials which get you up to speed.

 

 

 

Prerequisites

 

I expect that you've gone through my previous tutorials, explaining REST data source – QUERY and READ and CREATE operation – based on XML payload.

Please check the Links section for the relevant blogs.

 

Furthermore, you need:

 

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

 

 

 

Preparation

 

REST Service

 

For this tutorial, we need a REST service that supports writing scenario. I’m using a service that is not public, I hope this blog will be useful nevertheless.

I’ll post an update if that service becomes public some day in future…

 

 

 

OData Model

 

The OData model for this tutorial is the same like in the previous tutorial:

 

 

 

You can simply continue with the project that was created for the previous tutorial.

 

 

Bind data source

 

The Relative URI is the same like for the READ operation

 

/sap/rest/odata/address/companies/{ID}

 

 

Please check my previous tutorials for explanation about how to compose the Relative URI

 

 

 

 

 

 

 

 

Custom Code

 

Generate the Custom Code for Groovy.

 

For the DELETE operation, we don’t have to implement anything.

Why?

In the processRequestData() method, there’s no special requirement regarding request header or request body

In the processResponseData() method, no response body will be returned, according to OData specification.

 

 

Note:

If your backend requires additional handling, then of course you have to provide the implementation here

For example, my backend REST service requires the x-requested-with header to be set, while sending the request.

Therefore, I’m adding the following line to the processRequestData() method

 

 

 

def Message processRequestData(message) {

    message.setHeader("x-requested-with", "XMLHTTPRequest");

       return message;

}

 

 

 

Result

 

In order to test the DELETE  operation, proceed as described in the previous tutorial.

The response status code should be 204 No Content and the response body should be empty, as can be seen in the screenshot below.

 

 

 

Summary

 

In this tutorial, we’ve learned how to implement the DELETE operation in a Groovy script.

We’ve seen that it’s easy, as no implementation is required to fulfill the task

 

 

 

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 2: Understanding the return structure in json

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-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

 

Introduction in REST data source part 7: Implementing the READ operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-understanding-rest-data-source-7-read--xml

 

Introduction in REST data source part 9: Implementing the CREATE operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/04/21/integration-gateway-understanding-rest-data-source-9-create-operation

 

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/

CSI tool helps you in debugging Authentication Errors

$
0
0

You might have faced authentication errors many times while accessing an application id through SAP Mobile Platform and just wondered if there is some tool to troubleshoot the error apart from checking the security log. Yes, there is a way you can debug authentication errors against an authentication provider. Here, i will be using a tool called CSI (Common security infrastructure) tool.

 

Note: This guide is mainly for System Administrators and can be tested on same machine where SMP server is installed. I have tested it on Windows.

 

(I followed this guide but unfortunately it didn't work as per steps mentioned)

 

You can find 3 different security profiles (admin, default and Notification) present under SETTINGS > SECURITY PROFILES in Admin cockpit and for all these profiles you can see two different .xml file.

e.g. For Admin, admin.xml and admin-role-mapping.xml

      For default, default.xml and role-mapping.xml

      For Notification, Notification-role-mapping.xml, Notification-role-mapping.xml

 

Example 1:

 

Lets go with Admin profile first, we got a default username as smpAdmin& password as s3pAdmin (might be different in your case). Lets assume you are facing authentication error while accessing an application through the Admin profile. This CSI tool will help you in debugging the error:

 

Steps to be followed:

 

1. Create a temporary folder somewhere on server machine

2. copy below files into same folder

  • csi-tool.jar file from C:\SAP\MobilePlatform3\Server\tools\csi
  • csibootstrap.properties and csikeystore.jceks files from C:\SAP\MobilePlatform3\Server\configuration\com.sap.mobile.platform.server.security
  • admin.xml and admin-role-mapping.xml from C:\SAP\MobilePlatform3\Server\configuration\com.sap.mobile.platform.server.security\CSI

 

It should like this:

     1.PNG

 

4. Open admin.xml file, modified value for RoleMapFile as mentioned below

 

     2.PNG

 

5. Open a command prompt > Navigate to temporary folder and run this

 

java -Dcom.sybase.security.BootstrapConfigurationFile="C:\Users\Jitendra\Desktop\CSItest\csibootstrap.properties" -cp csi-tool.jar;C:\SAP\MobilePlatform3\Server\plugins\* -Djava.util.logging.config.file=logging.properties com.sybase.security.tools.CSILauncher csi.diag.authenticate --USERNAME "smpAdmin" --PASSWORD "s3pAdmin" --CONFIG_FILE C:\Users\Jitendra\Desktop\CSItest\admin.xml

You can a success message about true authentication.

3.PNG

Let me try passing wrong password

 

     4_error.PNG

 

You can try the same for other security profile(s) as well following steps 1-5

 

Example 2:

 

I have created a new security profile named as SAP_SSO2. You can see that there are 2 different xml files created for same profile: ie. SAP_SSO2.xml and SAP_SSO2-role-mapping.xml under C:\SAP\MobilePlatform3\Server\configuration\com.sap.mobile.platform.server.security\CSI

 

     ldap1.PNG

 

I have copied the same files into that temporary folder and modified the SAP_SSO2.xml file as per step 4.

 

5.PNG

 

java -Dcom.sybase.security.BootstrapConfigurationFile="C:\Users\Jitendra\Desktop\CSItest\csibootstrap.properties" -cp csi-tool.jar;C:\SAP\MobilePlatform3\Server\plugins\* -Djava.util.logging.config.file=logging.properties com.sybase.security.tools.CSILauncher csi.diag.authenticate --USERNAME "p1176845" --PASSWORD "******" --CONFIG_FILE C:\Users\Jitendra\Desktop\CSItest\SAP_SSO2.xml

 

(I have entered wrong password)

 

 

          sso2_incorrect.png

It is throwing '401 unauthorized' error as expected.

 

Note: This method is also quite valid for a security profile having more than one authentication providers.

 

Enable security logs for more troubleshooting. Set log level to DEBUG (Admin cockpit > Logs > Settings >Security)

 

Regards,

JK


Integration Gateway: Understanding REST data source [12]: Navigation

$
0
0

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

The OData specification describes the concept of navigation, in order to easily navigate from one resource to another one.


REST services can also support a kind of “navigation”, where the result set of one resource depends on another resource.

If we use such a REST service as data source in our Integration Gateway project, we can add the navigation capability to our OData service.

 

In this tutorial, I’d like to show an example for an easy-to-implement bidirectional navigation.

This tutorial is based on SMP SP07.

 

 

 

Prerequisites

 

I expect that you've gone through my previous tutorials , explaining REST data source, QUERY  and READ  operation.

Please check the Links section for more info.

 

Furthermore, you need:

 

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

 

 

 

Preparation

 

 

REST Service

 

For this tutorial, we need a REST service with at least 2 resources that supports that kind of “navigation”.

I’m using a service that is not public, I hope this blog will be useful nevertheless.

I’ll post an update if that service becomes public some day in future…

 

Let’s have a look at how the backend-REST-service is used.

 

First, we invoke the URL for the “companies”:

 

https://<host>:<port>/rest/addressbook/companies

 

The result is a list of “companies”:

 

 

We can now choose one single “company”:

 

https://<host>:<port>/rest/addressbook/companies/2

 

The “company” is uniquely identified by the ID field, which is in the URL.

 

 

 

Now, this REST-service allows to add a segment to the URL, in order to retrieve the “contacts” that are responsible for this “company”:

 

https://<host>:<port>/rest/addressbook/companies/2/contacts

 

The result is a list of “contacts”, since there can be more than one “contact” for a “company”.

This is a one-to-many relationship.

In the details of a “contact”, we can see the ID of the “company” for which he is responsible.

 

 

 

After navigating from a “company” to its “contacts”, let’s now check the other way ‘round:

Open one single “contact”

 

https://<host>:<port>/rest/addressbook/contacts/46

 

 

 

Go to the “company” for which this “contact” is responsible:

 

https://<host>:<port>/rest/addressbook/contacts/46/company

 

Here we have a many-to-one relation, so the result is a single entry.

This company is the same that we’ve seen above.

 

 

 

 

Eclipse Project

 

 

We need a SAP Mobile Platform OData Implementation Project that already has the QUERY and READ operations implemented for both resources of the backend-REST-service.

And it should be up and running on our SMP server.

 

 

 

OData Model

 

The 2 EntityTypes are modeled according to the 2 resources of the backend REST service.

Additionally, we have an association between them.

Note that is has to be a bidirectional association, because we want to navigate from the "Company" to the "Contacts" and as well from a "Contact" to its "Company".


 

 

 

 

As we’ve seen above, a company can have many contacts, but a contact can only belong to one company.

So we specify a one-to-many relation.


 

 

 

 

And we create a Referential Constraint, which maps the property ID (of the Company) to the property COMPANY_ID (of the Contact)

 

 

 

 

 

That’s it for the modelling.

 

Next, we create the bindings and the Custom Code for QUERY and READ operations for both EntitySets.

Since this is explained in my previous blogs, we can skip all explanations about it.

 

 


Custom Code

 

 

After testing the QUERY and READ operations of our OData service at runtime, we can start taking care about the navigation.

 

Background

 

We invoke the URL for a company, e.g. https://localhost:8083/gateway/odata/<ns>/<service>/Companies('2')

In the response we can see that the Integration Gateway framework has generated a link element:

 

 

 

 

What happens, if we invoke that link?

https://localhost:8083/gateway/odata/<ns>/<service>/Companies('2')/ContactSet

 

Let’s try it.

 

 

Implementing the navigation for one-to-many relation

 

First we have to understand, that our script for the contact-QUERY is invoked.

The last segment of the URL is the ‘ContactSet’, which means that a collection of contacts is requested.

Therefore, when navigating from company to contacts, the contact-QUERY script is invoked (the default name Contacts_REST_Query.groovy)

Note that this is the case because we have a 1-to-many association between Company and Contact

 

First, the processRequestData() method is invoked.

And here we have to do the actual work.

The Integration Gateway framework doesn’t know how the backend REST service is designed.

Integration Gateway only knows that the user requested “give me the contacts which are relevant for the-company-with-ID-2”

But it doesn’t know how to get exactly this amount of data.

 

Therefore, we have to tell, how this is done, because we do know it: the URI has to look as follows:

companies/<valueOfTheID>/contacts

And we tell it in the processRequestData() method.

 

Summarizing:

Our task is to modify the backend-REST-service-URI that is called by Integration Gateway.



Implementing the processRequestData() method

 

We’re already familiar with such tasks.

We hock into the HTTP request, before it is fired agains the backend-REST-service.

In the processRequestData() method, we have to obtain the URI and modify it.

 

Note:

Our contact-QUERY script is invoked in both cases, with navigation and without navigation.

E.g.

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Contacts

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Companies('2')/ContactSet

 

Both URLs lead to our script being invoked.

So we have to consider it in our implementation.

 


Steps that have to be performed:

 

1) Obtain the UriInfo object

We need it in order to get the information about the navigation, which is in the URL.

 

     a) We need to know from where the navigation comes.

         In our example, we have only 2 EntityTypes, so the navigation starts always from a “Company”, but for other services it would be different.

         This is realized with the method UriInfo.getStartEntitySet()

 

     b) We need to know the ID of the “Company”, because depending on it, the amount of “Contacts” is different

        This information is contained in the UriInfo.getKeyPredicates()

 

2) Obtain the relative URI

We need it, in order to modify it.

 

This is how the URL that is invoked by the user of our OData service:

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Companies('2')/ContactSet

 

and this is how it has to look like when the backend-REST-service is called:

https://<host>:<port>/rest/address/companies/2/contacts

 

3) Set the relative URI

After correcting the relative URI, it has to be given back to Integration Gateway.

This is done by setting the header in the message object

 

And here’s the code snippet for our example.

 

 

def Message processRequestData(message) {

 

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

 

       // check if navigation is in place

    List<NavigationSegment> navSegments = uriInfo.getNavigationSegments();

       if(navSegments.size() < 1){

             // no navigation, just normal getEntitySet operation

             // this is the case for the call to

        //<...>localhost:8083/gateway/<...>/Contacts

             return message; // do nothing

    }elseif (navSegments.size() > 1){

               /* this would be the case for e.g.

         * <...>/<srv>/Companies('2')/ContactSet('46')/Company/ContactSet

         * here the number of navSegments would be: 3

         * */

        log.logErrors(LogMessage.TechnicalError, "Not supported");

              return message; // ignore it for today

     }

 

 

       /* in our example, for the URL 

    * <...>localhost:8083/gateway/<...>/Companies('2')/ContactSet

    * the number of navSegments is: 1

    * */

       // handle the navigation

    String startEntitySetName = uriInfo.getStartEntitySet().getName();

 

       // we know that our EntityType has only one key field

    KeyPredicate keyPredicate = uriInfo.getKeyPredicates().get(0);

    String keyLiteral = keyPredicate.getLiteral();

 

       /* in our example, for the URL 

    * <...>localhost:8083/gateway/<...>/Companies('2')/ContactSet

    * the value of keyLiteral will be: 2

    * */

 

       // this is not really required for our service that has only 2 EntityTypes:

       if(startEntitySetName.equals("Companies")){ 

     

       String relativeUri = (String)message.getHeaders().get("RelativeUri");

             /* in our example, the value is: 

        * /rest/addressbook/contacts

       */

 

             // here we're doing the actual work: modify the URI

       String targetRESTuri = relativeUri.replace(

                                 "contacts", "companies/" + keyLiteral + "/contacts");

     

             // finally, set the manipulated REST-service-URI for the navigation

       ((Message)message).setHeader("RelativeUri", targetRESTuri);

            /* in our example, the final URI has to look like this:

       *  /rest/addressbook/companies/2/contacts

       */

    }

 

       return message;

}

 

 

 

Implementing the processResponseData() method

 

In terms of navigation, nothing has to be done here.

This method is already implemented for the QUERY operation, we don’t need to change or add anything.

 

 

 

Result

 

After deploy and configure the service, the navigation can be tried via a URL like this:

https://localhost:8083/gateway/odata/<yourNamespace>/<yourService>/Companies('2')/ContactSet

The result should be the same like in the backend:

https://<host>:<port>/rest/address/companies/2/contacts

 

 

Implementing the navigation for many-to-one relation

 

The first navigation example was for the 1-to-many relationship.

 

My sample backend-REST-service also supports to ask a single “contact” for the “company” that he is responsible for.

This is a many-to-1 relationship.

In our OData model, we’ve described this with a bidirectional association.

The OData modeler tool has generated a NavigationProperty called “Company” in the EntityType “Contact”.

Which means that we can invoke a URL like this:

https://localhost:8083/gateway/odata/<ns>/<service>/Contacts('46')/Company

 

Which corresponds to a backend-REST-service-URL like this:

https://<host>:<port>/rest/address/contacts/46/company

 

The implementation is very similar, the difference is:

Since here we have a many-to-1 relation, the script that is called is the Company-READ.

 

Again the response doesn’t need to be changed, it is just the READ implementation that is used to represent the target entry.

As for the request, the implementation is very similar like above

 

 

 

def Message processRequestData(message) {

 

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

   List<NavigationSegment> navSegments = uriInfo.getNavigationSegments();

   if(navSegments.size() == 1){

    String startEntitySetName = uriInfo.getStartEntitySet().getName();

    String keyLiteral  = uriInfo.getKeyPredicates().get(0).getLiteral()

     

       if(startEntitySetName.equals("Contacts")){

         // in our example: /rest/addressbook/companies/46

      String relativeUri = (String)message.getHeaders().get("RelativeUri");

            

         // modify the URI

         // in our example, we need: /rest/addressbook/contacts/46/company

     String targetRESTuri = relativeUri.replace("companies", "contacts") + "/company";

 

         // finally, set the manipulated REST-service-URI for the navigation

      ((Message)message).setHeader("RelativeUri", targetRESTuri);

    }

  }

 

  return message;

}






Summary

 

In this tutorial, we’ve learned how to realize navigation between 2 entities, based on REST data source.

It has been easy to realize, because the used backend-REST-service supported a kind of navigation that was easy to adapt to the OData way.

For other REST services it might be more tricky.

 

 

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 2: Understanding the return structure in json
http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/02/11/integration-gateway-understanding-rest-data-source-2-query--json-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

 

Introduction in REST data source part 7: Implementing the READ operation

http://scn.sap.com/community/developer-center/mobility-platform/blog/2015/03/06/integration-gateway-understanding-rest-data-source-7-read--xml

 

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/

Granting role-based access in SAP Mobile Platform 3.0

$
0
0

Hello SMP Administrators,

 

Let me start with an example. There are 3 users: A, B &C . SAP Mobile Platform administrator wants to grant admin role to user A, read-only access role (ie.Helpdesk) to user B and notification role to user C. He find this task very easy & simple. He logins to admin cockpit, adds two authentication providers System Login (Admin only) in admin security profile, provides Administrator and Helpdesk roles for A & B respectively. For User C, he adds a new System Login authentication provider but in Notification security profile. His job is done. User A got admin access to Admin cockpit, User B read access and User C can get notification via SMP.

     Untitled.png

Now, SMP Administrator has asked to grant admin roles to 3 more users, read-only access role to 10 more users and notification role to 20 more users.

But this time SMP administrator doesn't want to repeat what he has done before. He doesn't want to do it manually and wants to do it in a general way so that if in future, he gets any more requests for granting roles, he doesn't have to do much on SMP. He came to know that there is LDAP setup installed at organization. Could be like below:

 

     ldapstru.PNG

 

He thought of implementing LDAP/AD authentication provider in SMP security profile and mapping to the LDAP groups to which a user belongs.

 

    

 

Implementing LDAP/AD authentication provider in SMP Admin security profile:

 

  1. Login to Admin Cockpit > Settings > Security profiles > Admin (Cannot be deleted) > Edit
  2. Add a new authentication provider "Directory Serive (LDAP/AD)"

(below settings are as per above LDAP setup..there could be changes as per yours)

     ldap.png

 

Creating Users & groups in LDAP

 

1. I have added user a, b & c and created 3 different groups smp_admin_grp, smp_helpdesk_grp & smp_notification_grp.

2. Assigned a , b & c as uniqueMember in respective groups.

 

ldaprole.png

Modifying admin-role-mapping xml file

 

 

All predefined logical roles are there in SMP admin-role-mapping.xml file. Now we need to map newly created physical roles to predefined logical roles. Open admin-role-mapping.xml file can be found under C:\SAP\MobilePlatform3\Server\configuration\com.sap.mobile.platform.server.security\CSI .

 

Alert : Take a backup of same file before modifying it.

Map physical roles to predefined logical roles (as highlighted in bold)

<?xml version="1.0" encoding="UTF-8"?>

<rm:Mappings xmlns:rm="http://www.sybase.com/csi/3.1/mapping">

    <DefaultMapping>

        <LogicalName>Administrator</LogicalName>

        <MappedName>Administrator</MappedName>

        <MappedName>smp_admin_grp</MappedName>

    </DefaultMapping>

 

       <!-- Avatar Deployer Role Mappings -->

       <DefaultMapping>

             <LogicalName>NodeManager.deploycontent</LogicalName>

             <MappedName>Administrator</MappedName>

       </DefaultMapping>

       <DefaultMapping>

             <LogicalName>GenerationAndBuild.generationandbuildcontent</LogicalName>

             <MappedName>Administrator</MappedName>

       </DefaultMapping>

 

       <DefaultMapping>

             <LogicalName>IntegrationOperationServer.read</LogicalName>

             <MappedName>Administrator</MappedName>

             </DefaultMapping>

    <DefaultMapping>

        <LogicalName>Developer</LogicalName>

        <MappedName>Developer</MappedName>

    </DefaultMapping>

 

    <DefaultMapping>

        <LogicalName>Helpdesk</LogicalName>

        <MappedName>Helpdesk</MappedName>

        <MappedName>smp_helpdesk_grp</MappedName>

       </DefaultMapping>

 

    <DefaultMapping>

        <LogicalName>Notification User</LogicalName>

        <MappedName>Notification User</MappedName>

    </DefaultMapping>

 

    <DefaultMapping>

        <LogicalName>Impersonator</LogicalName>

        <MappedName>Impersonator</MappedName>

    </DefaultMapping>

</rm:Mappings>

 

 

Note: For Notification user role, you have to add a new authentication provider in Notification (cannot be deleted) security profile > Add> Directory service (LDAP/AD)

 

               ldapnotificaiton.png

 

     Once done, open Notification-role-mapping.xml file (C:\SAP\MobilePlatform3\Server\configuration\com.sap.mobile.platform.server.security\CSI) and map notification physical role to logical role as highlighted below.

 

<?xml version="1.0" encoding="UTF-8"?>

<rm:Mappings xmlns:rm="http://www.sybase.com/csi/3.1/mapping">

    <DefaultMapping>

        <LogicalName>Administrator</LogicalName>

        <MappedName>Administrator</MappedName>

    </DefaultMapping>

 

    <DefaultMapping>

        <LogicalName>Developer</LogicalName>

        <MappedName>Developer</MappedName>

    </DefaultMapping>

 

    <DefaultMapping>

        <LogicalName>Helpdesk</LogicalName>

        <MappedName>Helpdesk</MappedName>

    </DefaultMapping>

 

    <DefaultMapping>

        <LogicalName>Notification User</LogicalName>

        <MappedName>Notification User</MappedName>

        <MappedName>smp_notification_grp</MappedName>

    </DefaultMapping>

 

    <DefaultMapping>

        <LogicalName>Impersonator</LogicalName>

        <MappedName>Impersonator</MappedName>

    </DefaultMapping>

</rm:Mappings>

      

 

 

Few things to know

 

  1. By default, each logical role name is mapped to a physical role of the same name.

 

     <DefaultMapping>

     <LogicalName>Administrator</LogicalName>

     <MappedName>Administrator</MappedName>

     </DefaultMapping>

 

   2. By default, Admin security profile assigns smpAdmin user to the Administrator role. (Admin Cockpit credentials)

   3. As per documentation,

SMP includes <MappedName>Administrator</MappedName> in admin-role-mapping.xml, if you do not have a physical role/group called Administrator, delete this mapping from file to avoid unnecessary authorization checks and improve performance.

 

I agree with this point. But imagine if i remove this default mapping and if LDAP server is down, i will not even able to login with smpAdmin. In my opinion, let it be there, no need to remove.

4. You can configure security profiles in Management cockpit but role-mapping configuration has to be done manually by editing .xml file.

5. In SMP cluster, you can configure security profile from any active node, once you are done with changes (either in Management cockpit or editing .xml file or both), CSI pushes this changes to the shared database, which then propagates the changes to the cluster nodes.

6. Helpdesk role is usually granted to analyze root causes of issues/problems. Cannot perform any administrator related tasks.

7. There are also predefined Integration Gateway roles and these roles are mapped to Administrator logical role. But there is no read-only access role defined for Gateway management cockpit.

 

<!-- Avatar Deployer Role Mappings -->

       <DefaultMapping>

             <LogicalName>NodeManager.deploycontent</LogicalName>

           <MappedName>Administrator</MappedName>

    <MappedName>smp_admin_grp</MappedName>

       </DefaultMapping>

  

       <DefaultMapping>

             <LogicalName>GenerationAndBuild.generationandbuildcontent</LogicalName> in case of generate & deploy integration content to SMP

           <MappedName>Administrator</MappedName>

   <MappedName>smp_admin_grp</MappedName>

       </DefaultMapping>

 

 

<DefaultMapping>

             <LogicalName>IntegrationOperationServer.read</LogicalName> >>> needed in case of connecting to SMP server from eclipse kepler

          <MappedName>Administrator</MappedName>

   <MappedName>smp_admin_grp</MappedName>

</DefaultMapping>

 

8. The Developer role appears in the role-mapping.xml file, but is not implemented in SAP Mobile Platform.

 

I hope you find above information useful. Feel free to comment in case of any clarification and feedback.

 

CC: SAP for Mobile

Regards,

JK

command line also helps you in converting JDBC JAR to OSGi JAR to use with Integration Gateway in SMP

$
0
0

Hello SMP Administrators,

 

Do you want to expose database information into OData services? Integration gateway will help you. It will convert the data coming from the database to OData. As SMP is based on OSGi, you can not directly deployed JDBC driver to SMP server, it has to be OSGi enabled jar package.

 

There are two different ways to convert JDBC JAR to OSGi JAR:

 

1.  Eclipse plug-in development

2. Command line approach >> Limited to Oracle, SQL server & DB2 drivers

 

I will explain only 2nd approach:

 

In this example, i want to create an OSGi enabled bundle which includes Microsoft JDBC driver for SQL server.

 

Steps followed:

 

1. Copy driver jar file to a temporary folder (SMP server should be installed on same machine)

2. Navigate to this path: C:\SAP\MobilePlatform3\Server\tools\dbconfig

3. execute below command

        

F:\SAP\MobilePlatform3\Server\tools\dbconfig>create_jdbc_bundle.bat -sqlserver C:\temp\sqljdbc4.jar

   

      sqlserver.PNG


You will see a new OSGi enabled jar file has been created in the mentioned folder.

 

          sql2.png

 

  4. Copy this new jar file and paste in PICKUP folder and make sure that you see an "OK" message under .state folder

 

     pickup.PNG

                   

As mentioned already,  there are only 3 supported drivers: SQLServer, Oracle & DB2.


-sqlserver

-oracle

-db2


So for other databases like SAP HANA, SAP ASE, SAP SQL Anywhere, HSQLDB, MYSQL you can not use above command line method to generate OSGi enabled jar file. You have to go with Eclipse plug-in development

 

Inputs taken from SAP Note 2146914 and big thanks to Andreas Wegmannfor providing extra information.

 

Regards,

JK

Customizing MAF Logon Component in Android

$
0
0

Developers can use MAF Logon component to implement quickly the logon UI logic needed to onboard users with SMP 3.0. Although this component is very functional and easy to implement, companies would rarely use its default settings in mobile clients to go live. In this blog will explore some of the settings that can be modified in the MAF Logon component.

 

Prerequisites

The use of this component requires some dependencies that are described in this documentHow To... Setup MAF resources in Android Studio  Follow along and you will find that in this document we got an instance of the LogonUIFacade class and initialize it in the onCreate method of the LogonActivity as shown in the following code snippet


Code Snippet - initialize MAF Logon Component (with default settings)

 

   @Override

       protectedvoid onCreate(Bundle savedInstanceState) {

              super.onCreate(savedInstanceState);

              // set context reference

        Context mContext = this;

              // get an instance of the LogonUIFacade

        LogonUIFacade mLogonUIFacade = LogonUIFacade.getInstance();

              //Initialize the Logon UI Facade

              mLogonUIFacade.init(this, mContext, <name of your application id>);

              // ask LogonUIFacede to present the logon screen

              // set the resulting view as the content view for this activity

        setContentView(mLogonUIFacade.logon());

       }

 

As a result, the mobile client will display the default screen flow of the MAF Logon component

customMAFOriginal.jpg

 

Now with few more lines of codes in the onCreate method, you can customize the MAF logon component to simplify the registration process

 

Step 1-2: Hide MobilePlace Window and then initialize MAF Logon component

In order to hide the MobilePlace window we would need to get the logon core shared preferences settings and modify the mobile place shared property before we get an instance of the LogonUIFacade class.

 

Code Snippet - Hide MobilePlace window and initialize LogonUIFacade class


//STEP1: Hide MobilePlace window

SharedPreferences prefs = getSharedPreferences(LogonCore.PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);

SharedPreferences.Editor pEditor = prefs.edit();

pEditor.putBoolean(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_MOBILEPLACE.toString(), false);

pEditor.commit();

 

//STEP2: Get an instance of the LogonUIFacade and initialize it as in the original code

LogonUIFacade mLogonUIFacade = LogonUIFacade.getInstance();

mLogonUIFacade.init(this, this, "<your application id>");


 

Step 3: Set Default Values in login fields

Mobile clients require the SMP server URL and port in order to onboard the user with SMP 3.0. However, in most cases companies would like to set these fields with default values and hide them from the final user.  Developers can set default values with the setDefaultValue methods of the LogonUIFacade class as shown following code snippet.

 

Code Snippet - Set default values


//STEP3: Set Default values

mLogonUIFacade.setDefaultValue(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_SUPSERVERURL, "<your smp server host>");

mLogonUIFacade.setDefaultValue(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_SUPSERVERPORT, "<your smp server port>");

//type "true" if the mobile client is using HTTPS, type "false" otherwise

mLogonUIFacade.setDefaultValue(SharedPreferenceKeys.PREFERENCE_ID_HTTPSSTATUS, "<true|false>");


 

Step 4: Hide fields in the login details

Generally, users only know their username and passwords. Developers may want to hide the rest of the fields to simplify the registration process. By default, all fields are displayed, hence you only need to specify those fields that are going to be hidden as shown below.

 

Code Snippet - Hide fields


// STEP4: Hide Login details

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_SUPSERVERURL, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_SUPSERVERPORT, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_SECCONFIG, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_SUPSERVERFARMID, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_URLSUFFIX, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_MOBILEUSER, true); mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_ACTIVATIONCODE, true); mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_HTTPSSTATUS, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_GATEWAYCLIENT, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_SUPSERVERDOMAIN, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_PINGPATH, true);

mLogonUIFacade.isFieldHidden(LogonCore.SharedPreferenceKeys.PREFERENCE_ID_GWONLY, true);


 

After these changes, the new mobile client will display the screen flow of the MAF Logon component  as follows

customMAFModified.jpg

 

There are ways to skip the splash screen; however doing so overrides the settings we just had changed. I’ll investigate further the required changes in the splash screen and write a blog about it.

 

Hope this helps,

Claudia

New features for Windows in SAP Mobile Platform SDK - Part 1 (LogonFlow control)

$
0
0

It’s been a while since my last blog on SAP Mobile Platform 3.0 SDK (hereinafter referred to as “SMP SDK” or “SDK”).  A lot of new features have been added to the Windows SMP SDK in a short period of time. Chief among them is offline support for Windows.  I will be writing a series of 6 blogs talking about the various new features that have been included in the Windows SMP SDK.  Sample applications that illustrate the various new features will also be included along with the blogs…

 

This series of 6 blogs will cover the following topics.

 

On-boarding process

A key requirement in the use of SAP Mobile Platform runtime (hereinafter referred to as “SMP Server”) is registering the device with the SMP Server.  This process of registering a device with the SMP Server is known as on-boarding the device.  Without the on-boarding process, the backend data source is agnostic of devices that are connected to it.  So important features like push notification, usage collection, device logging etc. cannot be implemented.  With SMP Server, it is mandatory for new devices to register with the SMP Server before any CRUD operations can be performed.  SMP Server therefore can tie every CRUD operation to a single device. From an administrative standpoint, this is extremely valuable.  Key features like push notification, usage collection, device logging etc. can now be implemented.

 

So, how do I on-board a new device?

On-boarding a new device is accomplished by sending an HTTP POST request with the device information.

 

 

URL:http://<hostname>:<port>/odata/applications/v1/<application id>/Connections

Method: POST

 

Body:

 

<?xmlversion='1.0'encoding='UTF-8'?>

<entryxml:base="http://127.0.0.1:8080/odata/applications/v1/com.sap.windows/"xmlns='http://www.w3.org/2005/Atom'xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'xmlns:d='http://schemas.microsoft.com/ado/2007/08/dataservices'>

  <categoryterm='applications.Connection'scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme'/>

  <contenttype='application/xml'>

    <m:properties />

  </content>

</entry>

 

 

In the first installment of Windows SMP SDK, the on-boarding process required the developer to create their own UI, understand the process flow and implement the logic behind the process flow.  While this is not hard, it can get quite time consuming (especially the UI part of the process flow).  With that in mind, the SAP developers created the LogonFlow control.  The LogonFlow control is a new user control (a user control is essentially a component with visual representation) that greatly simplifies the on-boarding process.  In the simplest of use cases, the developer can drag the LogonFlow control from the Toolbox in Visual Studio and assign a few required default values.  And on-boarding is complete!!

 

 

Adding the LogonFlow user control to the Toolbox

The LogonFlow control is packaged as a NuGet package and shipped with the SMP SDK.  Install the LogonFlow control NuGet package as a reference in your application from within Visual Studio.  NuGet Package Manager installs all dependent packages for you automatically.  In addition, the proper package for your platform is installed.

 

  • Rebuild the solution.
  • Right click on the Toolbox (View -> Toolbox if it’s not displayed) and select ‘Choose Items…’.

Logon1.png

  • Click Browse to select the library from the bin\debug folder.  Note that you must have already added reference to the NuGet packages.

Logon2.png

  • The LogonFlow control should now show up in your Toolbox.

Logon3.png

  • Drag and drop the LogonFlow control into your .xaml file.  The properties for the user control can be from the properties page just like any other user control.

Logon4.png

 

 

Using the LogonFlow control

In order to use the LogonFlow control, set the Name and ApplicationId property of the LogonFlow control.

Logon5.png

In the code behind .xaml.cs file, assign a few required default values and you are done.

logonFlow.DefaultValues = new SAP.Logon.Core.RegistrationContext()

{

   ApplicationId = "com.sap.windows.flight",

   ServerHost = "54.80.15.207",

   IsHttps = false,

   ServerPort = 8080,

   CommunicatorId = "REST"

};

Further customizations…

The LogonFlow control can be highly customized.  Let us look at some of the ways we can customize the LogonFlow control.

 

Adding a background image– This can be done by selecting the Background property either using the properties page or setting the value directly in the .xaml page. 

Logon6.png

 

 

 

<FlowEngine:LogonUI Name="logonFlow" ApplicationId="com.sap.windows.flight" Background="{StaticResource BackgroundImage}"></FlowEngine:LogonUI>

 

Adding a demo mode


<FlowEngine:LogonUI Name="logonFlow" ApplicationId="com.sap.windows.flight" HasDemoMode="True"></FlowEngine:LogonUI>

 

Option for end user to edit ApplicationId


<FlowEngine:LogonUI Name="logonFlow" ApplicationId="com.sap.windows.flight" CanEditApplicationId="True"></FlowEngine:LogonUI>

 

Enable Mobile Place


<FlowEngine:LogonUI Name="logonFlow" ApplicationId="flightbasic"MobilePlaceEnabled="True" ApplicationVersion="1.0" Email="demo@sap.com">

</FlowEngine:LogonUI>

 

You can also set the values directly in the Properties page as well…

Logon7.png

 

In addition, the properties of the LogonFlow control can be modified in the code behind file.

 

logonFlow.CanEditApplicationId = true;

 

Adding a splash screen

<FlowEngine:LogonUI ApplicationId="com.sap.windows.flight" Name="logonFlow"

                           Background="{StaticResource BackgroundImage}">

   <FlowEngine:LogonUI.Splash>

      <DataTemplate>

         <Grid Background="{StaticResource SplashBackgroundBrush}">

            <Viewbox StretchDirection="DownOnly" Margin="12">

               <StackPanel VerticalAlignment="Center">

                  <Image Source="/Assets/SAP_Logo.png" Stretch="None" HorizontalAlignment="Center"/>

                  <StackPanel HorizontalAlignment="Center">

                     <TextBlock Text="SAP Sample Application Using FlowEngine" HorizontalAlignment="Right"Foreground="#666666" FontSize="24 Margin="0,24,0,12"/>

                     <TextBlock Text="© 2014 SAP SE. All rights reserved." HorizontalAlignment="Right"Foreground="#666666" FontSize="12" />

                    </StackPanel>

               </StackPanel>

            </Viewbox>

         </Grid>

      </DataTemplate>

   </FlowEngine:LogonUI.Splash>

</FlowEngine:LogonUI>

 

 

LogonFlow control events

The LogonFlow control has 2 events that a developer can handle.  One of them is LogonCompleted.  The other is DemoModeSelected (applicable only if HasDemoModeProperty is set to true).

 

The event handler for the LogonCompleted event has 2 parameters.  The first parameter is the sender object.  In this case, it is the LogonFlow control itself.  The second parameter is the LogonCompletedEventArgs object.  The LogonCompletedEventArgs object has a LogonCore property.  Typically in the event handler you will save the LogonCore object for later user and navigate to the main page of the application.

 

logonFlow.LogonCompleted += (sender, e) =>

{

   Globals.LogonCore = e.LogonCore;

   this.Frame.Navigate(typeof(MainPage));

};

 

LogonFlow control screen flow

 

Logon8.png

 

As you can see, the LogonFlow control greatly simplifies the on-boarding process and at the same time introduces quite a few functionality without the developer having to spend any effort.

 

See you all in the next blog where I will be talking about push notification.

Viewing all 370 articles
Browse latest View live


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