Build an Async Restful Webservice Client in Android in 5min and read BA flight data

In an earlier blog post we build a Restful WS running on Wildfly, now lets build the client part for Android. Thanks to the okHttp, an Apache 2.0 licensed java and Android library, this becomes a very easy challenge. In a mobile application we definitely want to implement a async call as we cannot rely on the response time being fast and a blocked application is not a pleasant user experience.

To make it a bit more interesting for the aviation context of my blog, we will take a real airline webservice and show operational flight data  in the second step.

Step 1: The Basic Webservice Client

Lets get started, I skip the project creation steps, you can create any basic empty Android application.

Add the dependency

..
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.1.1'
    testCompile 'junit:junit:4.12'
compile 'com.squareup.okhttp3:okhttp:3.9.0'
}

Implement the WS call with a call back

(we use the serverside we implemented previously with 2 parameters)

    private void testOKHttp() {

        OkHttpClient httpClient = new OkHttpClient();
        String callURL = "http://www.yourservername.com/DemoRandomizer/randomservice/random/numberrange";

        HttpUrl.Builder urlBuilder = HttpUrl.parse(callURL).newBuilder();
        urlBuilder.addQueryParameter("min", "10");
        urlBuilder.addQueryParameter("max", "100");
        callURL = urlBuilder.build().toString();

        Request request = new Request.Builder().url(callURL).build();
        httpClient.newCall(request).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println(e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                ResponseBody responseBody = response.body();
                if (!response.isSuccessful()) {
                    throw new IOException("Error response " + response);
                } else {
                    String str = new String(responseBody.bytes());
                    System.out.println(str);
                }
            }
        });

    }

It is good practice to block UI features (buttons, etc) that rely on the WS response or use a progress dialogm which is in some way blocking but the user is aware of the ongoing request.

 

// Set progress dialog befor the call
        prgDialog = new ProgressDialog(this);
        prgDialog.setMessage("Please wait...");
        prgDialog.setCancelable(false);
        prgDialog.show();

// remove the call after successful response or error
        prgDialog.dismiss();

Step 2: Client to read British Airways data

device-2017-10-14-092750

Lets gets our hand on some real data and tap into one of the open API’s offered by some airlines and airports, though I have to say that only very few players offer their data for public consumption. I will list some API’s in another blog post. Today we will make use of the British Airways API running on the Tibco Mashery platform. BA offers a variety of flight related data and we look at the flight status webservice. We can use the service for evaluation purpose up to 1 call a second and 500 calls a day, good enough to play with their data. We will collect the status of outbound flights from LHR (London Heathrow) for +/- 2hours. Before jumping into sourcode I recommend using a tool to test the webservice manually, eg. the Chrome Browser Rest Web Service Client extension.

2017-10-14 09_24_58-Rest Web Service Client

Using Chrome extension to get data from BA Webservice

One of the challenges, every API offering might support JSON and XML as WS GET, but the attributes and response format is different, we have to implement for every airline or airport we want to use the data, or add some kind of mapping layer.

For the BA service we have to change to call from step 1 above because we need to add the API key that you apply for under your account, also the parameter are not handled as query parameters but as parameter matrix.

The first code block only lists the relevant parts and the complete code block below adds some extras to handle GUI access, exception handling and the parameter creation.

 private void testWSCall() {

        OkHttpClient httpClient = new OkHttpClient();
        String callURL = "https://api.ba.com/rest-v1/v1/flights;departureLocation=LHR;startTime=12:00;endTime=18:00;";

        Request request = new Request.Builder().url(callURL)
                .addHeader("Content-Type", "application/json")
                .addHeader("client-key", "YOUR_KEY_HERE")
                .build();

        httpClient.newCall(request).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("WS Call failed (1):" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) {
                ResponseBody responseBody = response.body();
                if (!response.isSuccessful()) {
                    final String errRep = "WS Call failed (2):" + response.toString();
		    System.out.println(errRep);
                } else {
                    String str = new String(responseBody.bytes());
		    System.out.println(str);
                }
            }
        });

}
 private void testOKHttp() {

        OkHttpClient httpClient = new OkHttpClient();

        DateFormat dateFormatShort = new SimpleDateFormat("HH:mm");
        Date date = new Date();
        long timeNow = date.getTime();
        long timePlus = timeNow + 120 * 60 * 1000;
        long timeMinus = timeNow - 120 * 60 * 1000;
        Date datePlus = new Date(timePlus);
        Date dateMinus = new Date(timeMinus);

        String callURL = "https://api.ba.com/rest-v1/v1/flights;departureLocation=LHR;startTime=" + dateFormatShort.format(dateMinus) +";endTime=" + dateFormatShort.format(datePlus) +";";
        System.out.println(callURL);

        Request request = new Request.Builder().url(callURL)
                .addHeader("Content-Type", "application/json")
                .addHeader("client-key", "YOUR_KEY_HERE<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>")
                .build();

        httpClient.newCall(request).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("WS Call failed (1):" + e.getMessage());
                prgDialog.dismiss();
            }

            @Override
            public void onResponse(Call call, Response response) {
                ResponseBody responseBody = response.body();
                if (!response.isSuccessful()) {
                    final String errRep = "WS Call failed (2):" + response.toString();
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            editTextResult.setText(errRep);
                        }
                    });
                    prgDialog.dismiss();
                } else {
                    try {
                        System.out.println("Response " + response.toString());
                        String str = new String(responseBody.bytes());
                        prgDialog.dismiss();

                        final JSONObject svcresponse = new JSONObject(str);
                        int spacesToIndentEachLevel = 2;
                        final String prettyPrintString = svcresponse.toString(spacesToIndentEachLevel);
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                editTextResult.setText(prettyPrintString);
                            }
                        });

                    } catch (Exception e) {
                        e.printStackTrace();
                        final String errStr = e.getMessage();
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                editTextResult.setText(errStr);
                            }
                        });
                        prgDialog.dismiss();
                    }
                }
            }
        });

    }
Advertisements

MOOC – E-Learning on Steroids

Hardly any industry is moving as fast as the IT industry. While your operational experience and knowledge of the vertical domain you are working in, is growing naturally along your career, it is not the same for IT. For example the airport environment, the underlying basics and physics of handling aircrafts, planning flights, etc. are exposed to changes, innovations and challenges, look at the A-CDM program, it took quite some years to take off and become main-stream, that is a much slower than any new general IT technology or platform soaring. Though this industry is picking up speed too and the boundaries between the digital and physical world start to blur more and more, airports are running digital transformation programs, though passengers still flying in the physical world.

But on the IT side of things, the speed is way beyond breath-taking and it is hard to keep a minimal overview over many areas of IT concerns as well dive into specific topics. How to stay up-to-date and tune into relevant topics ? Books (ink and electronic versions) and forums are certainly the traditional approach, on top of that you join conferences and in-persons seminars and training (which comes at a cost and time spent).

Since the 2000’s online courses came into the picture, as the successor for e-learning, and allow a much bigger audience to learn new technologies, skills and more. The very positive part, there are lots and lots of free courses, most platforms offer free and commercial courses, sometimes free to participate and only charge a fee if you want to get an official certificate (one can argue about the value of such certs) but most important, you can learn and move forward and update your knowledge with the click of a button.

The big challenge though is to identify what you need or interested in, find the right courses and, most important, manage your time. Using your spare time you have to choose wisely, you can’t run for every course out there, even they are so many you are interested in and you are temped to sign up for a dozen of courses, only not to finish any of them.

Todays key-/buzzword for this is MOOC or Massive Open Online Courses. This is like e-learning on steroids, in the past you had to look at dull corporate slides pretty much by yourself, now we look at videos, reading material online and offline, interactions with the organizer, mentor, trainer or your virtual peers at various levels.

Not only the organizations that started online learning, like schools and universities, are into the game, as well companies operating specific online course platforms and now book publishing companies offering courses and finally professional social platforms like LinkedIn.

I attended online courses at Coursera and Udacity, which offer a broad range of topics, and now started with some specific courses on HCI and UX at Interaction Design Foundation which solely offers courses on UX, HCI, Visualization and related topics. Though the courses are unattended (except the rating of your text answers or comments) but repeating, you still have a motivation to participate and go though the lessons because you pay money and they help you pacing the whole course by releasing the lesson packages over time.

Stay tuned for the results.

Build a RESTful Webservice in less than 5 minutes

There are quite some tutorials around about building and exposing a RESTful Webservice, but some of them are outdated and make you wade through complex dependencies and tinkering with deployment descriptors and web.xml files. But using RESTeasy, the JBoss implementation that is fully compliant with the JAX-RS 2.0 JCP specification, and Eclipse you can build a simple webservice (“hello world”) with less than 10 lines of sourceode with annotations and no web.xml used in a few minutes and run it on Wildfly.

Lets build a webservice that creates random numbers.

Continue reading

PostgreSQL Replication Express Setup

The system I work on we deploy almost solely on the Amazon AWS platform. Even I try to design the architecture in a way not to be locked-in too much into Amazon, I make use of the Amazon tools and products as much as possible (EC2, VPC, S3, SNS). PostgreSQL is our reference DB and the only DB product in production environments, still we run dedicated instances with PostgreSQL. I am quite delighted about AWS recent offering staring RDS with PostgreSQL. While is is still in BETA and I did not started yet with a conclusive test and migration plan, I need to maintain our existing instances.

There are plenty of books and tutorials about setting up PostgreSQL replication with on-board tools, without going into the details I share the express setup in this tutorial based on Streaming Replication which is part of PostgreSQL since version 9.0. I highly recommend to review the parameters and settings from the below tutorial as your project might have different requirements.

References

Tutorial

Remarks

  • The tutorial is based on PostgreSQL 9.2 running on Ubuntu Server
  • Paths and settings are all the PostgreSQL defaults.
  • This is async setup, the master will not wait for feedback from the salve and continue to work even the slave is not available

Prerequisite

  • 2 Server running the same PostgreSQL version (9.0+)
  • Backup your data or use a sandbox environment.
  • In the tutorial I refer to
    MASTER (ip: 0.0.0.1) and
    SLAVE (ip: 0.0.0.2)

Configuration

Master

  • Create a replicator user
    sudo -u postgres psql -c "CREATE USER replicator REPLICATION LOGIN ENCRYPTED PASSWORD 'mypassword';"
    
  • Add the slave ip to /etc/postgresql/9.2/main/pg_hba.conf
    host    replication     all      0.0.0.2/32         trust
    
  • Modify parameters in /etc/postgresql/9.2/main/postgresql.conf
    wal_level = hot_standby
    max_wal_senders = 3
    checkpoint_segments = 3
    wal_keep_segments = 3
    

    Review these parameters and set them up according to your requirements

  • Start the PostgreSQL instance
    service postgresql start
    

Slave

  • Modify parameters in /etc/postgresql/9.2/main/postgresql.conf
    wal_level = hot_standby
    max_wal_senders = 3
    checkpoint_segments = 3
    wal_keep_segments = 3
    hot_standby = on
    
  • Stop the PostgreSQL instance
    service postgresql stop
    
  • Clean up the old data directory
    sudo -u postgres rm -rf /var/lib/postgresql/9.2/main
    
  • Copy the database from the master with pg_basebackup
    sudo -u postgres pg_basebackup -h 0.0.0.1 -D /var/lib/postgresql/9.2/main -U replicator -v -P -x
    

    You can see the backup progress and it should result in something likes

    root@:/var/lib/postgresql/9.2/main# transaction log start point: 41/7D000020
    31524952/31524952 kB (100%), 2/2 tablespaces (/var/lib/postgresql/9.2/main/PG_9.)
    transaction log end point: 41/7D0002A8
    pg_basebackup: base backup completed
    
  • Create a recovery configuration file /var/lib/postgresql/9.2/main/recovery.conf
    standby_mode = 'on'
    primary_conninfo = 'host=0.0.0.1 port=5432 user=replicator password=mypassword sslmode=require'
    trigger_file = '/tmp/postgresql.trigger'
    
  • Start the PostgreSQL instance
    service postgresql start
    

    Check the pglogs.

Test the replication

  • Open any table with pgadmin on the master and apply a change, it should be reflected in the slave within short time.
  • Try to change data on the slave, it will fail due to the hot-standby mode

Monitor the replication

  • The master instance will not alert you when the replication is down. You can check by yourself or create a little cronjob to do it for you with this sql statement.
    sudo -u postgres psql -x -c "select * from pg_stat_replication;"
    

    You get the status back (if it is running, otherwise the statement will return ‘no rows’).

    Check replication

    Check replication

Glassfish V3.1.2 and SSL

After almost 3 years (see previous post) I revisit the topic this time using the latest version og Glassfish 3.1.2 and GoDaddy as certificate provider. I created a certificate for a sub-domain (sub.whateverdomain.com) this time and make use of the extremly cheap 5.99 U$/year offer (no wildcard included)

Let me summarize the key steps here: Continue reading

d3.js – From tree to cluster and radial projection

Some visualizations seem to be more sophisticated (to implement) than they actual are, specifically the radial projections. Starting from a tree representation with nodes and links it is quite easy to get to the radial version.
Remark: Of course there are more challenging radial diagrams like the bundle, but lets get started with something simple first !

“Standard” Tree
(I added few more nodes to make the visual difference more obvious)

Tree Visualization

We change 1 line of our sourcecode (see previous tutorial for complete code):

var tree = d3.layout.tree().size([300,300]);
to
var tree = d3.layout.cluster().size([300,300]);

Continue reading

d3.js – Most simple stack layout with bars

In order to understand the physics of various visualizations I walk through the samples and gists I find, dissect them and strip them down to the bare minimum. From there I explore my own variations. I took Mike’s sample for a stacked bar chart and changed it to chart reading from a matrix instead of the csv file.

Stacked Bar Chart

The matrix object

            var matrix = [
                [ 1,  5871, 8916, 2868],
                [ 2, 10048, 2060, 6171],
                [ 3, 16145, 8090, 8045],
                [ 4,   990,  940, 6907],
                [ 5,   450,  430, 5000]
            ];

The stack layout expects an object with an array of objects, each member of the array represents the values of one value group (eg. “all dark blue values”), and each object contains x and y value. It will process theis array amnd a y0 value (the offset to the baseline).
You can see this if you run the below code with the console.log output.

Console

<!DOCTYPE html>
<html>
    <head>
        <title>Simple Stack</title>
        <script type="text/javascript" src="libnew/d3.v2.min.js"></script>
        <style>
            svg {
                border: solid 1px #ccc;
                font: 10px sans-serif;
                shape-rendering: crispEdges;
            }
        </style>
    </head>
    <body>

        <div id="viz"></div>

        <script type="text/javascript">

            var w = 960,
            h = 500

            // create canvas
            var svg = d3.select("#viz").append("svg:svg")
            .attr("class", "chart")
            .attr("width", w)
            .attr("height", h )
            .append("svg:g")
            .attr("transform", "translate(10,470)");

            x = d3.scale.ordinal().rangeRoundBands([0, w-50])
            y = d3.scale.linear().range([0, h-50])
            z = d3.scale.ordinal().range(["darkblue", "blue", "lightblue"])

            console.log("RAW MATRIX---------------------------");
	    // 4 columns: ID,c1,c2,c3
            var matrix = [
                [ 1,  5871, 8916, 2868],
                [ 2, 10048, 2060, 6171],
                [ 3, 16145, 8090, 8045],
                [ 4,   990,  940, 6907],
                [ 5,   450,  430, 5000]
            ];
            console.log(matrix)

            console.log("REMAP---------------------------");
            var remapped =["c1","c2","c3"].map(function(dat,i){
                return matrix.map(function(d,ii){
                    return {x: ii, y: d[i+1] };
                })
            });
            console.log(remapped)

            console.log("LAYOUT---------------------------");
            var stacked = d3.layout.stack()(remapped)
            console.log(stacked)

            x.domain(stacked[0].map(function(d) { return d.x; }));
            y.domain([0, d3.max(stacked[stacked.length - 1], function(d) { return d.y0 + d.y; })]);

            // show the domains of the scales
            console.log("x.domain(): " + x.domain())
            console.log("y.domain(): " + y.domain())
            console.log("------------------------------------------------------------------");

            // Add a group for each column.
            var valgroup = svg.selectAll("g.valgroup")
            .data(stacked)
            .enter().append("svg:g")
            .attr("class", "valgroup")
            .style("fill", function(d, i) { return z(i); })
            .style("stroke", function(d, i) { return d3.rgb(z(i)).darker(); });

            // Add a rect for each date.
            var rect = valgroup.selectAll("rect")
            .data(function(d){return d;})
            .enter().append("svg:rect")
            .attr("x", function(d) { return x(d.x); })
            .attr("y", function(d) { return -y(d.y0) - y(d.y); })
            .attr("height", function(d) { return y(d.y); })
            .attr("width", x.rangeBand());

        </script>
    </body>
</html>

My gist: bl.ocks.org/2940908