Skip navigation

This blog is inspired by Geertjan's Blog on Track the Closest Beacon on Android via Cordova

In this blog, I will be using Oracle Mobile Cloud Services (MCS) version 16.3.x and above, Oracle JavaScript Extension toolkit (JET) hybrid cordova based application version 1.0.2 and Bluetooth low energy (BLE) beacon hardware device to demonstrate a sample use case to develop proximity based marketing and engagement for a retail store.

 

Using proximity marketing campaigns, retailers can keep consumers informed about the products, services, and actual special offers from each of the respective store sections. Retailers place these beacons in different store section which in turn continously broadcast a unique identifier that a Smartphone app can use to trigger appropriate action.

 

Key Concepts

Below tables summarizes the key components involved in developing proximity-based engagement

 

Components

Details

Beacon

Beacons use Bluetooth Low Energy transmissions to send out their identity. It basically broadcast four pieces of information:

  • UUID that identifies the beacon.
  • A Major number identifying a subset of beacons within a large group.
  • A Minor number identifying a specific beacon.
  • A TX power level in 2's compliment, indicating the signal strength few meter's from the device.

Oracle MCS

Oracle MCS Location services will be used to fetch list of devices by the given place ( latitude, longitude and radius)

Mobile app – Oracle JET Hybrid app

An application on the mobile phone picks up the nearby beacon information , and calculates its distance based on signal strength (txpower and rssi)

 

Proximity-based engagement flow

Below diagram shows the life-cycle flow for implementing a basic proximity-based engagement:

flow.png

 

 

Below are the main steps involved:

  1. Mobile application sends it’s current location – latitude, longitude and radius to Oracle MCS
  2. Oracle MCS sends back the beacon registry details and that get’s stored in the mobile application.
  3. Mobile application starts listening to these devices over Bluetooth
  4. Mobile application starts picking up signals from nearby beacons
  5. On getting micro-location and context ,the application triggers appropriate action

 

Below table summarizes key terms used:

Terms

Details

UUID

UUID stands for Universally Unique Identifier.  It contains 32 hexadecimal digits, split into 5 groups, separated by dashes , example: F4826da6-4fa2-4e98-8024-bc5b71e0893f

 

Component: Oracle Mobile Cloud Services (MCS)

The retail store decides to deploy iBeacon inside its store and make a mobile application that can tell the user once they arrive at a specific store, they would define a UUID that is unique to their app and the beacons inside their stores. Inside the stores, they would place beacon devices and configure each of them to use a different “minor” value.

 

For example, at the store A, they would place all beacon devices broadcasting the RDXApparel UUID, major value 1, minor 1 near the Kid’s toy section , minor 2 near the handbags section and minor value 3 near the cashier. At store B, they would use the same UUID, but major 2 and minor values according to the location inside the store.

 

In order to achieve this, at first we need to create a mobile backend, assign location of our retail store and then assign the device details that are present in that particular store.

 

Step 1: Create a mobile backend

Create a mobile backend to access and configure services, in this example we will incorporate mobile user management and location services

 

Step 2: Add Location and associate device(s)

The retail store location and related device detailed needs to be uploaded/added into Oracle MCS. In order to do that,  we need to first add a new place under "Location" section in Oracle MCS.

A place is a physical location associated with one or more location devices.

Add a Location

OracleLocationOverview.png

 

Associate device(s) with the location

Add a device.png

 

Step 3: Test the added/uploaded data

Once data is added, we need to test if user is able to download this information. We will use Location services platform, POST API Return Places by Query. Here is sample request:

 

POST /mobile/platform/location/places/query HTTP/1.1
Host: xxx.mobileenv.us2.oraclecloud.com:443
oracle-mobile-backend-id: 12345678-3297-4d93-87a8-e36dc7d6842c
Authorization: Basic abc=
Content-Type: application/json
Cache-Control: no-cache
{
  "inGeoFence" : {
    "gpsCircle" : {
      "longitude":77.3211437,
      "latitude": 28.5591575,
      "radius": 10000
    }
  }
}

 

Here is sample output:

{
  "items": [
    {
      "id": 10,
      "createdOn": "2016-08-28T07:54:55.826+0000",
      "createdBy": "xxx@oracle.com",
      "modifiedOn": "2016-08-31T05:23:20.785+0000",
      "modifiedBy": "xxx@oracle.com",
      "name": "RDxApparel Noida",
      "label": "RDApparelx",
      "description": "RDxApparel store in Noida Sector 127",
      "hasChildren": false,
      "address": {
        "gpsCircle": {
          "longitude": 77.321,
          "latitude": 28.549,
          "radius": 999.99999999
        }
      },
      "devices": [
        {
          "id": 10,
          "createdOn": "2016-08-28T08:16:12.302+0000",
          "createdBy": "xxx@oracle.com",
          "modifiedOn": "2016-08-28T08:19:32.341+0000",
          "modifiedBy": "xxx@oracle.com",
          "name": "RDxApparel Kids section",
          "description": "Thank-you for visiting kid's section! \nCongrats, you are now eligible for 10% discount on kid's section items , your coupon code is RDXKIDS10",
          "beacon": {
            "iBeacon": {
              "major": "65504",
              "minor": "65505",
              "uid": "74278BDA-1114-4520-8F0C-720EAF059935"
            }
          },
          "properties": {},
          "links": [
            {
              "rel": "canonical",
              "href": "/mobile/platform/location/devices/10"
            },
            {
              "rel": "self",
              "href": "/mobile/platform/location/devices/10"
            }
          ]
        },
        {
          "id": 11,
          "createdOn": "2016-08-29T08:53:49.813+0000",
          "createdBy": "xxx@oracle.com",
          "modifiedOn": "2016-08-31T05:20:08.898+0000",
          "modifiedBy": "xxxx@oracle.com",
          "name": "RDxApparel Handbag section",
          "description": "Thank-you for visiting Handbag section! Congrats, you are now eligible for 40% discount on handbags , your coupon code is RDXHANDBAGS40",
          "beacon": {
            "iBeacon": {
              "major": "16808",
              "minor": "19400",
              "uid": "748BEEA2-1111-44C2-A59C-706FFACA6A3B"
            }
          },
          "properties": {},
          "links": [
            {
              "rel": "canonical",
              "href": "/mobile/platform/location/devices/11"
            },
            {
              "rel": "self",
              "href": "/mobile/platform/location/devices/11"
            }
          ]
        }
      ],
      "properties": {},
      "links": [
        {
          "rel": "canonical",
          "href": "/mobile/platform/location/places/10"
        },
        {
          "rel": "self",
          "href": "/mobile/platform/location/places/10"
        }
      ],
      "children": []
    }
  ],
  "totalResults": 1,
  "limit": 100,
  "count": 1,
  "hasMore": false
}

 

Once we get status 200 OK, our back-end part is ready!

 

Component: Developing client-side Oracle JET Hybrid application

We will start implementing from scratch, you may please refer to Troubleshooting while developing your First JET based Hybrid Application blog in case of initial issues faced during development/configuration issues.

 

Project Setup using Yeoman

Yeoman generator for Oracle JET lets you quickly set up a project for use as a Web application or mobile-hybrid application for Android and iOS. Use following command to generate hybrid application for Android:

 

yo oraclejet:hybrid proximitysample --appId=com.rdh.proximitysample --appName="proximitysample" --template=navBar --platforms=android

 

The navBar template based native hybrid application code will be placed in “proximitysample” folder.

 

Cordova Plugin Required

Following cordova plugin needs to be added in our application:

 

  1. cordova-plugin-geolocation: This plugin provides information about the device's location, such as latitude and longitude. Link: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-geolocation/
  2. cordova-plugin-ibeacon: iBeacon plugin for cordova based hybrid application. Link: https://github.com/petermetz/cordova-plugin-ibeacon
  3. Cordova Local-Notification Plugin:  Plugin to display local notification, especially when application is not running in foreground. Link: https://github.com/katzer/cordova-plugin-local-notifications

 

cd hybrid
cordova plugin add cordova-plugin-geolocation 
cordova plugin add https://github.com/petermetz/cordova-plugin-ibeacon.git
cordova plugin add https://github.com/katzer/cordova-plugin-local-notifications

 

Implementation Steps

 

We will be implementing the entire code in dashboard.html and dashboard.js for easy implementation.

 

Below is the updated dashboard.html file

 

Untitled.png

 

Add below variables in dashboard.js file DashboardViewModel() function, they will be used in various function in given steps:

       var mRegions = [];
                var mNearestBeacon = null;
                var mNearestBeaconDisplayTimer = null;
                var beaconID = '00000';
                var winningCounter = 0;
                self.viewbeaconName = ko.observable();
                self.viewDiscountCoupon = ko.observable();

 

 

Below are the implementation steps to fetch micro-location and context and generating a location notification in the app to display relevant information:

 

Step 1:  Fetch the current location – latitude, longitude and radius and send it to Oracle MCS

 

               // fetches current latitude and longitude
                function getGeoLocationCoordinates()
                {
                    var onSuccess = function (position) {
                        var lat = position.coords.latitude;
                        var long = position.coords.longitude;
                        console.log(lat);
                        console.log(long);
                        // Pass the latitude and logitude to MCS location services
                        downloadBeaconRegistry(lat, long);
                    };
                    function onError(error) {
                        alert('code: ' + error.code + '\n' +
                                'message: ' + error.message + '\n');
                    }
                    navigator.geolocation.getCurrentPosition(onSuccess, onError);
                }
                function onDeviceReady() {
                    // Will execute when device is ready, or immediately if the device is already ready.
                    getGeoLocationCoordinates();
                }

 

Step 2:  Fetch all the iBeacon device details received from Oracle MCS Location Services Platform API in the application

 

// downloads device deatails based on location given from Oracle MCS Location Platform Service
                function downloadBeaconRegistry(gpsLat, gpsLong)
                {
                    var settings = {
                        "async": true,
                        "crossDomain": true,
                        "url": "https://xxx.mobileenv.us2.oraclecloud.com:443/mobile/platform/location/places/query",
                        "method": "POST",
                        "headers": {
                            "content-type": "application/json",
                            "oracle-mobile-backend-id": "your-oracle-mobile-backend-id",
                            "authorization": "Basic " + btoa("mobile-user-name" + ":" + "mobile-user-password")
                        },
                        "data": JSON.stringify({
                            inGeoFence: {
                                gpsCircle: {
                                    longitude: gpsLong,
                                    latitude: gpsLat,
                                    radius: 1000 // assuming the radius to be around 1000 meter
                                }
                            }
                        })
                    };
                    $.ajax(settings).done(function (response) {
                        // populate beacon registry in an array
                        for (var i in response.items[0].devices)
                        {
                            // save all device details in mRegions array
                            mRegions.push(
                                    {
                                        id: response.items[0].devices[i].id,
                                        name: response.items[0].devices[i].name,
                                        description: response.items[0].devices[i].description,
                                        uid: response.items[0].devices[i].beacon.iBeacon.uid,
                                        major: response.items[0].devices[i].beacon.iBeacon.major,
                                        minor: response.items[0].devices[i].beacon.iBeacon.minor,
                                        beaconID: response.items[0].devices[i].beacon.iBeacon.uid + ':' +
                                         response.items[0].devices[i].beacon.iBeacon.major + ':' + response.items[0].devices[i].beacon.iBeacon.minor
                                    });
                        }
                        startDetectingBeacons();
                    });
                }

 

Step 3: Start listening to devices over Bluetooth

 

 // start detecting given beacons
                function startDetectingBeacons()
                {
                    function onDidRangeBeaconsInRegion(result)
                    {                      
                        updateNearestBeacon(result.beacons);
                    }
                    var delegate = new cordova.plugins.locationManager.Delegate();
                    cordova.plugins.locationManager.setDelegate(delegate);
                    delegate.didRangeBeaconsInRegion = onDidRangeBeaconsInRegion;
                    for (var i in mRegions)
                    {
                        var beaconRegion = new cordova.plugins.locationManager.BeaconRegion(
                                mRegions[i].beaconID,
                                mRegions[i].uid,
                                mRegions[i].major,
                                mRegions[i].minor);
                        cordova.plugins.locationManager.startRangingBeaconsInRegion(beaconRegion)
                                .fail()
                                .done();
                    }
                    mNearestBeaconDisplayTimer = setInterval(displayNearestBeacon, 1000);
                }

 

 

Step 4: Find the nearest Beacon

 

 // generates uniqueId based on uuid, major and minor number
               function getBeaconId(beacon)
                {
                    return beacon.uuid + ':' + beacon.major + ':' + beacon.minor;
                }               
                function isSameBeacon(beacon1, beacon2)
                {
                    return getBeaconId(beacon1) === getBeaconId(beacon2);
                }
               // beacon.accuracy returns the distance of beacon from mobile device
                function isNearerThan(beacon1, beacon2)
                {
                    return beacon1.accuracy > 0
                            && beacon2.accuracy > 0
                            && beacon1.accuracy < beacon2.accuracy;
                }
                function updateNearestBeacon(beacons)
                {
                    for (var i = 0; i < beacons.length; ++i)
                    {
                        var beacon = beacons[i];
                        if (!mNearestBeacon)
                        {
                            mNearestBeacon = beacon;
                        } else
                        {
                            if (isSameBeacon(beacon, mNearestBeacon) ||
                                    isNearerThan(beacon, mNearestBeacon))
                            {
                                mNearestBeacon = beacon;
                            }
                        }
                    }
                }

 

Step 5: On getting micro-location and context, calculate how far the user is from a given beacon (i.e. retail store section) and display relevant information on the mobile app

 

 function findBeacon(beaconObj)
               {                    
                    return (beaconObj.beaconID).toUpperCase() === beaconID.toUpperCase();
                }
                function displayNearestBeacon()
                {
                    if (!mNearestBeacon) {                        
                        return;
                    }                    
                    beaconID = getBeaconId(mNearestBeacon);              
                    var objBeacon = mRegions.find(findBeacon);                   
                   self.viewbeaconName('You are now just '+ mNearestBeacon.accuracy + ' meters away from our ' + objBeacon.name +'!');
                   if(mNearestBeacon.proximity === 'ProximityNear' || mNearestBeacon.proximity === 'ProximityImmediate' )
                    {
                        self.viewbeaconName('Welcome to '+ objBeacon.name +'!');                        
                        winningCounter++;                        
                    }                    
                    if(winningCounter > 30)
                    {
                        winningCounter = 0;
                        showLocalNotification(objBeacon.name, objBeacon.description);                 
                     }
                }
Step 6: Trigger a local notification displaying important information based on time spent by user near a section

 

 function showLocalNotification(beacontitle, beacondescription)
                {
                    cordova.plugins.notification.local.schedule({
                        title: beacontitle,
                        message: beacondescription
                    });                 
                   self.viewDiscountCoupon(beacondescription);                    
                }

 

Finally, update handleDetached function to reset the timer interval:

 self.handleDetached = function () {
                    document.removeEventListener('deviceready', onDeviceReady);
                    clearInterval(mNearestBeaconDisplayTimer);
                    mNearestBeaconDisplayTimer = null;
                };

 

This is the one of the easiest way to interact. However, the user context information is of real advantage as we can send this information to big data systems for extracting information like user behavior , analytics.

 

Build and Run the application on Android device

In your command prompt, please change directory to project folder and run the following command:

 

  1. Build the application using following command
grunt build --platform=android

 

Please ensure to turn ON your mobile's Bluetooth, GPS and mobile data  before running the application. Also please allow the app to use Bluetooth and GPS when app runs.

  1. Once build is success, then run the application using following command:
grunt serve --platform=android  --disableLiveReload=true

Demo Video

Below is the video output of the Mobile application:

 

 

**The views expressed in this post are my own and do not necessarily reflect the views of Oracle.

This is a two-part blog series which covers setup, development, integration and deployment aspects of developing lightweight Java EE applications for the Oracle Application Container Cloud service – all with the help of a sample application. The following are the key items which will be covered in this post

 

  • Developing ‘right sized’ applications by using ’just enough’ of the Java EE platform, thanks to Wildfly Swarm
  • Leveraging a scalable and easy to use cloud platform: Oracle Application Container Cloud service
  • Evolutionary not revolutionary: use your existing Java EE skills and continue building WARs
  • Second part of this blog will focus more on Oracle Developer Cloud service

 

 

 

 

 

 

 

Introduction

Here is a quick peek into the major services/tools/frameworks which we’ll be used for the sample application

 

Runtime/Service/Framework/Tool

Version

 

 

Oracle JDK

7

Java EE Platform

Java EE 7

Oracle Application Container Cloud Service

16.3.3

Wildfly Swarm

1.0.0

Maven

3.3.9

Netbeans IDE

8.1

 

Oracle Cloud services

 

Oracle Application Container Cloud

 

Oracle Application Container Cloud service provides a robust, polyglot PaaS infrastructure for building lightweight applications in Oracle Cloud. Without going into the details, here are the major highlights of this service

 

  • Open & Polyglot: you're free to leverage any of the thousands of open source or commercial Java SE, Node or PHP (latest addition) frameworks. More programming languages will be added in future
  • Docker based: Built on the proven containerization technology
  • Elastic: Your applications can be easily scaled in or out using the REST API or the service console
  • Easy to manage: update your language runtimes to their latest releases with a single click
  • Profiling: Java based applications can leverage the Flight Recorder to monitor the JVM and analyze using Mission Control
  • Provides integration with other Oracle Cloud services: Developer Cloud, Java Cloud, Database Cloud

 

Oracle Developer Cloud

 

Oracle Developer Cloud Service is a cloud-based software development Platform as a Service (PaaS) and a hosted environment for your application development infrastructure. It provides an open source standards-based solution to develop, collaborate, build, and deploy applications within Oracle Cloud.

 

Platform, frameworks & tools

 

Wildfly Swarm

 

WildFly Swarm is an open source framework that allows the selective reconstitution of Java EE APIs (JAX-RS, CDI, EJB JPA, JTA etc.) within your applications. The goal is to use just enough of a Java EE application server to support whatever subset of the APIs your application requires.

 

Maven

 

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.

 

IDE

 

For development, this sample uses NetBeans which is a free, open source and immensely popular IDE. For more details, please feel free to check https://netbeans.org/features/index.html

 

Why NetBeans ?

 

  • Integration with Oracle Developer Cloud service powered by the Team Server plug-in
  • Great for Java EE development with support for the latest (v7) platform version
  • Excellent Maven support

 

Diving in…

 

Introducing the Ticker Tracker application

 

Here is a quick intro to the functional aspect of the application. Ticker Tracker revolves around the ability to keep track of stock prices of NYSE scrips.

 

  • Users can check the stock price of a scrip (listed on NASDAQ) using a simple REST interface
  • Real time price tracking is also available – but this is only for Oracle (ORCL)

 

The logic itself is kept simple in order to focus on the core concepts rather than get sucked into complexity of technical implementation. The sample application uses below mentioned Java EE APIs

 

API

Version

JSR #

 

 

 

JAX-RS

2.0

JSR-339

WebSocket

1.0

JSR-356

EJB

3.2

JSR-345

CDI

1.1

JSR-346

JSON-P

1.0

JSR-353

 

Evolutionary not revolutionary

Wildfly Swarm supports two development modes

 

Uber JAR (Java SE)

 

  • Include the required components/fractions (e.g. JAX-RS, CDI, JPA etc.) in pom.xml
  • Write your code
  • Configure your components and deployment using the Swarm Container API within a main method and

 

Hybrid Mode i.e. WAR to JAR (Java EE roots)

 

  • Stick to the business logic which you have already developed
  • Let Swarm auto-magically detect and configure required fractions and create an Uber JAR from your WAR

 

Why go with WAR?

 

  • Less intrusive: let’s you continue your development process with minimum changes/additions
  • Suitable for existing applications: In addition to embarking upon developing Uber JAR Java EE application from scratch, one would also want to retrofit existing deployments to the Fat/Uber JAR model
  • Developer friendly: As a developer, you would do not need to wrap your head around auxiliary concerns such as WAR to Uber JAR conversion – just continue writing your Java EE application and let the framework help you out with this
  • As mentioned before, the Swarm Maven plugin does the job of creating your Fat/Uber JAR
  • Use the Swarm Maven plugin to assist you with the Uber JAR creation

 

Implementation details

 

Let’s go over this in a step-by-step manner

 

Initialize NetBeans project

Start with a Maven based Java EE project in NetBeans. If you want to explore this in further detail, feel free to refer NetBeans documentation around this topic (starting with this source)

 

 

 

 

 

It’s not necessary to include/link a Java EE application server at this point. You can opt for ‘No Server selected’ and click Finish to complete the process

 

 

After bootstrapping the Maven based Java EE Web application project, you should see the following project structure along with a pom.xml

 

 

 

Note: the only dependency defined in your pom.xml would be the Java EE 7 APIs

 

..........
<dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
..........

 

Add Swarm specific magic

 

Once NetBeans sets up the base version of your project, all you need to do is include the wildfly-swarm-plugin configuration your Maven pom.xml

 

......
<plugin>
                <groupId>org.wildfly.swarm</groupId>
                <artifactId>wildfly-swarm-plugin</artifactId>
                <version>1.0.0.Final</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
</plugin>
......

 

Note: You can choose to explicitly add dependencies for the fractions (the required Java EE components) which you are using in your application or you can offload this duty to Swarm which in turn will automatically detect the required fractions (after code introspection) and pull them during the build phase. Below is an example of how you can add a JAX-RS fraction for RESTful services

 

.....
<dependency>  
    <groupId>org.wildfly.swarm</groupId>  
    <artifactId>jaxrs</artifactId>  
</dependency>
.....

 

Once the setup process is complete, we can proceed with our application development.

 

Develop business logic

Let’s look a quick look at the classes/code artifacts along with some code snippets to get you warmed up.

 

 

RealTimeStockTicker.java

 

  • A server side WebSocket endpoint (@ServerEndpoint) to broadcast stock prices to connected clients/users
  • The data is pushed to clients in an asynchronous manner, thanks to this feature in the Java WebSocket API
  • Please note that for this sample, users are restricted to be able to track real time prices for Oracle (ORCL) stocks only

 

.....
public void broadcast(@Observes @StockDataEventQualifier String tickTock) {

        for (final Session s : CLIENTS) {
            if (s != null && s.isOpen()) {
                /**
                 * Asynchronous push
                 */
                s.getAsyncRemote().sendText(tickTock, new SendHandler() {
                    @Override
                    public void onResult(SendResult result) {
                        if (result.isOK()) {
                            Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.INFO, "Price sent to client {0}", s.getId());
                        } else {
                            Logger.getLogger(RealTimeStockTicker.class.getName()).log(Level.SEVERE, "Could not send price update to client " + s.getId(),
                                    result.getException());
                        }
                    }
                });
            }


        }
}
.....

 

StockPriceScheduler.java

 

    • A Singleton EJB (@Singleton) acts as the source of stock price data
    • It uses native (EJB) scheduling (combination of TimerService and @Timeout) capabilities to periodically poll the Google Finance REST endpoint using the JAX-RS client API to pull stock prices
    • Leverages CDI capabilities like events and qualifiers (@Qualifier) to push latest stock prices to the connected WebSocket clients in real time

 

.....
@Timeout
public void timeout(Timer timer) {

        /**
         * Invoked asynchronously
         */
        Future<String> tickFuture = ClientBuilder.newClient().
                target("https://www.google.com/finance/info?q=NASDAQ:ORCL").
                request().buildGet().submit(String.class);

        /**
         * Extracting result immediately with a timeout (3 seconds) limit. This
         * is a workaround since we cannot impose timeouts for synchronous
         * invocations
         */
        String tick = null;
        try {
            tick = tickFuture.get(3, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException ex) {
            Logger.getLogger(StockPriceScheduler.class.getName()).log(Level.INFO, "GET timed out. Next iteration due on - {0}", timer.getNextTimeout());
            return;
        }

        if (tick != null) {
            /**
             * cleaning the JSON payload
             */
            tick = tick.replace("// [", "");
            tick = tick.replace("]", "");
            msgEvent.fire(StockDataParser.parse(tick));
        }
}
.....

 

StockPriceResource.java

A simple REST endpoint (@Path, @GET) exposed for the end users to be able to query (on demand) the stock price of any index listed on NYSE

 

.....
@GET
public String getQuote(@QueryParam("ticker") final String ticker) {


        Response response = ClientBuilder.newClient().
                target("https://www.google.com/finance/info?q=NASDAQ:" + ticker).
                request().get();


        if (response.getStatus() != 200) {
            //throw new WebApplicationException(Response.Status.NOT_FOUND);
            return String.format("Could not find price for ticker %s", ticker);
        }
        String tick = response.readEntity(String.class);
        tick = tick.replace("// [", "");
        tick = tick.replace("]", "");


        return StockDataParser.parse(tick);
}
.....

 

RESTConfig.java

Basic configuration class for the JAX-RS container

 

StockDataParser.java

A simple utility class which leverages the JSON Processing (JSON-P) API to filter the JSON payload obtained from the Google Finance REST endpoint and returns data useful for the end users

 

beans.xml

This is the CDI configuration file. Although it does not have too much content, it does enable the CDI events feature

 

Execution

 

In this section, we’ll see how to

  • Build and run the example on your local machine
  • Package it as per Oracle Application Container semantics, upload it and see our sample app in action in the cloud

 

Standalone mode

Now that we have our business logic ready, it’s time to build our application! You can do so by right-clicking on your project in NetBeans and choosing Build with dependencies (please note that this might take time during the first iteration)

 

 

The file structure of your maven build directory (target) will look similar to this. Please note that the ticker-tracker-swarm.jar is the Uber JAR produced by Wildfly Swarm plugin

 

 

Since the artifact produced by the build process is just a JAR file, running this is very simple (all you need is a JRE). Here is template for the deployment command

 

java -jar -Dswarm.http.port=<port_number> -Dswarm.context.path=/<custom_context_root> <full_path_to_jarfile>

 

Here is an example

 

java -jar -Dswarm.http.port=9090 -Dswarm.context.path=/ticker-tracker /work/demo/ticker-tracker-swarm.jar

 

Your application should now be up and running on port 9090 with the ticker-tracker  context root

 

  • Access the REST endpoint as follows - http://localhost:9090/ticker-tracker/api/stocks?ticker=AAPL
  • Here is the WebSocket endpoint - ws://localhost:9090/ticker-tracker/rt/stocks

 

Configuration

The below mentioned parameters are specific to Wildfly Swarm and can be used to configure its containers and components

 

Parameter

Description

swarm.http.port

The port on which the Servlet container (Undertow in case of Wildfly) accepts incoming connections. This is 8080 by default

swarm.context.path

Defines a custom context root for your web application

 

Package, upload to Application Container Cloud

 

Package

 

Here are the important points to note in terms of packaging as far as the sample application in this post is concerned (some of these are generally applicable to all JAR based deployments on Application Container Cloud). For a deep dive into this topic, please refer the Packaging Your Application section in the product documentation

 

  • Its packaged as an Uber (Fat) JAR
  • Uses a manifest.json (compulsory deployment artifact) which includes the launch command as well

 

{
    "runtime": {
        "majorVersion": "8"
    },
    "command": "java -jar -Dswarm.https.port=$PORT -Dswarm.context.path=/ticker-tracker ticker-tracker-swarm.jar",
    "release": {
        "build": "24082016.2052",
        "commit": "007",
        "version": "0.0.1"
    },
    "notes": "notes related to release"
}

 

 

A note about the $PORT environment variable

As a platform, Application Container Cloud is ephemeral by nature and parameters like hostname and port etc. are allocated dynamically by the platform and are subject to change (in case your application is redeployed). The startup command takes this into account and binds to the dynamic port allotted by the underlying Application Container Cloud instance. More on this in the following sections - Making the Application Configurable at Runtime, Design Considerations

 

Upload

Oracle Application Container Cloud provides multiple channels for uploading our application

 

Using the Web UI

  • You can choose to upload your ZIP file directly, or
  • You can upload it to your Storage Cloud Service instance first and provide its path

 

Using REST API – Refer Create an Application for the API semantics

 

You can refer the Getting Started with Oracle Application Container Cloud Service tutorial to get a better understanding of the above mentioned points with a focus on the following topics

 

Test out your Cloud deployment

 

To check price of a specific stock just issue a GET request on the specified URL. Mentioned below is the template

 

https://<your-accs-app-url>/ticker-tracker/api/stocks?ticker=<ticker_symbol>

 

Example:

 

https://ticker-tracker-mydomain.apaas.em1.oraclecloud.com/ticker-tracker/api/stocks?ticker=AAPL

 

For real time tracking of Oracle stock prices, you would need a WebSocket client. I would personally recommend using the client which can be installed into Chrome browser as a plugin – Simple WebSocket Client. This client will be used to demonstrate the real time stock tracking feature. The following is a template for the URL of the WebSocket endpoint

 

wss://<your-accs-app-url>/ticker-tracker/rt/stocks

 

Use this as the value for the URL attribute in the client and click on the Open button to start tracking

 

Example

 

wss://ticker-tracker-mydomain.apaas.em1.oraclecloud.com/ticker-tracker/rt/stocks

 

Note: Please notice the fact that we're using secured channel for both REST (https) and WebSocket (wss) protocols since the applications deployed on Application Container Cloud service only listen on secured ports (via a load balancer which the end users and developers do not need to worry about)

 

 

 

 

You should start receiving real time price updates in the Message Log box. You can choose to disconnect any time by clicking the Close button. You now have a lightweight Java EE application running and fully functional on the Oracle Application Container Cloud !

 

This marks the end of part I. As promised, the second part of this post will focus on Oracle Developer Cloud service features such as IDE Integration for source code management, project configuration, build monitoring, seamless deployment to Application Container and more…

 

 

 

**The views expressed in this post are my own and do not necessarily reflect the views of Oracle.

Oracle JET is the new open source JavaScript library from Oracle which is great for building Single Page Applications.  In this blog post, I discuss the various steps to create an Oracle JET web application hosted on Java Cloud Service – SaaS Extension that can hold a logged in user Context.  For implementing this, we will leverage the out-of-box capabilities of the Shared Identity Management (SIM) to achieve Single Sign on and Single Log off and integrate with a JAX-RS REST Service that provides User Context.

 

Here is the list of softwares/services I am using:

  • IDE – Netbeans 8.1
  • Oracle JET Quick Start template (version 2.1.0).
  • Oracle Weblogic Server 10.3.6 (for testing locally)
  • Oracle Java Cloud Service – SaaS Extension 16.3.x

 

Pre-requisites

  1. Register the Oracle Weblogic Server in your Netbeans IDE (Tools -> Servers -> Add Server – add the Oracle Home for the Oracle Weblogic Server in the location).  More details can be found in the Netbeans documentation
  2. Make sure you have the Jersey 1.x library for compilation purposes.    If not create a new library in Netbeans  via Tools -> Libraries -> New Library, give it a name of Jersey 1.x and add the following jars under it:

jersey-bundle-1.18.1.jar

jersey-core-1.18.1.jar

jersey-json-1.18.1.jar

jersey-multipart-1.18.1.jar

jersey-server-1.18.1.jar

jersey-servlet-1.18.1.jar

 

You can download these jars from the maven central repository or from the Jersey downloads page.

 

Step 1 – Create a Web Application in Netbeans 8.1

 

Go to File -> New Project -> Java Web.  Choose Web Application as the project type as shown below:

 

NetbeansSetup - create project.png

NetbeansSetup - create project 2.png

 

Give the information required by the New Project Wizard like project name, runtime server environment (Oracle Weblogic) etc.  This will create an empty Web Application with the name you chose with a web.xml, weblogic.xml and an index.jsp

 

Step 2 - Download and Unzip the Quick Start template.

There are multiple ways to scaffold an Oracle JET (web) application as given in the JET documentation.  The technique given here is my preferred one in Netbeans.

 

Download the Oracle JET Quick Start template from here into any directory.  This contains the skeleton of a working Oracle JET project, which you can then change according to your needs.  This is great if you are starting out with Oracle JET.

 

Next unzip the Oracle JET Quick Start template.  This is what the structure looks like:

NetbeansSetup - Quick Start structure.png

 

Step 3 – Copy the contents of the QuickStart to the Netbeans Web Application

Delete the index.jsp that was created by default in the Netbeans Web Application, because we will be using the index.html that is provided with the Oracle JET Quick Start.

NetbeansSetup - index.png

 

Select all the contents in the Quick Start template as shown in Figure 3 and copy them to the Netbeans Web application Web Pages folder.

NetbeansSetup - copyall.png

 

Step 4 – Modify the WEB-INF/web.xml and WEB-INF/weblogic.xml

Add security-constraint and login-config entries to the web.xml.  This will make the application protected by delegating authentication to the SIM which will require a user to log in before he can access index.html

 

The login-config entry has a value of CLIENT-CERT as the auth-method which is Oracle's recommended mode of authentication, as it enables the tenant-specific SSO authentication mode for an application.

 

web.xml

 

<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <security-constraint>
        <display-name>name</display-name>
        <web-resource-collection>
            <web-resource-name>name</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
    </security-constraint>
    <login-config>
        <auth-method>CLIENT-CERT</auth-method>
        <realm-name>default</realm-name>
    </login-config>
</web-app>

 

 

weblogic.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd">
    <container-descriptor>
        <prefer-web-inf-classes>true</prefer-web-inf-classes>
    </container-descriptor>
    <context-root>/QuickStartProject</context-root>
</weblogic-web-app>

 

Now you have a basic Oracle JET web application that is restricted to only tenants having access to JCS-SX.  You can further restrict this to a certain role in web.xml.  For more details consult JCS-SX documentation for securing applications

 

Tip – You can add this skeleton project in Netbeans as a sample project, so that you don’t need to carry out the same steps again.  Here is the tutorial that helps to make this a sample project in NetBeans.

 

 

Running it locally using the Netbeans platform (Project -> Run) will yield the following screen

JET QuickStart - running application.png

 

Not Bad!  But the logged in user is hardcoded (on the top right hand corner) and the ‘Sign Out’ menu (on expanding the user name menu dropdown) doesn’t do anything.  Let us correct that.

 

Tip – If you want to use the minified version of oracle JET JavaScript and not the debug version, change the path in main.js to use the minified JS.

 

'ojs': 'libs/oj/v2.1.0/debug',  

 

to

 

'ojs': 'libs/oj/v2.1.0/min',

 

You can now delete the debug folder  (js/libs/oj/v2.1.0/debug) if you wish, which will make the application smaller in size.

 

Step 5 - To get the logged in User

Let us develop a JAX-RS REST service to get the logged in User.  You can create this in the same project or any other project that will be deployed on the same server.  I am going to do this in the same project.

 

Add the Jersey 1.x library to the project by clicking on Project -> Properties -> Libraries -> Add Library.  If you only see the Jersey 2.x library, click Create and add a custom library called Jersey 1.x as given in prerequisites.

 

The Jersey 1.x is only required for compilation purposes – so make sure the library is not packaged (by unticking the Package checkbox)

 

NetbeansSetup - add Jersey Library.png

 

We need to develop a JAX-RS REST API that the JET application can call which would give the logged in user name.

 

UserApplication.java – This is the JAX-RS Application that is the entry point for the REST Service

 

package com.oracle.rest;

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;

public class UserApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(UserService.class);
        return classes;
    }
}

 

UserService.java – This is the JAX-RS resource which contains the logic for getting the logged in user

 

package com.oracle.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

@Path("rest")
public class UserService {
    public UserService() {
        super();
    }
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("userinfo")
    public String getLoggedInUser(@Context SecurityContext sc) {
        String userName = "No user";
        if (sc.getUserPrincipal() != null) {
            userName = sc.getUserPrincipal().getName();
        }
        return userName;
    }
}

 

Change the web.xml as below to add the Jersey servlet

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
 <servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.oracle.rest.UserApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
<servlet-name>jersey</servlet-name>
<url-pattern>/gateway/*</url-pattern>
</servlet-mapping>

<security-constraint>
<display-name>name</display-name>
 <web-resource-collection>
<web-resource-name>name</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>default</realm-name>
</login-config>
<web-app>

 

 

Deploy the web application on JCS-SX and test the rest service.  For deploying the application on JCS-SX please refer to the documentation.

 

Then test the REST service using

http://<java instance url>/QuickStartProject/gateway/rest/userinfo

 

This should give the logged in user in text/plain format.

 

Now let us call this REST service in our JET application to display the correct user name.  A search for the hardcoded user name (john.hancock@oracle.com) points us to js/appController.js  and sure enough it has the following lines:

 

      self.userLogin = ko.observable("john.hancock@oracle.com");

 

Change this to the following lines

 

                self.userLogin = ko.observable("");
                self.getUserName = function () {
                    return $.ajax({url: "/QuickStartProject/gateway/rest/userinfo",
                        dataType: 'text',
                        type: 'GET'
                    });
                };
               self.getUserName().then(function (result) {
                    self.userLogin(result);
                });

If you deploy and run the application on JCS-SX it will show the logged in user instead of the hardcoded user name.

 

Step 5 - To implement logoff

If you click on the user name, you can see the “Sign Out” button which is not implemented yet.  Let us map it to a LogoutServlet.

 

package com.oracle.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.*;
import javax.servlet.http.*;

public class LogoutServlet extends HttpServlet {

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getSession().invalidate();
        response.sendRedirect( "/oamsso/logout.html");
    }
}

 

Add the Logout servlet entry in web.xml

 

<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>com.oracle.servlet.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>

 

In index.html, you can see the menu1 element that has the ‘Sign Out’ menu item.  The selection of any menu item triggers function menuItemSelect.

 

<ul id='menu1' data-bind="ojComponent: {component: 'ojMenu', select: menuItemSelect}" style="display:none">
        <!--<li id="pref"><a href="#">Preferences</a></li>-->
        <li id="help"><a href="#">Help</a></li>
        <li id="about"><a href="#">About</a></li>
        <li id="out"><a href="#">Sign Out</a></li>
</ul>

 

In appController.js, add a case for the sign out event

 

self.menuItemSelect = function (event, ui) {
 switch (ui.item.attr("id")) {
 case "about":
 $("#aboutDialog").ojDialog("open");
 break;
 case "out" :
 window.location.href = "/QuickStartProject/logout";
 default:
       }
};

 

 

Deploy and run the application and clicking on Sign Out redirects to the cloud logout page as below:

JET QuickStart - loggedoff.png

 

If you want to display a particular page and not the cloud logout page, customize the URL in the LogoutServlet.

 

response.sendRedirect( "/oamsso/logout.html?end_url=<landing page>");

 

There you have it – a working JET application deployed on JCS-SX with the logged in user context and Single Sign On and Single Logoff features.

 

**The views expressed in this post are my own and do not necessarily reflect the views of Oracle.

In this post, we discuss how to design Custom APIs for Oracle Mobile Accelerator (MAX) using Oracle Mobile Cloud Service (MCS).

 

About MAX

Before we delve into how to develop an MCS API for MAX,a few words about MAX and how does it work. MAX or mobile accelerator is a browser based mobile application development platform which is bundled along with MCS 2.0. It’s a zero code and 100% declarative development platform.

 

Target User

MAX is for the business users and API testers, to develop mobile applications quickly without having any mobile platform or coding knowledge/skill set. MAX provides a complete drag and drop based declarative mobile application development platform. The MAX browser tool set also comes with an embedded simulator for testing the application for both iOS and Android platforms.

 

APIs that MAX based application can consume

The current MAX version consumes the Custom API developed and published in MCS 2.0. These Custom APIs should be REST based with JSON response developed using Node.js. Any REST or SOAP end point that needs to be consumed from Oracle SaaS or external systems have to be first consumed using the MCS connector and  in turn consumed in the Custom API developed using Node.js.

 

 

MAX App and API development approach

Development of the MAX application and the MCS Custom API to be consumed in the API go hand in hand. Assuming the MAX Application developer to be a business user and API being developed by a MCS developer, it is important to have a right development approach to ensure productive development with least number of iterations possible. Below development approach flowchart describes the stakeholders and the activity flow.

 

Picture1.png

 

 

How to make MCS Custom API MAX compliant:

 

For a person developing the MAX application, he/she will be dealing with business objects that are based on the endpoints of MCS Custom API. Below screenshots where the accountdeals and userdeals endpoints show as the business object for MAX design time exhibits the same.

 

endpoints.png

bo.png

 

CRUD operations available for the business object is dependent on the methods defined for the endpoint, such as GET, POST, DELETE, PUT, PATCH etc. Below given table maps the functionality to the method:

 

 

Method

Functionality

Get

For read only forms, list, bar chart, pie chart, and other charting components. We can select simple screen/template and then add the layouts later on.

Post

For creating a record using the create functionality. The create screen has to be selected while selecting the screen type to be able to assign the create BO.

Put/Patch

For edit functionality. The edit screen has to be selected while selecting the screen type to be able to assign the edit BO.

Delete

Swipe gesture to remove a record.

 

 

 

Based on the screen functionality and user interface components required the business object needs to be designed and the methods corresponding to the business object needs to be selected. For example, as shown in the picture below, the “notes” endpoint/business object has GET, POST and DELETE methods defined. It has been done so to furnish the functionality, where a user would see a list(using MAX List component) of  notes and on the same screen there will be a “+” sign button for creating a note and swipe gesture(please refer the table above) enabled on the List to delete a note. 

 

object_operations.png

 

For MAX to consume the Custom API it is essential that the Custom API’s response is either an object or an array. An object can also contain an array, but it should not contain more than one array at the same object hierarchy level. Below is the sample JSON response which is compliant for MAX:

 

                array_resp.png   mixed.pngobject_resp.png

                                  

 

The eligible business components that will be shown for binding to a UI component in MAX will be dependent on response type. Some of them have been mentioned below in the mapping matrix:

 

Component Type

Response Type

List

Array

Form

Object

Metric

Object

Bar Chart

Array

Pie Chart

Array

 

To ensure that the endpoints defined in MCS are available as Business Objects for MAX, it is mandatory that we define the sample response for methods for an endpoint. Below screenshot shows the same.

sample.png

 

Last but not the least, for current version of MCS, make sure the custom API is published for it to be available for consumption in MAX design time.

 

 

Architecture

 

To conclude, below diagram depicts sample architecture for MAX based mobile extension for Oracle Sales cloud:

 

arch.png

In case of integration with ERP systems, which have SOAP based API, SOAP connector would have to be used, instead of the REST connector shown in the architecture diagram above.

Please visit here for more details.

 

**The views expressed in this post are my own and do not necessarily reflect the views of Oracle.

In the previous blog post, we discussed about Identity propagation from Java Cloud Service - SaaS Extension (JCS-SX) to Document Cloud Service (DCS) using SAML token.  Here we will explore achieving Identity Propagation by using OAuth 2.0 tokens.

 

To know more about OAuth 2.0 specification, go here.

 

Broadly the architecture of the interaction between JCS-SX, DCS and SIM is as follows:

architecture_oauth.png

 

There are 4 roles in OAuth 2.0 :

Client - the client application that is requesting resources.  In our use case - this would be the JCS-SX Servlet

Resource Owner - the person/entity on whose behalf the resources are being requested.  This is the end user who is logging in.

Resource Server - the server that hosts the protected resources.  This is the Document Cloud Service.

Authorization (OAuth) Server - the server that authenticates the user and issues tokens to the client - this is the Shared Identity Management (SIM)

 

 

The SIM and OWSM enable OAuth token generation and validation seamlessly in Oracle Public Cloud environment.  The pre-requisites for these are

1) Registering a client with the OAuth server - particularly having the client_id and client_secret

2) Registering what resource do we need to access for this client

3) Importing the OAuth signing certificates into the trust store of the Resource Server

4) Generating a client signing certificate and importing it to the Oauth Server.

 

These steps are automatically done during cloud provisioning for different Cloud Services in the same identity domain.  For e.g. in the identity domain having JCS-SX and Document Cloud Service, if you go to the OAuth administration tab, you can see the following already created.

oauth_tab.png

 

Make sure that the resource is accessible to the JCS SX OAuth Client.  You can check list by clicking on the "Modify OAuth Client" option available and see if the same is ticked as below.

modify_oauth_client.png

 

The next step is to create a CSF key for the map "oracle.wsm.security" with the client_id and client_secret corresponding to the JCS-SX.  For this you need to have the JCS-SX SDK (go here for download ) installed.  Replace the values in angular brackets with the actual values.   The credentials here should be of a user who has the Identity Domain Administrator role.

 

set SDK_LIB={location where JCS-SDK is installed}/lib
java -jar %SDK_LIB%/javacloud.jar -user {user} -password {pass} -identitydomain {id-domain} -serviceinstance {sc-name} -datacenter {dc} -set-credential -map oracle.wsm.security -key myoauth.csf.key -keyuser 01b410bc-3b63-4804-9728-c1c64d493526 -keypassword  aUyQltthbCkBzgTZlWw7

 

Next change the JCS-SX Servlet to use the oracle/http_oauth2_token_over_ssl_client_policy.  This policy mandates that the oracle/oauth2_config_client_policy should also be attached. The full code is given below.  Replace example.documents.us.oraclecloud.com with the documents instance URL and example.identity.us.oraclecloud.com with the SIM URL.

 

package com.oracle.rest;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.api.client.filter.LoggingFilter;
import com.sun.jersey.client.urlconnection.HTTPSProperties;

import java.io.IOException;
import java.io.PrintWriter;

import java.security.NoSuchAlgorithmException;

import java.util.Map;

import javax.net.ssl.SSLContext;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import oracle.wsm.agent.handler.jaxrs.RESTClientFilter;
import oracle.wsm.metadata.feature.AbstractPolicyFeature;
import oracle.wsm.metadata.feature.PolicyReferenceFeature;
import oracle.wsm.metadata.feature.PolicySetFeature;
import oracle.wsm.metadata.feature.PropertyFeature;

public class DocsOAuthServlet extends HttpServlet {

    private static final String ENDPOINT_URL =
        "https://example.documents.us.oraclecloud.com/documents/api/1.1/folders/self";
    private static final String CLIENT_CSF_KEY = "myoauth.csf.key";
    private static final String OAUTH_TOKEN_ENDPOINT =
        "https://example.identity.us.oraclecloud.com/oam/oauth2/tokens";


    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }


    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
                                                                                          IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        try {
            ClientConfig cc = new DefaultClientConfig();
            Map<String, Object> properties = cc.getProperties();
            SSLContext sslContext = null;
            try {
                sslContext = SSLContext.getInstance("TLS");
            } catch (NoSuchAlgorithmException e) {
                out.println("Exception in setting sslContext is : " + e + " exc messaage is: " + e.getMessage());
            }


            properties.put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(null, sslContext));
            //Adding the policy - starts
            PropertyFeature[] oauthClientPropFeature = new PropertyFeature[1];
            oauthClientPropFeature[0] = new PropertyFeature("oauth2.client.csf.key", CLIENT_CSF_KEY);
            PropertyFeature oauthGeneralPropFeature = new PropertyFeature("token.uri",OAUTH_TOKEN_ENDPOINT);
            
            PolicyReferenceFeature[] oauthPolicyFeature = new PolicyReferenceFeature[] {
                new PolicyReferenceFeature("oracle/oauth2_config_client_policy", oauthGeneralPropFeature ),
                new PolicyReferenceFeature("oracle/http_oauth2_token_over_ssl_client_policy", oauthClientPropFeature)
            };
            properties.put(AbstractPolicyFeature.ABSTRACT_POLICY_FEATURE, new PolicySetFeature(oauthPolicyFeature));
            //Adding the policy - finishes
            
            Client client = Client.create(cc);


            //Adding the client filter - starts
            ClientFilter filter = new RESTClientFilter();
            client.addFilter(new LoggingFilter(System.out));
            client.addFilter(filter);
            //Adding the client filter - finishes
          

            WebResource resource = client.resource(ENDPOINT_URL);
            ClientResponse clientResponse = resource.get(ClientResponse.class);
            String output = clientResponse.getEntity(String.class);
            out.println("\n\n<br>----------Output starts--------------");
            out.println("<br/>Response body from Service is : " + output);
            out.println("<br/>HTTP Status : " + clientResponse.getStatus());
            out.println("<br/>--------------Output ends----------------");
        } catch (Exception e) {
            out.println("Exception is : " + e + " exc messaage is: " + e.getMessage());
        }
        out.println("</body></html>");
    }


    public void destroy() {
        super.destroy();
    }
}

 

In the web.xml we add another servlet entry corresponding to DocsOAuthServlet as below:

<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <servlet>
        <servlet-name>Servlet1</servlet-name>
        <servlet-class>com.oracle.rest.DocsServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Servlet1</servlet-name>
        <url-pattern>/saml</url-pattern>
    </servlet-mapping>
    
        <servlet>
        <servlet-name>Servlet2</servlet-name>
        <servlet-class>com.oracle.rest.DocsOAuthServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Servlet2</servlet-name>
        <url-pattern>/oauth</url-pattern>
    </servlet-mapping>
    
    
    <security-constraint>
        <display-name>name</display-name>
        <web-resource-collection>
            <web-resource-name>name</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
    </security-constraint>
    <login-config>
        <auth-method>CLIENT-CERT</auth-method>
        <realm-name>default</realm-name>
    </login-config>
</web-app>

 

Accessing the servlet URL should give an output as below:

oauth_output1.png

The servlet will call the DCS REST API with the authorization header "Bearer <oauth token" and display the result in the servlet response.  The process of getting the token is handled by the OWSM agent on the JCS-SX side.

 

Jersey 1.0 provides a LoggingFilter class that can be used for logging requests.  We have attached this filter in our code:

            client.addFilter(new LoggingFilter(System.out));

 

We can check the output of the LoggingFilter from the system logs provided by JCS-SX.  Go to the JCS-SX Console -> View Log Messages

 

oauth_console_log.png

 

Additional information

In case you dont attach the oracle/oauth2_config_client_policy, you will get an exception like the below

oracle.wsm.common.sdk.WSMException: WSM-07620 : Agent cannot enforce policies due to either failure in retrieving polices or error in validations, detail= "WSM-01797 Attachment of an oauth2 client policy without oauth2 config policy is invalid. Ensure you attach a valid oauth2 config policy either via Direct Policy Attachment or via Global Policy Attachment along with an oauth2 client policy. "

 

 

Note that, there is a mandatory property "token-uri" that needs to be provided to the oauth2_config_client_policy.  This is the OAuth 2.0 token endpoint and will typically be <iddomain>.identity.oracle.com/oam/oauth2/tokens.  For more details refer to the OWSM oauth2_config_client_policy documentation.

 

For the http_oauth2_token_over_ssl_client_policy, there is a mandatory property called oauth2.client.csf.key.  This will hold the key name in the oracle.wsm.security map that will store the client_id and client_secret for the JCS-SX client.  If the required key doesnt exist in the oracle.wsm.security map you will get an exception like the below

 

oracle.wsm.common.sdk.WSMException: WSM-00054 : The password credentials cannot be obtained from the Credential Store Framework (CSF). Unable to retrieve the csfKey "myoauth.csf.key". The csf map "oracle.wsm.security" found in the credential store is being used and the following keys are found in this map :- []

 

 

For more details, refer to the OWSM http_oauth2_token_over_ssl_client_policy documentation

 

The views expressed in this post are my own and do not necessarily reflect the views of Oracle.

In this blog, I will be covering step-by-step process to implement Push-Notification in Oracle JavaScript Extension Toolkit (JET) hybrid application. To complete the entire Push-notification ecosystem, the notification will be sent from Oracle Mobile Cloud services (MCS) to Firebase Cloud Messaging (FCM) as the notification server, and these notifications will be received by our JET hybrid app.

Note: Google Cloud Message (GCM) will be upgraded to Firebase Cloud Messaging (FCM) soon, so this blog is applicable to both GCM and FCM

Key Concepts

Below tables summarizes the key components involved in implementing for Push Notification:

ComponentDetails
GCM/FCM serverGCM/FCM will be involved in sending messages between Oracle MCS and the client app.
Oracle MCSOracle MCS sends data to the client application via the GCM/FCM server.
Client Application (JET Hybrid app)A JET Hybrid application that communicates with Oracle MCS

 

 

Push Notification Life-cycle Flow

Below diagram shows the life-cycle flow for implementing Push Notification:

 

Push Notification Flow.png

Below are the main steps involved:

  1. First android device sends Sender ID, application ID to GCM/FCM for registration
  2. Upon successful registration, GCM/FCM server issues Registration ID to android device
  3. After receiving registration id, android device will send this registration id to Oracle MCS
  4. Oracle MCS will store registration id for later use
  5. Whenever push notification is required, it will be sent via Oracle MCS to GCM/FCM , it will send message and registration ID
  6. GCM/FCM server will deliver  that message to mobile device.

 

 

Below table summarizes key terms used:

CredentialsDetails
Sender IDThis is the “Project Number” in Google Developer Console. The sender ID is used in the registration process by the application with GCM/FCM
Application ID

The  JET hybrid client application that is registering to receive messages.  Application ID will be the Package name (from application manifest) incase of hybrid app running on android device

Registration IDThe registration ID issued by GCM/FCM server to the client application that allows it to receive messages, i.e. notifications

 


Component 1: Cloud Messaging Server: FCM/GCM

Firebase Cloud Messaging (FCM) is the new version of GCM, so demonstrating FCM instead of GCM.

The first step is to enable Notification from Firebase network, below are the details:

 

  1. Go to https://console.firebase.google.com/ and  Create a New Project
  2. Provide Project Name and  Country/Region
  3. Click on "Add APP" and select Android version
  4. Add Firebase to your app, enter Package Name,  please note that the package name should match exactly with your hybrid application name
  5. Click on Add App, a google-services.json file will be downloaded on your system
  6. Please take note of  “project_number” and “api_key” values, these values will be used in Oracle MCS and in client app

 

google-services.png

 

Component 2: Our Application Server: Oracle MCS

The next step is to register the client application in Oracle MCS:

  1. Click menu icon in the upper left corner of the page and select Applications > Mobile Backends.
  2. Select the mobile backend you want to use and click Open.
  3. In the left navigation bar, click Clients.
  4. In the Client Applications part of the page, identify the client application, and click Register Client.
  5. In the dialog, select the platform and fill in the information needed, based on your platform.
  6. For Android, you insert the Google API Key in the API Key field.
  7. Make a note of the value of the Application Key that is shown in the dialog when you create the client. You'll need to add this key to the configuration file

 

GoogleCloudMessagingKey.png

 

Component 3: Client Application

Before starting the client application, we need to configure our system for setting up the pre-requisites for developing for Android device, below are the same:

 

Android Settings in your machine

Please ensure following items are updated on your system:

  1. Android SDK 23 version or above (latest version is preferred)
  2. Upgrade Android SDK Tools for following to their latest version available:
    • Android Support Library version
    • Local Maven repository for Support Libraries (Android Support Repository)
    • Google Play Services version

 

sdktools.png

 

Developing client-side Oracle JET Hybrid application

We will start implementing from scratch to implement receiving push notification in oracle jet hybrid application. You may please refer to Troubleshooting while developing your First JET based Hybrid Application blog incase of initial issues faced during development/configuration issues.

 

Project Setup using Yeoman

Yeoman generator for Oracle JET lets you quickly set up a project for use as a Web application or mobile-hybrid application for Android and iOS. Use following command to generate hybrid application for Android:

 

yo oraclejet:hybrid pushnotifysample --appId=com.rdh.pushnotify --appName="pushnotifysample" --template=navBar --platforms=android

 

 

The navBar template based native hybrid application code will be placed in “pushnotifysample” folder.

 

Cordova-plugin-push

  • In command prompt, go to pushnotifysample\hybrid folder
  • Now, add cordova-plugin-push plugin.This plugin offers support to receive and handle native push notifications with a single unified API, and with no dependency on any other plugin’s. Here is the link: https://github.com/phonegap/phonegap-plugin-push

 

cordova plugin add phonegap-plugin-push --variable SENDER_ID=" XXXXXXXX"

 

Where the XXXXXXX in SENDER_ID="XXXXXXX" maps to the project number in the Firebase developer console

 

 

Adding Oracle MCS Cordova SDK

In order to communicate with Oracle MCS, following steps are required:

  1. Download the Cordova SDK from Oracle MCS. Extract the same on your local machine. It will contain Javascript based Cordova SDK , configuration files and documentation
  2. Add Oracle MCS Cordova SDK to your application, Copy mcs.js, mcs.min.js and oracle_mobile_cloud_config.js into the directory where you keep your JavaScript libraries.

 

For example, in this implementation, I have kept these files in mcs folder added in js/libs folder as shown in below image:

mcs-additions.png

2. Fill in your mobile backend details in oracle_mobile_cloud_config.js.

 

mcs_config.png

 

For more details, please See Configuring SDK Properties for Cordova.

 

After adding the physical files, update the paths mapping for mcs and mcs_cloud_config  in main.js file under requirejs.config section:

     paths:
               //injector:mainReleasePaths
                            {
                                'knockout': 'libs/knockout/knockout-3.4.0.debug',
                                'jquery': 'libs/jquery/jquery-2.1.3',
                                'jqueryui-amd': 'libs/jquery/jqueryui-amd-1.11.4',
                                'promise': 'libs/es6-promise/promise-1.0.0',
                                'hammerjs': 'libs/hammer/hammer-2.0.4',
                                'ojdnd': 'libs/dnd-polyfill/dnd-polyfill-1.0.0',
                                'ojs': 'libs/oj/v2.0.2/debug',
                                'ojL10n': 'libs/oj/v2.0.2/ojL10n',
                                'ojtranslations': 'libs/oj/v2.0.2/resources',
                                'text': 'libs/require/text',
                                'signals': 'libs/js-signals/signals',
                                'mcs': 'libs/mcs/mcs',
                                'mcsconf': 'libs/mcs/oracle_mobile_cloud_config'
                            }
                    //endinjector

 

 

Implementation Steps

 

Add the additional modules: mcs and mcsconf to get loaded in dashboard.js file:

 

define(['ojs/ojcore', 'knockout', 'jquery', 'mcs', 'mcsconf'],

 

Below are the four main steps of push notification implementation, they need to be called only once in the application life cycle.

 

 

Step 1.  Load Mobile Back end’s Configuration into the application:

function initializeMCS() {
    mcs.MobileBackendManager.platform = new mcs.CordovaPlatform();
    mcs.MobileBackendManager.setConfig(mcs_config);
    mcsBackend = mcs.MobileBackendManager.getMobileBackend("BACKEND_NAME_AS_MENTIONED_IN_CONF_FILE");
    if (mcsBackend != null) {
        mcsBackend.setAuthenticationType("basicAuth");
    }
}

 

 

Step 2. Authenticate and Log In Using the SDK

               function login(mcsBackend, username, password) {
                    mcsBackend.Authorization.authenticate(username, password, success, failed);
                    function success(response, data) {
                        alert("Login Success!");
                        registerDeviceForMCSPush(mcsBackend);
                    }
                    function failed(response, data) {
                        alert("Login failed!");                       
                    }
                }

 

 

Step 3. Below code initializes, registers the device and is ready to receive notification or error from GCM/FCM

   function registerDeviceForMCSPush(mcsBackend) {
        var defer = $.Deferred();
        if (typeof PushNotification !== 'undefined') {
            try {
                var push = PushNotification.init({
                    "android": {
                        // TODO replace Google Project Number here
                        senderID: "XXXXXXXXX"
                    }
                });
                push.on('registration', function (data) {
                    var regId = data.registrationId;
                    deviceHandshakeforCordova(mcsBackend, regId);
                });
                push.on('notification', function (data) {
                    alert("Push Notification from Oracle MCS: " + data.message);
                });
                push.on('error', function (e) {
                    alert("Push Notification Error=" + e.message);
                });
            } catch (ex) {
                alert("Error registering device with MCS" + ex);
                defer.reject();
            }
        } else {
            alert("PushNotification NOT Defined!");
            defer.reject();
        }
        return $.when(defer);
}

 

 

Step 4. Device Handshake, here the application sends Registration Id to Oracle MCS

function deviceHandshakeforCordova(mcsBackend, registrationID)
{
    var appId = "your_package_name_here";
    var appVersion = "1.0";
    mcsBackend.Notifications.registerForNotifications(registrationID, appId, appVersion,
            function (statusCode, headers, data) {
                var success_msg = "sucess:statusCode=" + statusCode + ",data=" + data + "headers=" + headers;
                console.log(success_msg);
            },
            function (statusCode, data) {
                var failure_msg = "failure:statusCode=" + statusCode + ",data=" + data;
                console.log(failure_msg);
            });
}

 

Build and Run the application on Android emulator/device

 

In your command prompt, please change directory to project folder, e.g. pushnotifysample in this case and run the following command:

 

  1. Build the application using following command
grunt build --platform=android

 

  1. Once build is success, then run the application using following command, assuming android emulator is already up and running:
grunt serve --platform=android  --disableLiveReload=true

Test Push notification

At this point of time the client app is ready to receive push notifications from GCM/FCM

 

Sending notification from MCS console

To test the application, we can send a notification from MCS console

pushnotifyconsole.png

 

The message would be sent to FCM, which in turn will send to our registered application. If the application is in background, it will receive the notification in the notification area, on tapping the notification, it will land you to your app for further action

device-2016-08-11-171917.png

 

Troubleshooting section

Issue 1: ERROR: During build process, encounter following error:

 

Could not resolve com.google.android.gms:play-services-gcm:9.0.2+

googleplayservices-bug.png

Possible Solution:

Please update following to the latest:

  • Android Support Library
  • Local Maven repository for Support Libraries (formerly Android Support Repository)
  • Google Play Services

 

 

Issue 2: Client Application throws 400 Bad Request after logging is success

 

Failed to load resource: the server responded with a status of 400 (Bad Request)

 

Possible Solution:

 

Please confirm that you are sending correct registrationID, appId and appVersion to mcsBackend.Notifications.registerForNotifications function.Example :

Registration ID: "AxdkfjfDfkfkfkfjfFdjfjjf=",

appId: "com.yourcompany.project"

appVersion: "1.0"

 

***EDIT on 18-Aug-2016,  Change log: Added Issue 3***

 

Issue 3: During building the application, I get "No platforms added to this project" error.

Error log on console:

 

D:\jetapps\jetdemo>grunt build --platform=android
Running "build" task
Running "clean:www" (clean) task
>> 0 paths cleaned.
Running "copy:wwwDev" (copy) task
Created 212 directories, copied 1274 files
Running "copy:merges" (copy) task
Created 8 directories, copied 37 files
Running "includeCordovaJs" task
Running "shell:cordovaPrepare" (shell) task
>> Error: No platforms added to this project. Please use `cordova platform add <platform>`.
>>
Warning: Done, with errors: command "cordova prepare android" (target "cordovaPrepare") exited with
code 1. Use --force to continue.
Aborted due to warnings.

 

Possible Solution:

Probably  it's path problem, you didn't add android SDK path in your environment variable. Please download latest android SDK and give path in environment variable and try again

In-case of Windows: Go to System Properties ->  Environment Variable : Add ANDROID_HOME as variable and path should be path to your Android SDK, For example path is C:\Users\rdh\AppData\Local\Android\Sdk

 

 

**The views expressed in this post are my own and do not necessarily reflect the views of Oracle.

To use multiple cloud services as one cohesive unit, it is often needed to propagate identity from one PaaS cloud service to another. For e.g. an application in Java Cloud Service or Mobile Cloud Service that needs to access some RESTful resources in another PaaS service like Documents Cloud Service.    In this blog post I will discuss one such case i.e. identity propagation from Java Cloud Service – SaaS Extension (JCS-SX) to Documents Cloud Service (DCS).  The assumption is that both these services are hosted in the same identity domain.

 

First a note about the two common services that make identity propagation possible.

 

  • In Oracle Cloud, the Shared Identity Management (SIM) provides Identity Management, Single Sign On, Federation and other services out-of-the-box to Oracle PaaS services.  The SIM also acts as the OAuth Server to handle OAuth flows.

 

  • Oracle Web Services Manager (OWSM) provides a policy management and enforcement framework to secure and manage Web Services at design time via code or post deployment from administration consoles.  OWSM can be used to manage both REST and SOAP web services.

 

Our Use Case is based on an application hosted in JCS-SX which has a Servlet (or REST service) accessing a Documents Cloud Service REST API.  Here we have two resources:

  • Resource A in JCS-SX – protected by security-constraint in web.xml, which requires the user to login in order to access the resource.
  • Resource B in Documents Cloud Service – protected by the internal Documents Cloud Service OWSM.

 

The sequence of events that occur when the user requests the URL of the servlet are as follows:

  1. User tries to access Resource A (servlet URL from the browser)
  2. Since Resource is protected, user asked to login
  3. Post Login, User is redirected to the Resource A
  4. Resource A makes a call to Resource B.  Resource B should work in the context of logged in user. E.g. return folders that belong to the logged in user.

 

Here the JCS-SX Servlet acts as the REST client w.r.t the Documents Cloud Service.

 

architecture.png

 

The Document Cloud Service REST APIs are protected by the oracle/multi_token_over_ssl_rest_service_policy (details here) which enforces one of the following authentication policies, based on the token sent by the client:

  • HTTP Basic over SSL—Extracts username and password credentials from the HTTP header.
  • SAML 2.0 Bearer token in the HTTP header over SSL—Extracts SAML 2.0 Bearer assertion in the HTTP header.
  • HTTP OAM security (non-SSL)—Verifies that the OAM agent has authenticated user and establishes identity.
  • JWT token in the HTTP header over SSL—Extracts username from the JWT token in the HTTP header

 

So we could use potentially any of these methods to propagate identity from JCS-SX to DCS (However the first one - HTTP Basic over SSL would require putting in credentials again and is not really a true identity propagation method)  Here I will be using the SAML 2.0 Bearer token as the identity propagation method.

 

The steps to achieve this are as follows:

 

Step 1 – Create JCS-SX Servlet that calls a Documents Cloud Service REST API

Note – replace example.com with the URL for your Documents Cloud Service instance and the API endpoint as any DCS REST API that you need.  The full list of DCS REST APIs are published here.  In this example I have used the ENDPOINT_URL to point to a special folder called "self".

 

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.client.urlconnection.HTTPSProperties;

import java.io.IOException;
import java.io.PrintWriter;

import java.security.NoSuchAlgorithmException;

import java.util.Map;

import javax.net.ssl.SSLContext;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DocsServlet extends HttpServlet {

 private static final String ENDPOINT_URL =
        "https://example.com/documents/api/1.1/folders/self";

 public void init(ServletConfig config) throws ServletException {
        super.init(config);
 }

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
 out.println("<html><body>");
        try {
            ClientConfig cc = new DefaultClientConfig();
            Map<String, Object> properties = cc.getProperties();
            SSLContext sslContext = null;
            try {
                sslContext = SSLContext.getInstance("TLS");
            } catch (NoSuchAlgorithmException e) {
                out.println("Exception in setting sslContext is : " + e + " exc messaage is: " + e.getMessage());
            }

 properties.put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(null, sslContext));

            Client client = Client.create(cc);
            WebResource resource = client.resource(ENDPOINT_URL);
            ClientResponse clientResponse = resource.get(ClientResponse.class);
            String output = clientResponse.getEntity(String.class);
 out.println("\n\n<br>----------Output starts--------------");
 out.println("<br/>Response body from Service is : " + output);
            out.println("<br/>HTTP Status : " + clientResponse.getStatus());
 out.println("<br/>--------------Output ends----------------");
        } catch (Exception e) {
            out.println("Exception is : " + e + " exc messaage is: " + e.getMessage());
        }
 out.println("</body></html>");
 }

 public void destroy() {
 }       super.destroy();
 }

 

I am using maven as a dependency management tool.  Here is the pom.xml

<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.oracle.idprop</groupId>
 <artifactId>idproptodocs</artifactId>
 <version>1.0-SNAPSHOT</version>
 <description>Project for idproptodocs</description>
 <packaging>war</packaging>
 <properties>
 <jersey-version>1.19.1</jersey-version>
 </properties>
 <build>
        <resources>
            <resource>
 <directory>${basedir}</directory>
                <includes>
 <include>*</include>
                </includes>
            </resource>
            <resource>
 <directory>src/main/resources/</directory>
                <includes>
 <include>*</include>
                </includes>
            </resource>
        </resources>
 </build>
 <dependencies>
        <dependency>
 <groupId>javax.servlet</groupId>
 <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <type>jar</type>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
 <artifactId>json</artifactId>
 <version>20140107</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
 <groupId>com.sun.jersey</groupId>
 <artifactId>jersey-json</artifactId>
 <version>${jersey-version}</version>
            <type>jar</type>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
 <groupId>com.sun.xml.bind</groupId>
 <artifactId>jaxb-impl</artifactId>
                </exclusion>
                <exclusion>
 <groupId>com.sun.xml.bind</groupId>
                    <artifactId>jaxb-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
 <groupId>com.sun.jersey</groupId>
 <artifactId>jersey-core</artifactId>
            <version>${jersey-version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
 <groupId>com.sun.jersey</groupId>
 <artifactId>jersey-servlet</artifactId>
 <version>${jersey-version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
 <groupId>com.sun.jersey</groupId>
 <artifactId>jersey-bundle</artifactId>
 <version>${jersey-version}</version>
            <type>jar</type>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
 <groupId>com.sun.xml.bind</groupId>
                    <artifactId>jaxb-impl</artifactId>
                </exclusion>
                <exclusion>
 <groupId>com.sun.xml.bind</groupId>
 <artifactId>jaxb-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
 <groupId>com.sun.jersey.contribs</groupId>
 <artifactId>jersey-multipart</artifactId>
 <version>${jersey-version}</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
 </dependencies>
</project>

 

 

And the following is the web.xml entry.  We use a security-constraint and a CLIENT-CERT login-config element in the web.xml to protect the DocsServlet and make it a Tenant Restricted Page

 

<?xml version = '1.0' encoding = 'windows-1252'?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
 <servlet>
 <servlet-name>Servlet1</servlet-name>
 <servlet-class>com.oracle.rest.DocsServlet</servlet-class>
 </servlet>
 <servlet-mapping>
 <servlet-name>Servlet1</servlet-name>
 <url-pattern>/saml</url-pattern>
 </servlet-mapping>
 <security-constraint>
 <display-name>name</display-name>
        <web-resource-collection>
 <web-resource-name>name</web-resource-name>
 <url-pattern>/*</url-pattern>
        </web-resource-collection>
 </security-constraint>
 <login-config>
 <auth-method>CLIENT-CERT</auth-method>
 <realm-name>default</realm-name>
 </login-config>
</web-app>

 

 

And at last - the weblogic.xml entry

<?xml version = '1.0' encoding = 'windows-1252'?>
<weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.7/weblogic-web-app.xsd"
 xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app">
 <container-descriptor>
 <prefer-web-inf-classes>true</prefer-web-inf-classes>
 </container-descriptor>
 <context-root>idproptodocs</context-root>
</weblogic-web-app>

 

Create the WAR for the application and deploy to JCS-SX via the console.  For details on how to deploy to JCS-SX via the Java console, please consult the documentation

 

Post deployment, access the Servlet URL from the browser.  It will first prompt for the user to login.

cloud_sign_in.png

On successful login, it should navigate to the Servlet page, which should give output as below:

output1.png

The 401 indicates that the Document Cloud Service API couldn’t authorize the request made by the JCS-SX Servlet.

Step 2 – Import required libraries for OWSM

The wsm-policy-core and wsm-rest-lib jars are needed for compilation purposes in the IDE.  You can find these jars from the 11g local installation of Weblogic in the following location

MW_HOME/oracle_common/modules/oracle.wsm.common_11.1.1/wsm-policy-core.jar

MW_HOME/oracle_common/modules/oracle.wsm.common_11.1.1/wsm-rest-lib.war

 

Copy these to a specific folder in your local filesystem for use with Maven and then add them in your pom.xml with scope as system.  I have copied the files to C:\temp in my example.  These are referred by Maven only during compilation and are not packaged.

        <!-- owsm -->
        <dependency>
            <groupId>com.oracle.wsm</groupId>
 <artifactId>wsm-rest-lib</artifactId>
            <version>11.1</version>
            <type>jar</type>
            <scope>system</scope>
 <systemPath>C:\temp\wsm-rest-lib.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>com.oracle.wsm</groupId>
 <artifactId>wsm-policy-core</artifactId>
            <version>11.1</version>
            <type>jar</type>
            <scope>system</scope>
 <systemPath>C:\temp\wsm-policy-core.jar</systemPath>
        </dependency>
        <!-- owsm -->

 

The following two libraries are also needed to be deployed to the JCS-SX instance as libraries if they don’t exist there.  The steps for deploying a shared library are given here.

MW_HOME/oracle_common/modules/oracle.webservices_11.1.1/wls-rest-client.war

MW_HOME/oracle_common/modules/oracle.wsm.common_11.1.1/wsm-rest-lib.war

 

libraries.png

Step 3 – Attach required policy to the REST Client

Add properties to the ClientConfig to use the respective client side OWSM policies.  Here we would attach a SAML based client policy.  This means that the OWSM would intercept the REST request, inspect the authenticated subject within JCS-SX, generate a SAML based token from the user assertion, and insert the SAML token as a Bearer token as an Authorization header in the REST request.

For this we would use the “oracle/http_saml20_token_bearer_over_ssl_client_policy”.  The following code illustrates this:

 

          

 //Adding the policy - starts
            PolicyReferenceFeature[] samlfeature = new PolicyReferenceFeature[] {
                new PolicyReferenceFeature("oracle/http_saml20_token_bearer_over_ssl_client_policy",
                                           new PropertyFeature[] { })
            };
 properties.put(AbstractPolicyFeature.ABSTRACT_POLICY_FEATURE, new PolicySetFeature(samlfeature));
            //Adding the policy - finishes
            
            Client client = Client.create(cc);

 

 

To intercept the REST requests and generate and insert the SAML token, attach the OWSM provided out-of-the-box filter for REST Clients called RestClientFilter to the JAX RS Client.

           //Adding the client filter - starts
 ClientFilter filter = new RESTClientFilter();
                 client.addFilter(filter);
//Adding the client filter - finishes

 

Modify the weblogic.xml to refer to the deployed libraries as below

<?xml version = '1.0' encoding = 'windows-1252'?>
<weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app
 http://xmlns.oracle.com/weblogic/weblogic-web-app/1.7/weblogic-web-app.xsd"
                  xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app">
    <container-descriptor>
 <prefer-web-inf-classes>true</prefer-web-inf-classes>
    </container-descriptor>
 <context-root>idproptodocs</context-root>
    <library-ref>
        <library-name>wsm-rest-lib</library-name>
    </library-ref>
    <library-ref>
 <library-name>wls-rest-client</library-name>
    </library-ref>
</weblogic-web-app>

Generate the war file and re-deploy to JCS-SX.

 

Now re-run the Servlet - You will see the output as below which indicates that the identity of the logged in user was propagated to the DCS REST API.

output2.png

The output is actually the JSON response of GET request issued to the ENDPOINT_URL of Documents Cloud Service instance - which is the folder information of the "self" folder.

 

The views expressed in this post are my own and do not necessarily reflect the views of Oracle.