quinta-feira, 22 de outubro de 2015

Consuming a CFapi generated API with Android

Hello folks! Today I am going to show you how to consume an API (dynamically created with CFapi) in an Android application.

I have created the API named "health" with CFapi (http://CFapi.riaforge.org) based on a database view called "hospitals". The view contains three fields: Nome, Latitude and Longitude (sorry, I left field name as "Nome", the word in portuguese for name).

After filling the CFapi main form and clicking the "Generate API" button, I was ready to deploy my instant generated API "Health" to my server on the Internet.

You can check it out in the following address:


By clicking in the above link, you will see a real-time generated REST/JSON flow coming from RAM memory that was previously filled by a database query on view "hospitals".

This JSON shows a list of some of the hospitals available in the city of Rio de Janeiro.

Now, the idea is to show here how to consume this JSON in an Android application.

In out project, the first thing to do is to define a class to receive the objects from JSON. Lets call it HospitalDTO:

package com.example.androidcfapiconsumer;

public class HospitalDTO
{

   String name = "";
   String latitude = "";
   String longitude = "";
  
   public String getName()
   {
      return name;
   }
   public void setName(String name)
   {
      this.name=name;
   }
   public String getLatitude()
   {
      return latitude;
   }
   public void setLatitude(String latitude)
   {
      this.latitude=latitude;
   }
   public String getLongitude()
   {
      return longitude;
   }
   public void setLongitude(String longitude)
   {
      this.longitude=longitude;
   }  
}
 


Then, we have to create a webService consumer. So we are going to add a new class called HospitalWS:


// REST WebService consumer
package com.example.androidcfapiconsumer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.net.Uri;
import android.util.Log;

public class HospitalWS
{

   private static String URL="";
   private static String ENDPOINT="";
   private static int timeout=10000;

   public ArrayList<HospitalDTO> getHospitals(String token)
   {
      URL="http://www.tradeteller.com.br";
      ENDPOINT="/health/api/v1/rest/hospitals.cfm";

      ArrayList<HospitalDTO> hospitals=new ArrayList<HospitalDTO>();
      String result="";

      try
      {
         HttpParams httpParameters=new BasicHttpParams();
         int timeoutConnection=timeout;
         HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
         int timeoutSocket=timeout;
         HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

         // Define inputStream
         InputStream inputStream=null;

         // Create http client.
         HttpClient httpclient=new DefaultHttpClient(httpParameters);

         // Define input parameters values.
         Uri.Builder b=Uri.parse(URL).buildUpon();
         b.path(ENDPOINT);
         b.appendQueryParameter("token", token);
         b.appendQueryParameter("pretty", "");
         b.appendQueryParameter("filter", "");
         String url=b.build().toString();

         // Create the GET and set URL.
         HttpGet httpget=new HttpGet(url);

         // Define data type.
         httpget.setHeader("Accept", "text/json");

         try
         {
            // Send the URL request, using GET.
            HttpResponse httpResponse=httpclient.execute(httpget);

            // Receive response as "inputStream".
            inputStream=httpResponse.getEntity().getContent();

            String converted=convertInputStreamToString(inputStream);

            // Check if http response code is 200 (OK).
            if (httpResponse.getStatusLine().getStatusCode() == 200)
            {
               result=converted;

               // Parse JSON.
               hospitals=parseJSONString(result);
            }
            else
            {
            }

         }
         catch (Exception g)
         {
            g.printStackTrace();
         }
      }
      catch (Exception h)
      {

      }

      return hospitals;
   }

   private ArrayList<HospitalDTO> parseJSONString(String result)
   {
      ArrayList<HospitalDTO> hospitals=new ArrayList<HospitalDTO>();

      JSONArray ja=null;

      try
      {
         ja=new JSONArray(result);
      }
      catch (JSONException e)
      {
         e.printStackTrace();
      }

      for (int i=0; i < ja.length(); i++)
      {

         JSONObject oneObject=null;

         try
         {
            oneObject=ja.getJSONObject(i);
         }
         catch (JSONException e)
         {
            e.printStackTrace();
         }

         // Pulls items from the array.
         try
         {
            String name=oneObject.getString("NOME");
            String latitude=oneObject.getString("LATITUDE");
            String longitude=oneObject.getString("LONGITUDE");

            HospitalDTO hospital=new HospitalDTO();
            hospital.setName(name);
            hospital.setLatitude(latitude);
            hospital.setLongitude(longitude);
            hospitals.add(hospital);

         }
         catch (JSONException e)
         {
            e.printStackTrace();
         }

      }

      return hospitals;

   }

   private static String convertInputStreamToString(InputStream inputStream) throws IOException
   {
      BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

      int c;
      StringBuilder response=new StringBuilder();

      while ((c=bufferedReader.read()) != -1)
      {
         response.append((char) c);
      }
      String result=response.toString();
      inputStream.close();

      return result;

   }

}

Finally,copy and paste the code below to MainActivity.java:


package com.example.androidcfapiconsumer;

import java.util.ArrayList;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.FragmentActivity;
import com.example.projetomapa.R;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;

public class MainActivity extends FragmentActivity
{

   ArrayList<HospitalDTO> hospitals=new ArrayList<HospitalDTO>();
   GoogleMap supportMap;

   @Override
   protected void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      int resultCode=GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext());

      if (resultCode == ConnectionResult.SUCCESS)
      {

         FragmentManager fmanager=getSupportFragmentManager();
         Fragment fragment=fmanager.findFragmentById(R.id.map);
         SupportMapFragment supportmapfragment=(SupportMapFragment) fragment;
         supportMap=supportmapfragment.getMap();

         supportMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);

         supportMap.setMyLocationEnabled(true);

         LatLng latlng=new LatLng(-22.910509, -43.203932);

         CameraPosition cameraPosition=new CameraPosition.Builder().target(latlng).zoom(10).build();
         supportMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

      }
      else
      {
         try
         {
            int RQS_GooglePlayServices=1;

            GooglePlayServicesUtil.getErrorDialog(resultCode, this, RQS_GooglePlayServices);
         }
         catch (Exception erro)
         {
            finish();
         }
      }

      // Start webService consumption.
      showHospitals();

   }

   private void showHospitals()
   {
      new Thread()
      {
         public void run()
         {

            try
            {
               String token="8FBC592A-9899-998C-906EFD25C55AAA97";

               HospitalWS hospitalWS=new HospitalWS();
               hospitals=hospitalWS.getHospitals(token);

               Bundle bundle=new Bundle();
               bundle.putString("message", "ok");

               Message message=new Message();
               message.setData(bundle);
               messageHandler.sendMessage(message);

            }
            catch (Exception e)
            {
               e.printStackTrace();
            }

         }
      }.start();

   }

   public Handler messageHandler=new Handler()
   {
      public void handleMessage(Message message)
      {
         String result=(String) message.getData().getString("message");

         if (result == "ok")
         {
            MarkerOptions markeroptions=new MarkerOptions();

            for (HospitalDTO item : hospitals)
            {

               // Draw markers on the map.
               markeroptions.position(new LatLng(Double.valueOf(item.latitude), Double.valueOf(item.longitude)));
               markeroptions.title(item.name);
               markeroptions.draggable(false);

               supportMap.addMarker(markeroptions);

            }

         }
      }
   };

}

Of course, AndroidManifest.xml must be altered to:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.projetomapa"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <permission
        android:name="com.example.projetomapa.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.androidcfapiconsumer.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="AIzaSyB_i5ss8XwMWOXTDVj50A8jgdecWhH29X0" />
    </application>

</manifest>

And also this small definition on strings.xml file:


<string name="app_name">Android CFapi consumer</string>



Basically what we are doing here is starting an application with a standard Google Map, then we call an asynchronous task to consume our "Health" JSON API entry-point. When its finished, the hospitals are shown on the map.

The result can be seen in the following screen capture:






The complete Android CFapi consumer example can be downloaded here.



quarta-feira, 21 de outubro de 2015

Integrating CKAN open data with CFapi

Hello everyone. In my last post I introduced CFapi (CFapi.riaforge.org), a dynamic API generator for Coldfusion, Railo and Lucee Server CFML engines, now in version 1.0.5.

Today I am going to show you how easy it is to integrate CKAN open data server with data coming from a CFapi generated API.

In this example, I will use a database table that contains the neighbourhoods of the city of Rio de Janeiro. The word for "Neighbourhood" in portuguese is "Bairro". So that is the name of our database table:

Bairro table:














It has only three fields: BAIRRO (the name of the neighbourhood), LATITUDE and LONGITUDE.

The goal here is to create an API exposing the Bairro table as CSV, and then creating a CKAN new dataset resource to feed it *in real time*.

Of course it is possible to save a CSV file and deploy it to CKAN server, but that approach would demand updates. By using an API entry point, on the other hand, the information will always be up to date, because the CSV is generated by request.

So lets begin by pointing our browser to CFapi main page (CFapi/index.cfm) and fill in the form like this:




 Then press the "Generate API" button. The following screen will show up:





Now we have to follow the CFapi instructions. First, lets click on the "download API" link and deploy our just created City API to our web server root folder, something like "c:\inetpub\wwwroot\City" if you use IIS or "c:\Program files\Apache Software Foundation\Apache2.2\htdocs\City" if you use Apache Server.

After that we will click on the link that calls our City API main page: http://localhost/city/index.cfm - That will show the City API welcome screen and also fill the memory caches:





Now lets switch back to CFapi page and click on the link that runs the CSV file generation:

http://localhost/city/api/v1/csv/neighbourhoods.cfm?token=8AD8E16D-BE6B-AEC8-958DA1D111013363&filter=&download=true&delimiter=,

Notice that the browser tries to download the generated CSV file:




That is not what we want, as CKAN will not understand a download request as data feed. So we have to change the URL parameter "download=true" to "download=false". The delimiter *MUST* be a comma (,) to work with CKAN.



http://localhost/city/api/v1/csv/neighbourhoods.cfm?token=8AD8E16D-BE6B-AEC8-958DA1D111013363&filter=&download=false&delimiter=,


The result is that now we receive the CSV information within the browser screen:




Fine! Now we have got what it takes to integrate data with CKAN Server. Now lets open a CKAN dataset edition page (you must be logged in CKAN) and click on "Edit" button:

CKAN Server dataset page


 Now lets click on "Add New Resource" button:




The "Add New Resource" page will show up. Choose "Link for an API" and fill in the form field "Resource" with our CFapi generated City API link, like below (don´t forget to specify CSV in the format field). Then click on the "Add" button.

http://yourServer/city/api/v1/csv/neighbourhoods.cfm?token=8AD8E16D-BE6B-AEC8-958DA1D111013363&filter=&download=false&delimiter=,


With this we have added a new resouce called "Neighbourhoods" to our CKAN dataset. To visualize the result, lets just click on its name:




CKAN will then show in a table display format, the contents of the Neighbourhoods dataset (whose source data is coming from our City API, created dynamically by CFapi) :



To make things more beautiful, lets click on the "Map" tab and put the value "0-1000" in the range options. The end result is all the Rio de Janeiro´s city neighbourhoods showing on a map:




This example shows the endless possibilities of CFapi instant API generation when used in conjunction with CKAN or other opendata systems. Some time ago it was a real pain to create and integrate  APIs to open data systems, now we can do it in less than ten minutes.

And... That´s it! Let me hear from you about your experience with CFapi. That is important so I keep improving its tools.

Thanks a lot.


sexta-feira, 16 de outubro de 2015

CFapi - Creating REST / JSON / SOAP / WSDL / CSV / KML / GeoJSON APIs dynamically

Although this blog is about Android development, I kindly ask my reader´s permission to talk here about an off-topic subject.

Today I´m going to show you a little bit about the backstage part of mobile application development. By "backstage", I mean the back-end part of the software, i.e.: The API.

Not only I am an Android application developer, I am also a Coldfusion developer. And I usually do my back-end software in Coldfusion language.

Having done dozen of Coldfusion APIs that feeds Open Data CKAN sites, and various Android APPs, I some day had this idea of creating an API Dynamic Generator.

It would work like this: I would take a basic API project and turn it to a template with replaceable terms in the code. Then I would ask the developer some details about his database and which database view/table he/she was going to expose as open data or to feed a mobile APP. Then the dynamic API generator would take these information and create by its own a brand new API, making the data available in REST/JSON, SOAP/WSDL,  CSV, KML and GeoJSON formats to be integrated with any software.

Well... That idea became the project CFapi (Coldfusion Dynamic API Generator). It is available now at http://CFapi.riaforge.org and it is open source. You can download, use, share, modify, at your will. Enjoy!

Let me show you a step by step guide to use CFapi to create a new API:

1) Download CFapi in http://CFapi.riaforge.org
2) Extract CFapi.rar file contents to your Coldfusion developer server.
3) Open your web browser and point it to http://localhost/cfapi/index.cfm
    You will see this screen:


 At first run, CFapi comes prepared to use Coldfusion´s default cfbookclub datasource as the main source of data to create an API. Also, I choose to expose, as an example, two of its tables: authors and books. I decided that the generated API (bookclub) user would be allowed to search for authors using a "lastname" filter and for books using "title". And that´s all that is needed!

4) Press the "Generate API" button. Wait some seconds. If everything went ok,
    the following screen will show up:




This means that CFapi created successfully the Bookclub API with all features, i.e.: Rest, Soap and CSV entry-points.

5) Now you have to download the created API and deploy it to your development server. This is done by clicking in the first item "Click here to download your API Project zip file".  Then you extract that file´s content to your Coldfusion web root folder.

6) Now you have to call the main page of your new created API: http://localhost/bookclub/index.cfm. This will fill the memory caches for the first time. If you skip this step, when you consume the entry- points, you will get "no results found" message. The following screen will show up:




This is the welcome screen of your recently created API, showing the available entry-points to the consumer and the needed arguments. An access token is always needed, as access control. Of course you have to use https on your production server to hide it. there is also a "pretty" parameter for REST/JSON entry-points, which means that if set to true, the browser will show the JSON in a human readable format. Otherwise it will put everything in a single row, for optimisation purposes. The default value for "pretty" is false.

Another important parameter is "filter". This is where you should specify a search value so that the API will only return that value, if found. Notice that filter will use the search fields you have specified in the main form of CFapi application. In our case, "lastname" for authors and "tittle" for books.

In the SOAP entry-point you get a default WSDL file to be processed by your SOAP SOA system. I tested it with SOAPUI software and it worked flawlessly. As you can see in the pictures below (REST and SOAP output):

Rest/JSON output


WSDL output file









SOAPUI consuming WSDL


And also, a picture of standard JAVA program using httpGet() and a JSON extraction library to consume the REST/JSON entry-point. This program is available in the "examples" folder of CFapi project:




7) Last, but not least: The memory caches empty from time to time. It is mandatory thay you create a Scheduled Task in Coldfusion Administrator to call, from time to time (you decide how frequent) to call the "updateCache.cfm" file, located in "CFapi/ScheduledTasks" folder. This file is tailor made for your API. You just have to create the Scheduled Task in CF Admin to call it.


Observations:

* CFapi uses Google GSON library to generate JSON. Also, it uses Mark Mandel´s Javaloader project.

* You have to use https on your production server to hide the access token.

* For standard RESTfull URL compliance, you will have to use Web Server URL rewriting, to take the "*.cfm" file extension out of the URL.


Conclusion

I think that CFapi can give Coldfusion an important role on the API market. It represents a viable and fast (instant) solution in creating mobile back-end APIs and also for Open Data projects, like CKAN or Socrata based web sites.

Hope to hear feedback from developers and users as soon as possible.  Of course there is much room to evolve CFapi, and developers opinion is crucial on that subject.

So, please, leave a comment, send an email, contact me on Twitter or Facebook (luizMilfont).

Hope to have helped people with my CFapi project contribution.

Enjoy!