Simple/Basic Java Rest Web Services Client (with source)

Post date: Jan 22, 2014 2:36:51 AM

This blog entry presents the Java code for a basic Rest Web services client I wrote in Java. It also shows how to use it to wrap around ISY REST services.It is kept as small and lightweight as possible but includes all the code to get a working authenticated service connection with a restful web services server. Think of it as the "hello world"/getting started How -to Java code to be re-used and extended to wrap around the full processing of any published REST web services.

Background

I am writing an Android app to interact with many of my home Audio/Video devices and need to talk to my Universal-Devices ISY-994 home lighting controller using its REST Web Services. I could (and have) interact with the ISY-994 directly with its full and robust Java SDK but since I am including this in an Android app, I want to keep it as light as possible - without the inclusion of the UDI SDK Java libraries. Calling the REST interface is perfect for this.

I started with a proof-of-concept, to ensure I could communicate and consume Rest services, using the basic classes in the standard JDK.

    1. I wrote a generic class to connect and consume Rest services from any specified Rest Services server.
      • RestRequester.java
    2. I then extended it to focus specifically on my ISY-994, and its set of services (see UDI Wiki for the list of available services).
      • ISYRestRequester.java

The source code for both the base generic class and the ISY994 extended class are included below.

Scope of this Rest Client

  • use as few external libraries as possible - keep it to the standard Java Library (unfortunately Authentication requires the use of Base64 encoding so I used an Apache library for this)
  • provide a extendable class with the basic code to connect, send service requests, and receive responses
  • has a basic set of exposed methods to setup and use published Rest Web Services
  • handles Authentication to the service, if it is needed
  • has a main method to allow testing or calling services from the commandline
  • configurable server url
  • handles all the connections to the web services URL
  • POST requests or GET requests
  • Minimal or no processing of the responses other than returning the results as a StringBuilder (and dumping to System.out when run from the commandLine)

Extending this class to wrap around a specific Server and REST services

The generic class works out of the box on any configured Server URL, however it is easier to create a new class that extends the above base class with specific Rest service parameters, such as

  • Rest Service Base URL
  • Authentication username and password
  • response type - plain XML or JSON
  • response processing - pull out the useful information from the XML responses and transcode it into whatever
  • wrap individual service calls into callable Java methods that "do something" with the responses

Java Source Code

The source is released as free and open under the GNU license version 3.

    • It is available in-line below
    • The latest version is available at my Subversion repo at http://svn.webarts.bc.ca
    • as a zip file at the very bottom of this page
    • ca.bc.webarts.tools.RestRequester
    • ca.bc.webarts.tools.isy.ISYRestRequester

My code depends (NOT Derivative) on a few Java files from the Apache Commons Codec library to perform Base64 encoding required by the http basic authentication. These files are licensed and re-distributed unmodified, as links below, under the Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0 .

If you plan on using this (or other REST client) within an Android app, you can use the Android Base64 class in the Android SDK that will handle the Encoding instead of the ApacheCommons Codec library files. I commented out the import android.util.Base64 in the source. I tried it it works fine as well.

Base Java Class

ca.bc.webarts.tools.RestRequester

/*
 *
 *  Written by Tom Gutwin - WebARTS Design.
 *  Copyright (C) 2014 WebARTS Design, North Vancouver Canada
 *  http://www.webarts.ca
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without_ even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package ca.bc.webarts.tools;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.File;
import java.lang.StringBuilder;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
//import android.util.Base64;
import org.apache.commons.codec.binary.Base64;
//import ca.bc.webarts.widgets.Util;
/** A class to encapsulate the calls to Restful Web Services. It is kept very basic with low overhead to live in android apps.
 **/
public class RestRequester
{
  protected static String CLASSNAME = "ca.bc.webarts.tools.RestRequester"; //ca.bc.webarts.widgets.Util.getCurrentClassName();
  public static final String LOG_TAG = CLASSNAME;
  public static boolean debugOut_ = false;
  /**  A holder for this clients System File Separator.  */
  public final static String SYSTEM_FILE_SEPERATOR = File.separator;
  /**  A holder for this clients System line termination separator.  */
  public final static String SYSTEM_LINE_SEPERATOR =
                                           System.getProperty("line.separator");
  protected static String baseUrl_ = ""; // http://isy994
  public static boolean authenticating_ = true;
  protected static String username_ = "";
  protected static String password_ = "";
  protected static boolean acceptJSON_ = false;
  public void setUsername(String uName){username_=uName;}
  public void setPassword(String uPasswd){password_=uPasswd;}
  public void setBaseUrl(String url){baseUrl_=url;}
  public void setAcceptJSON(boolean acceptJson){acceptJSON_=acceptJson;}
  public String getUsername(){return username_;}
  public String getPassword(){return password_;}
  public String getBaseUrl(){return baseUrl_;}
  public boolean getAcceptJSON(){return acceptJSON_;}
  public RestRequester()
  {
  }
  public RestRequester(String baseUrl)
  {
    setBaseUrl( baseUrl);
    authenticating_=false;
  }
  public RestRequester(String baseUrl,String uName,String uPasswd)
  {
    setBaseUrl( baseUrl);
    authenticating_=true;
    setUsername( uName);
    setPassword( uPasswd);
  }
  public boolean isInit()
  {
    boolean retVal = true;
    if( baseUrl_.equals("") ||
        (authenticating_ &&
            (username_.equals("") || password_.equals(""))
        )
      )
      retVal=false;
    return retVal;
  }
  /** Sends the rest service GET request off and retruns the results.
    * @param serviceName is the service (string) to append to the baseURL - example /rest/sys
    * @return the serviceResult as a stringBuilder, null if error
    **/
  public StringBuilder serviceGet(String  serviceName)
  { return callService(serviceName, true);}
  /** Sends the rest service POST request off and retruns the results.
    * @param serviceName is the service (string) to append to the baseURL - example /rest/sys
    * @return the serviceResult as a stringBuilder, null if error
    **/
  public StringBuilder servicePost(String  serviceName)
  { return callService(serviceName, false);}
  /** Sends the rest service request off and retruns the results.
    * @param serviceName is the service (string) to append to the baseURL - example /rest/sys
    * @param getNotPost is a flag to tell this method to do a get or post based on this flag - true does a GET, false does a POST
    * @return the serviceResult as a stringBuilder, null if error
   **/
  public StringBuilder callService(String  serviceName, boolean getNotPost)
  {
    StringBuilder retVal = null;
    if(isInit())
      try
      {
        String usrlStr = (baseUrl_+serviceName).replace(" " ,"%20");
        URL url = new URL(usrlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        if(getNotPost)
          conn.setRequestMethod("GET");
        else
          conn.setRequestMethod("POST");
        if(acceptJSON_)
          conn.setRequestProperty("Accept", "application/json");
        else
          conn.setRequestProperty("Accept", "application/xml");
        //BASE64Encoder enc = new sun.misc.BASE64Encoder();
        String userpassword = username_ + ":" + password_;
        //String encodedAuthorization = android.util.Base64.encodeToString( userpassword.getBytes(), android.util.Base64.DEFAULT );
        String encodedAuthorization = new String(Base64.encodeBase64( (userpassword.getBytes()) ));
        conn.setRequestProperty("Authorization", "Basic "+ encodedAuthorization);
        if (conn.getResponseCode() == 200)
        {
          BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
          if (br!=null)
          {
            retVal = new StringBuilder();
            String output;
            if (debugOut_) System.out.println("Output from Server .... \n");
            while ((output = br.readLine()) != null)
            {
              if (debugOut_) System.out.println(output);
              retVal.append(output);
              retVal.append("\n");
            }
          }
        } // valid http response code
        conn.disconnect();
      }
      catch (MalformedURLException e)
      {
       e.printStackTrace();
      }
      catch (IOException e)
      {
         e.printStackTrace();
      }
    return retVal;
  }
  public StringBuilder responseIndenter(StringBuilder sb)
  {
    StringBuilder retVal = new StringBuilder("");
    int indent = -1;
    boolean opening = false;
    boolean closing = false;
    boolean lf = false;
    char [] sbChar = sb.toString().toCharArray();
    for (int i=0; i< sbChar.length;i++)
    {
      opening = false;
      closing = false;
      lf = false;
      if(sbChar[i]=='<')
      {
        indent++;
        opening = true;
        retVal.append("\n");
        for (int j=0;j<indent;j++) retVal.append("  ");
        retVal.append("<");
      }
      else if (sbChar[i]=='/'&&sbChar[i-1]=='<')
      {
        indent--;indent--;
        closing = true;
        retVal.append("/");
      }
      else if (sbChar[i]=='>')
      {
        lf = true;
        retVal.append(">\n");
        for (int j=0;j<indent;j++) retVal.append("  ");
      }
      else
      {
        retVal.append(sbChar[i]);
      }
    }
    return retVal;
  }
}

ISY-994 Extension Java Class

ca.bc.webarts.tools.isy

/*
 *
 *  Written by Tom Gutwin - WebARTS Design.
 *  Copyright (C) 2014 WebARTS Design, North Vancouver Canada
 *  http://www.webarts.ca
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without_ even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package ca.bc.webarts.tools.isy;
import ca.bc.webarts.tools.RestRequester;
public class ISYRestRequester extends RestRequester
{
  protected static String CLASSNAME = "ca.bc.webarts.tools.ISYRestRequester"; //ca.bc.webarts.widgets.Util.getCurrentClassName();
  private static StringBuffer helpMsg_ = new StringBuffer(SYSTEM_LINE_SEPERATOR);
  public ISYRestRequester()
  {
    setBaseUrl( "http://isy994/rest");
    authenticating_=true;
    setUsername( "admin");
    setPassword( "*******");
  }
  /**
   * Class main commandLine entry method.
   **/
  public static void main(String [] args)
  {
    final String methodName = CLASSNAME + ": main()";
    ISYRestRequester instance = new ISYRestRequester();
    /* Simple way af parsing the args */
    if (args ==null || args.length<1)
      System.out.println(getHelpMsgStr());
    else
    {
      if (args[0].equals("test"))
      {
        System.out.println("Testing ISY Rest Service: "+ "/sys");
        StringBuilder resp =  instance.serviceGet("/sys");
        System.out.println(resp.toString());
        System.out.println();
      }
      else
      {
        // Parse the command
        String allcommands = args[0];
        for (int i=1;i< args.length;i++) allcommands+=" "+args[i];
        System.out.print("Sending ISY Rest Service: "+allcommands);
        String passedCommand = (allcommands.startsWith("/rest/") ? allcommands.substring(5) : allcommands);
        System.out.println(" ("+passedCommand+")");
        StringBuilder resp =  instance.serviceGet(passedCommand);
        if (resp!=null)
        {
          System.out.println(instance.responseIndenter(resp).toString());
          System.out.println();
        }
        else
        {
          System.out.println("Response Error");
          System.out.println();
        }
      }
    }
  } // main
    /** gets the help as a String.
   * @return the helpMsg in String form
   **/
  private static String getHelpMsgStr() {return getHelpMsg().toString();}
  /** initializes and gets the helpMsg_
  class var.
   * @return the class var helpMsg_
   **/
  private static StringBuffer getHelpMsg()
  {
    helpMsg_ = new StringBuffer(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("---  WebARTS ISYRestRequester Class  -----------------------------------------------------");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("---  $Revision:$ $Date:$ ---");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("-------------------------------------------------------------------------------");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("WebARTS ca.bc.webarts.android.ISYRestRequester Class");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("SYNTAX:");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("   java ");
    helpMsg_.append(CLASSNAME);
    helpMsg_.append(" test or restCommand");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("Available Commands:");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("see: http://wiki.universal-devices.com/index.php?title=ISY_Developers:API:REST_Interface");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    helpMsg_.append("---------------------------------------------------------");
    helpMsg_.append("----------------------");
    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
    return helpMsg_;
  }
}

Have fun. I hope it helps someone else get started.

tom

Rest Services Icon