How to build a Java binding for a web API

Cometdocs logo

Introduction

This article is the Java part of a series whose main article is How to build a language binding for a web API which you should read before this one to get general background information about implementing a language binding.

If you are interested by the .Net implementation you can refer to the dedicated article: How to build a .Net binding in C# for a web API.

This article will give you more concrete information, mainly source code, if you need to implement a web API binding in Java.
When applicable each section of this article references a section of the main article to give you more context before diving into the Java implementation details.

The different sections are not directly related so you can read them in a random order, and particularly if you’re an experienced .Net developer some sections, e.g. about object-oriented programming, may bore you, so feel free to skip them completely.

The open source code of the project is available on GitHub at CometDocs.

Design

For an overview of the design of the Cometdocs binding see the “Design” section of the main article.

The client class

For an overview of the design of the client class see the “The client class” section of the main article.

First here is the Java interface that reifies the functional interface of a Cometdocs client:

public interface Client
{
	public AuthenticationToken authenticate(String username, String password, String key) throws Exception;
	public AuthenticationToken authenticate(String username, String password, String key, Integer validity) throws Exception;
	public FileInfo convertFile(AuthenticationToken token, FileInfo file, ConversionType conversion) throws Exception;
	public FileInfo convertFile(AuthenticationToken token, FileInfo file, ConversionType conversionType, Integer timeout) throws Exception;
	public void createAccount(String name, String email, String password) throws Exception;
	public FolderInfo createFolder(AuthenticationToken token, FolderInfo parent, String name) throws Exception;
	public void deleteFile(AuthenticationToken token, FileInfo file) throws Exception;
	public void deleteFile(AuthenticationToken token, FileInfo file, Boolean deleteRevisions) throws Exception;
	public void deleteFolder(AuthenticationToken token, FolderInfo folder) throws Exception;
	public File downloadFile(AuthenticationToken token, FileInfo file) throws Exception;
	public Notification[] getNotifications(AuthenticationToken token) throws Exception;
	public void invalidateToken(AuthenticationToken token) throws Exception;
	public Category[] getCategories() throws Exception;
	public Conversion[] getConversions(AuthenticationToken token) throws Exception;
	public Conversion[] getConversions(AuthenticationToken token, FileInfo file) throws Exception;
	public ConversionStatus getConversionStatus(AuthenticationToken token, FileInfo file, ConversionType conversion) throws Exception;
	public ConversionType[] getConversionTypes() throws Exception;
	public Folder getFolder(AuthenticationToken token) throws Exception;
	public Folder getFolder(AuthenticationToken token, FolderInfo folder) throws Exception;
	public Folder getFolder(AuthenticationToken token, Boolean recursive) throws Exception;
	public Folder getFolder(AuthenticationToken token, FolderInfo folder, Boolean recursive) throws Exception;
	public String[] getMethods() throws Exception;
	public FileInfo[] getPublicFiles(AuthenticationToken token) throws Exception;
	public FileInfo[] getPublicFiles(AuthenticationToken token, Category category) throws Exception;
	public FileInfo[] getSharedFiles(AuthenticationToken token) throws Exception;
	public void refreshToken(AuthenticationToken token) throws Exception;
	public void refreshToken(AuthenticationToken token, Integer validity) throws Exception;
	public void sendFile(AuthenticationToken token, FileInfo file, String[] recipients) throws Exception;
	public void sendFile(AuthenticationToken token, FileInfo file, String[] recipients, String message) throws Exception;
	public void sendFile(AuthenticationToken token, FileInfo file, String[] recipients, String sender, String message) throws Exception;
	public FileInfo uploadFile(AuthenticationToken token, InputStream file, String name) throws Exception;
	public FileInfo uploadFile(AuthenticationToken token, InputStream file, String name, Long folderId) throws Exception;
	public FileInfo uploadFile(AuthenticationToken token, String file) throws Exception;
	public FileInfo uploadFile(AuthenticationToken token, String file, Long folderId) throws Exception;
	public FileInfo uploadFile(AuthenticationToken token, File file) throws Exception;
	public FileInfo uploadFile(AuthenticationToken token, File file, Long folderId) throws Exception;
	public FileInfo uploadFile(AuthenticationToken token, File file, FolderInfo folder) throws Exception;
	public FileInfo uploadFileFromUrl(AuthenticationToken token, String url) throws Exception;
	public FileInfo uploadFileFromUrl(AuthenticationToken token, String url, String name) throws Exception;
	public FileInfo uploadFileFromUrl(AuthenticationToken token, String url, Long folderId) throws Exception;
	public FileInfo uploadFileFromUrl(AuthenticationToken token, String url, FolderInfo folder) throws Exception;
	public FileInfo uploadFileFromUrl(AuthenticationToken token, String url, String name, Long folderId) throws Exception;
}

Quite a bunch of methods to implement!
Most of the overloads are needed to map the Cometdocs API optional parameters; as an example when you authenticate you can provide a validity period for the generated session token.

The client factory is as simple as it can be:

public class ClientFactory
{
    public static Client getClient()
    {
        return new ClientImpl();
    }
}

The authentication token class

Here is the wrapper used to represent the authentication token:

public class AuthenticationToken
{
    private String value;
    public String getValue() { return value; }
	public void setValue(String value) { this.value = value; }

	public AuthenticationToken(String value)
    {
        this.value = value;
    }
}

This is a minimal illustration of object-oriented abstraction.

The files and folders types

And finally here are the different classes that carry the basic info about a file, “FileInfo“:

public class FileInfo
{
    private long id;
    public long getId() { return id; }
    public void setId(long id) { this.id = id; }
	
    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
	
    private String extension;
    public String getExtension() { return extension; }
    public void setExtension(String extension) { this.extension = extension; }
	
    private long size;
    public long getSize() { return size; }
    public void setSize(long size) { this.size = size; }
	
    private Boolean hasConversions;
    public Boolean hasConversions() { return hasConversions; }
    public void hasConversions(Boolean hasConversions) { this.hasConversions = hasConversions; }

    public FileInfo()
    {
        this(null);
    }
	
    public FileInfo(String nameWithExtension)
    {
        if (nameWithExtension != null)
        {
            String[] tokens = nameWithExtension.split("\\.");

            String name;
            
            if (tokens.length >= 2)
            {
                name = tokens[0];
                for (int i = 1; i < tokens.length - 1; ++i)
                {
                    name += "." + tokens[i];
                }

                setExtension(tokens[tokens.length - 1]);
            }
            else
            {
                name = tokens[0];
            }
            setName(name);
        }
    }
	
    @Override
    public int hashCode()
    {
        return (int)id;
    }

    @Override
    public boolean equals(Object obj)
    {
    	if (!(obj instanceof FileInfo)) return false;
    	
        FileInfo other = (FileInfo)obj;

        return other != null && other.id == this.id;
    }	
}

and about a folder, “FolderInfo“:

public class FolderInfo
{
    private long id;
    public long getID() { return id; }
    public void setID(long id) { this.id = id; }
	
    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

For “FileInfo” I’ve implemented “hashCode” and “equals” to be able to compare two instances based on their internal Cometdocs ID.

File” is the class that represents a file with some content:

public class File extends FileInfo
{
    private byte[] content;
    public byte[] getContent() { return content; }
    public void setContent(byte[] content)
    {
        if (content == null) throw new NullPointerException("Content cannot be null; for empty files provide an empty array or do not provide anything!");
		
        this.content = content;
    }
	
    public File()
    {
        super();

        content = new byte[0];
    }
	
    public File(String nameWithExtension)
    {
        super(nameWithExtension);

        content = new byte[0];
    }
}

And “Folder” a folder with some files and sub-folders:

public class Folder extends FolderInfo
{
    private Folder[] folders;
    public Folder[] getFolders() { return folders; }
    public void setFolders(Folder[] folders) { this.folders = folders; }

    private FileInfo[] files;
    public FileInfo[] getFiles() { return files; }
    public void setFiles(FileInfo[] files) { this.files = files; }
}

The web/HTTP client

For general information about what is an HTTP client and how it fits into the building of a web API binding have a look at the dedicated section in the main article.

The Apache HttpClient

There is a bad and a good news…
The bad news is Java does not come with a native HTTP client, so you have to use a third-party component.
The good news is there exist at least two good implementations: the Apache HttpClient, a part of the HttpComponents project and the Google HTTP Client Library for Java
I’ve chosen the first one because the Apache foundation projects are high-quality (from my own experience), sometimes serve as the anteroom for the standard Java implementation and I’ve already use it a little.
The Apache HttpClient is relatively well documented but you have to take care of referring to the correct version of the documentation as there is more than one version of this component.
The abstraction it uses is a set of requests and responses that both carry some “entity”, i.e. the payload: for the Cometdocs API we exclusively use HTTP/POST request whose entity is more often than not a set of parameters, and you get back a response whose entity is a bunch of JSON.

A typical example is the authentication, whose method source code is:

/**
 * Authenticate to the Cometdocs API: https://www.cometdocs.com/developer/apiDocumentation#method-authenticate
 * 
 * @param username The Cometdocs website username (typically an email address).
 * @param password The Cometdocs website password.
 * @param key The Cometdocs API key (you can create one here: https://www.cometdocs.com/developer/myApps).
 * @param validity The desired validity of the emitted token.
 * @return An authentication token to use with subsequent API calls.
 * @throws Exception
 */
public AuthenticationToken authenticate(String username, String password, String key, Integer validity) throws Exception
{
	// Build the parameters bag
	List<NameValuePair> params = new ArrayList<NameValuePair>(4);
    params.add(new BasicNameValuePair("username", username));
    params.add(new BasicNameValuePair("password", password));
    params.add(new BasicNameValuePair("clientKey", key));
	if (validity != null)
	{
		params.add(new BasicNameValuePair("validity", validity.toString()));
	}
	
	// Build the HTTP/POST request
	HttpPost post = new HttpPost(APIRoot + "authenticate");
	post.setEntity(new UrlEncodedFormEntity(params));
	
	// Send the request and retrieve the response
    HttpResponse httpResponse = httpClient.execute(post);

    // Interpret the response as text
    String json = EntityUtils.toString(httpResponse.getEntity());
    
    // Deserialize the response object from JSON 
    AuthenticateResponse response = gson.fromJson(json, AuthenticateResponse.class);

    // Check that there is no error
    checkAndThrow(response);

    // Build and return a new authentication token for the session
    return new AuthenticationToken(response.getToken());
}

File upload

The upload of files is special because you need to send a composite request: the first parts are made of the parameters, as for the other requests, but the last part is made of the binary content of the file.
The good news is that on the Java side the HttpClient does most of the work and all you have to do is give it the sub-payloads and it will take care of building the multipart HTTP request.

Here is the Java source code for the “uploadFile” method:

/**
 * Upload a file from the local file-system to the Cometdocs store: https://www.cometdocs.com/developer/apiDocumentation#method-uploadFile
 * 
 * @param token A valid authentication token.
 * @param file A file stream.
 * @param name The name to give to the uploaded file.
 * @param folderId The folder in which the file will be uploaded or the root folder if unspecified.
 * @return A FileInfo instance representing the uploaded file.
 * @throws Exception
 */
public FileInfo uploadFile(AuthenticationToken token, InputStream file, String name, Long folderId) throws Exception
{
	// Read the file content
	byte[] fileContent = getBytes(file);

	// Build the POST request
	HttpPost post = new HttpPost(APIRoot + "uploadFile");
	MultipartEntity entity = new MultipartEntity();
	entity.addPart("token", new StringBody(token.getValue()));
	if (folderId != null)
	{
		entity.addPart("folderId", new StringBody(folderId.toString()));	
	}
	entity.addPart("file", new ByteArrayBody(fileContent, name));
	post.setEntity(entity);
	
	// Send the request and retrieve the response as a JSON string
	HttpResponse httpResponse = httpClient.execute(post);
	String json = EntityUtils.toString(httpResponse.getEntity());

	// Deserialize the response from JSON
	UploadFileResponse response = gson.fromJson(json, UploadFileResponse.class);

	// Check the response
	checkAndThrow(response);

	// Get and returns the object representing the uploaded file
	return response.getFile();
}

Thanks to the abstraction provided by the HttpClient the code is really similar to the simpler form POST case.

JSON data binding

For a quick introduction to data binding and how it is applied to the Cometdocs binding see the “Data binding” section of the main article.

Gson

In the java world there is more than one good JSON libraries like Jackson or Gson.
I’ve chosen Gson for the same reason I’ve chosen the HttpClient: a strong trust in Google stuff.
And I was not disappointed: it’s almost as good as my preferred JSON library, Json.NET, it just requires a little more work, like to convert numeric booleans expressed with 0 and 1.

Here is an illustration of its typical usage in our Cometdocs binding: when we authenticate to the Cometdocs API we get back a JSON response similar to this:

{"token":"b687500d-3cff-44b7-ad14-a241a7edabf1","status":0,"message":"OK"}

Gson maps this JSON representation to a Java object:

Deserialized AuthenticateResponse

Deserialized AuthenticateResponse


whose class is “AuthenticateResponse“:

class Response
{
    private Status status;
    public Status getStatus() { return status; }
    public void setStatus(Status status) { this.status = status; }
	
    private String message;
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
	
    private String error;
    public String getError() { return error; }
    public void setError(String error) { this.error = error; }
}

class AuthenticateResponse extends Response
{
    private String token;
    public String getToken() { return token; }
}

This is all good for most of the mapping, but sometimes you have to help Gson be it to map numerical booleans, enums values or files conversions.

For a complete introduction to Gson you can read Java/JSON mapping with Gson.

Mapping numerical booleans

The Cometdocs API uses integers, 0 and 1, to represent boolean values, but Gson is not natively able to map this representation to a Java boolean type.
As we only convert JSON to Java we only need to write a JsonDeserializer:

/**
 * Deserialize numeric booleans, 0 and 1, because Gson does not handle them natively. 
 */
class BooleanTypeAdapter implements JsonDeserializer<Boolean>
{
	  public Boolean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
	  {
		  int code = json.getAsInt();
		  return code == 0 ? false :
			  	 code == 1 ? true :
			     null;
	  }
}

We make Gson aware of this by registering the deserializer:

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Boolean.class, new BooleanTypeAdapter());
gson = builder.create();

From now on each time Gson encounters a field in the JSON document mapped to a Boolean, it will call our deserializer for doing the job.

Mapping statuses

The Cometdocs API responses carry a status code that indicates if the request is successful (code 0) or has failed (code > 0).
As an example if you send invalid credentials the response will have status 6, “BadCredentials“.
From the Java side the list of statuses is represented using an enum named “Status“:

enum Status
{
    OK,
    InternalError,
    UnsupportedAPIMethod,
    MethodInvocationError,
    HttpsRequired,
    InvalidToken,
    BadParameters
    ...
}

So to map between the numerical status codes and the Java enum we once again have to define a custom deserializer:

class StatusTypeAdapter implements JsonDeserializer<Status>
{
	  public Status deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
	  {
		  int code = json.getAsInt();
		  Status[] values = Status.values();
		  return code < values.length ? values[ code] : null;
	  }
}

Mapping conversion types

The Cometdocs API represents the files conversions in a compacted text format: e.g. to represent a conversion from PDF to XLS instead of using a composite object like:

{from: "PDF", to: "XLS"}

it uses a simple string:

"PDF2XLS"

Here is the “ConversionTypeAdapter” class, responsible for filling the gap between the Cometdocs text representation and the Java “ConversionType” class created for the binding:

class ConversionTypeAdapter implements JsonDeserializer<ConversionType>
{
	  public ConversionType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
	  {
		  String type = json.getAsString();
		  
		  String[] tokens = type.split("2");
		  
		  return new ConversionType(tokens[0], tokens[1]);
	  }
}

Security

For an overview of the security issues associated with the implementation of a web API binding have a look at the “Security” section in the main article.

The communication with the Cometdocs API is protected using HTTPS.
It means we have to register the Cometdocs security certificate in some way to allow the Apache HttpClient to interact with it.
In Java you have to do some work, even on Windows where the certification authority of the certificate should be trusted, because Java, more precisely the JRE, uses its own certificates stores, so you have to retrieve and provide the JRE a copy of the Cometdocs certificate.

Otherwise you’ll end up with an exception like:

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
	at sun.security.ssl.SSLSessionImpl.getPeerCertificates(Unknown Source)
	...

See the main article “Security” section for the general procedure to export the certificate, from here I assume you have saved it, e.g. as a “CER” file, somewhere on your local file-system.

Here is the process to make your JRE aware of your trust:

  • first you should double check the version of the JRE running your code to avoid working on a wrong store (yes I did it once :(), e.g. by looking at the environment variable JAVA_HOME.
  • once you know which JRE is used locate its store, it should be in:
    lib/security/cacerts
    

    this path is relative to the location of your JRE.

  • the default password for your store if you’ve never changed it is “changeit”
  • add the Cometdocs certificates you downloaded to the store with the “importcert” command:
    keytool -keystore "C:\opt\Java\jdk1.7.0_09\jre\lib\security\cacerts" -importcert -alias cometdocs -file "C:\Users\pragmateek\Desktop\cometdocs.cer"
    
  • to check that the certificate has correctly been added to the store you can use the “list” command:
    keytool -list -keystore "C:\opt\Java\jdk1.7.0_09\jre\lib\security\cacerts"
    

    this will print a bunch of text so you should filter it with a tool like “grep” to ease the check, here is an example using Cygwin:

    $ keytool -list -keystore "C:\opt\Java\jdk1.7.0_09\jre\lib\security\cacerts" | grep -i cometdocs
    Enter keystore password:  changeit
    cometdocs, 1 juin 2013, trustedCertEntry,
    

The keytool.exe tool is bundled with the JRE in the “bin” folder. If your Java setup is correct, your “PATH” environment variable is correctly set, then you should be able to invoke keytool.exe directly as in the above command lines. If this is not the case you’ll have to use its absolute path, e.g. “C:\opt\Java\jdk1.7.0_09\jre\bin\keytool.exe“.

Errors management

For general information on the way errors are managed see “Errors management” in the main article.

Here is the Java class that is the representation of a Cometdocs API error:

/**
 * Represents an Exception raised by the Cometdocs API.
 */
public class CometDocsException extends Exception
{
	/**
	 * The corresponding Response's message.
	 */
	private String message;
	public String getMessage(){ return this.message; }
	public void setStatus(String message){ this.message = message; }
	
	/**
	 * The corresponding Response's status.
	 */
	private Status status;
	public Status getStatus(){ return this.status; }
	public void setStatus(Status status){ this.status = status; }
	
	/**
	 * The corresponding Response's error message.
	 */
	private String error;
	public String getError(){ return this.error; }
	public void setError(String error){ this.error = error; }
	
	/**
	 * Create a new instance of CometDocsException using the error data sent by the CometDocs web API.
	 * @param message
	 * @param status
	 * @param error
	 */
	public CometDocsException(String message, Status status, String error)
	{
		super(String.format("%s (%s) [%s]", message, error, status));
		
		this.message = message;
		this.status = status;
		this.error = error;
	}
}

And here is a specialization (currently the sole) for the “invalid token” error:

public class InvalidTokenException extends CometDocsException
{
    public InvalidTokenException(String message, Status status, String error)
    {
        super(message, status, error);
    }
}

And finally the “checkAndThrow” method that is called right after receiving a response from the Cometdocs API:

private void checkAndThrow(Response response) throws Exception
{
    if (response.getStatus() != Status.OK)
    {
    	if (response.getStatus() == Status.InvalidToken)
    	{
    		throw new InvalidTokenException(response.getMessage(), response.getStatus(), response.getError());
    	}
    	
        throw new CometDocsException(response.getMessage(), response.getStatus(), response.getError());
    }
}

Testing

For more general information about the testing of the bindings have a look at the “Testing” section in the main article.

Tools

For testing I’ve used the vanilla solution: JUnit, and benefited from its perfect integration in the Eclipse IDE.

For those not familiar with JUnit, here is how you can create a set of unit-tests:

  • create a class that will hold all the unit-tests, in our case “ClientTests
  • create a method for each unit-test and annotate it with the “Test” annotation

As an example here is the unit-test for checking that we can retrieve all the Cometdocs categories:

public class ClientTests
{
    ...

    @Test
    public void canGetCategories() throws Exception
    {
        Category[] categories = client.getCategories();

        assertTrue(categories.length > 10);
        
        boolean hasArt = false;
        boolean hasBusiness = false;
        boolean hasUnicorns = false;
        for (Category cat : categories)
        {
        	hasArt |= cat.getName().equals("Art");
        	hasBusiness |= cat.getName().equals("Business");
        	hasUnicorns |= cat.getName().equals("Unicorns");
        }
        
        assertTrue(hasArt);
        assertTrue(hasBusiness);
        assertFalse(hasUnicorns);
    }

    ...
}

And here is what we get when all the tests are OK:

Unit tests OK

Unit tests OK


(No Photoshopping I promise ;))

If you’re using NetBeans rather than Eclipse you can of course use JUnit too.

Test fixture setup

In JUnit you define a test fixture setup by marking a static method with the BeforeClass annotation.

Here is the Java source code of the fixture setup:

@BeforeClass
public static void testFixtureSetUp() throws Exception
{
    readCredentials();

    client = ClientFactory.getClient();

    canAuthenticate();

    Folder root = client.getFolder(authToken);

    for (FolderInfo f : root.getFolders())
    {
        if (f.getName().equals(testFolderName))
        {
            testFolderInfo = f;
            break;
        }
    }

    if (testFolderInfo == null)
    {
        throw new Exception(String.format("Unable to find tests folder '%s'!\nPlease create it first.", testFolderName));
    }
}

Reflection

In Java the entry point to the reflection API is the Class class, which represents a class.
It exposes all the methods necessary to explore the properties of a class like its methods using the getDeclaredMethods method which returns an array of Method, a class whose instances represent a single method; the Method class itself has a getName method which returns the name of the method it represents.

There is one subtlety: we don’t want to get all the methods of the Client type because we’re only interested in the public instance methods, not the implementation details which are private like the checkAndThrow method.
So we filter them based on their “modifiers” that are flags that indicate the characteristics of each method: instance or static, abstract or concrete, final or overridable, the visibility (public, package, protected, private).
We get the modifiers using dedicated methods like Modifier.isStatic and Modifier.isPublic.
Moreover we only need the methods defined by the Client class itself, not those inherited from the Object base class like toString.
It’s why we use the getDeclaredMethods method instead of getMethods.

Here is the complete code of the unit-test:

@Test
public void canGetMethods() throws Exception
{
    String[] methods = client.getMethods();

    Set<String> methodsName = new HashSet<String>();
    for (String method : methods)
    {
        methodsName.add(method.split("\\(")[0]);
    }
	
    Set<String> clientMethods = new HashSet<String>();
    for (Method method : Client.class.getDeclaredMethods())
    {
        if (!Modifier.isStatic(method.getModifiers()) && Modifier.isPublic(method.getModifiers()))
        {
            clientMethods.add(method.getName());
        }
    }
	
    assertTrue(methodsName.size() >= 18);
    assertTrue(clientMethods.equals(methodsName));
	
    return;
}

HashSets are used for two reasons:

  • they don’t store duplicates, so if there is more than one overload for a method, like uploadFile, the set will store the name once
  • two sets can be easily compared for equality based on their elements

Result

To illustrate how the language binding can be used to simply store and retrieve a file here is the JUnit unit-test used to test the “uploadFile” and “downloadFile” methods:

@Test
public void canUploadAndDownloadAFile() throws Exception
{
	assertClean();

	String content = "The peanut, or groundnut (Arachis hypogaea), is a species in the legume or \"bean\" family (Fabaceae). The peanut was probably first domesticated and cultivated in the valleys of Paraguay.[1] It is an annual herbaceous plant growing 30 to 50 cm (1.0 to 1.6 ft) tall. The leaves are opposite, pinnate with four leaflets (two opposite pairs; no terminal leaflet), each leaflet is 1 to 7 cm (⅜ to 2¾ in) long and 1 to 3 cm (⅜ to 1 inch) broad.\n" +
					 "The flowers are a typical peaflower in shape, 2 to 4 cm (0.8 to 1.6 in) (¾ to 1½ in) across, yellow with reddish veining. Hypogaea means \"under the earth\"; after pollination, the flower stalk elongates causing it to bend until the ovary touches the ground. Continued stalk growth then pushes the ovary underground where the mature fruit develops into a legume pod, the peanut – a classical example of geocarpy. Pods are 3 to 7 cm (1.2 to 2.8 in) long, containing 1 to 4 seeds.[2]\n" +
					 "Peanuts are known by many other local names such as earthnuts, ground nuts, goober peas, monkey nuts, pygmy nuts and pig nuts.[3] Despite its name and appearance, the peanut is not a nut, but rather a legume.";
	byte[] contentBytes = content.getBytes(charset);
	File inputFile = new File("Peanuts.txt");
	inputFile.setContent(contentBytes);

	FileInfo info = client.uploadFile(authToken, inputFile, testFolderInfo);
	assertEquals(contentBytes.length, info.getSize());

	File outputFile = client.downloadFile(authToken, info);

	String outputContent = new String(outputFile.getContent(), charset);

	assertEquals(content, outputContent);
}

If you drop all the assertions you can see that this non trivial task is made relatively straightforward.

I let you judge whether the result is worth the trouble…

Conclusion

As you see combining the Apache HTTP client, Gson, JUnit, JRE magic, reflection and some background on web development is enough to develop a Java binding for virtually any web API.

If you want more precision on one of the sections of this article or on a part of the Java source code not detailed in this article do not hesitate to ask by letting a comment, I’ll update the article accordingly.

If you have already developed a Java binding or expect to build one please share your experience and suggestions, I’d like to hear from you.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove me you\'re human :) *