Saving and Retrieving Images using Cloudant with HTTP Adapters

Overview

Cloudant is a DBaaS, Database-As-A-Service, that manages and scales fast growing data sets. Cloudant is built under Couchdb, a NoSQL database which uses dynamic schemas instead of predefined schemas seen in relational database. The user has the complete power to be create a very structured database or non structured database. In this blog post, I will create a Hybrid app that uses a Cordova Plugin to read raw data and store it into Cloudant. In addition, I will use a HTTP adapter to retrieve that content from Cloudant. The raw data will be an base64 encoded string of an image. I will be using a sample cluster which you can view by going to this url : https://ibmdemo.cloudant.com. The account name is ibmdemo and the password is password123.

Create your database

In the ibmdemo cluster I have already created a database called demo. To create a database you can use the dashboard, use Cloudant's REST Services to generate a database or use any client library that is compatible with Cloudant missing_alt

Save the image into your database

We can use a Cordova plugin to read an image file from an app then store this image into our Cloudant Database. In my example I am using iOS so I created two files, ReadFiles.m and ReadFiles.h, that are responsible for reading an image file from a sample app and converting it to a base64 string. This string will be sent to our Cloudant database called demo.

ReadFiles.m

1
2
3
4
5
6
7
8
9
10
11
12
#import "ReadFiles.h"
@implementation ReadFiles
- (void)readFile:(CDVInvokedUrlCommand *)command{
    NSString *fileName = [command.arguments objectAtIndex:0];
    NSString *fullPath = [NSString stringWithFormat:@"www/default/images/%@", fileName];
    NSString *path = [[NSBundle mainBundle] pathForResource:fullPath ofType:@"png"];
    NSData *myData = [NSData dataWithContentsOfFile:path];
    NSString *img = [myData base64EncodedStringWithOptions:0];
    CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:img];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@end

ReadFiles.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#import <Foundation/Foundation.h>
#import <Cordova/CDV.h>
@interface ReadFiles : CDVPlugin
- (void)readFile:(CDVInvokedUrlCommand*)command;
@end[/code]
Once we have the base64 string we will need to send a POST request to the database to store the document.
[code lang="js"]
var getData = function(fileName) {
    var def = $.Deferred();
    cordova.exec(
                 function (result) {
                 setTimeout(function () {
                            def.resolve(result);
                            },0);
                 },
                 function (error) {
                 setTimeout(function () {
                            def.reject(error);
                            },0);
                 }, "ReadFiles", "readFile", [fileName]);
    return def.promise();
};
function generateName(){
	var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
	var input = "";
	for(var i = 0; i < 5; i++){
		input+=chars[Math.floor(Math.random(10)*chars.length)];
	}
	return input;
}
function sendRequest(res){
	var def = $.Deferred();
	name = generateName();
	var payload = {
			"_id" : name,
			"payload": res
	};
	$.ajax({
        url: 'https://ibmdemo.cloudant.com/demo',
        data : JSON.stringify(payload),
        contentType : "application/json",
           headers : {"Authorization" : "Basic " + btoa("ibmdemo:password123")},
        dataType : "json",
        type : "POST",
        success : function(response) {
           console.log("OK " + response);
     		WL.Logger.info(response);
     		def.resolve(response);
        },
        error : function(response) {
     	   WL.Logger.error(response);
     	   def.reject(response);
        }
    });
	return def.promise();
}
function saveToCloudant(){
		getData("icon")
			.then(function (res) {
				return sendRequest(res);
			})
			.then(function (res) {
				console.log("saved to cloudant");
			})
			.fail(function (err) {
				console.log("failed ", err);
			});
}

You may notice that I added an authorization header to my request by creating a base64 string comprised of the username and password concatenated by a colon. I do this because I am sending a through HTTPS. You may not need to do this if you are using HTTP but it is recommended that you send request through HTTPS. In addition, I will strongly suggest you not hardcode your username and password into your production code. There are other ways to securely send data into Cloudant, but this is beyond the scope of this post.

After saving your image you can preview the document that you have created using the dashboard. missing_alt

Retrieve your image from Cloudant

Just as we did in storing the image into Cloudant we can use a GET request to retrieve our image. We will use an HTTP adapter to request the image. You will need to edit the .xml file and the -impl.js file.

cloudant.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<wl:adapter name="cloudant"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:wl="http://www.ibm.com/mfp/integration"
	xmlns:http="http://www.ibm.com/mfp/integration/http">
	<displayName>cloudant</displayName>
	<description>cloudant</description>
	<connectivity>
		<connectionPolicy xsi:type="http:HTTPConnectionPolicyType">
			<protocol>https</protocol>
			<domain>ibmdemo.cloudant.com</domain>
			<port>443</port>
			<connectionTimeoutInMilliseconds>30000</connectionTimeoutInMilliseconds>
			<socketTimeoutInMilliseconds>30000</socketTimeoutInMilliseconds>
			<maxConcurrentConnectionsPerNode>50</maxConcurrentConnectionsPerNode>
			<!-- Following properties used by adapter's key manager for choosing specific certificate from key store
			<sslCertificateAlias></sslCertificateAlias>
			<sslCertificatePassword></sslCertificatePassword>
			-->
		</connectionPolicy>
	</connectivity>
	<procedure name="getImage"/>
</wl:adapter>

cloudant-impl.js

1
2
3
4
5
6
7
8
9
10
11
function getImage(param) {
	var config = JSON.parse(param);
	var input = {
	    method : 'get',
	    path : '/' + config.db + '/' + config.attachment,
	    headers : {
	    	Authorization : "Basic " + config.auth
	    }
	};
	return WL.Server.invokeHttp(input);
}

Just like when we sent a request to store our image we will need to include an authorization header to receive it. Please take note of the path; the path is /$DB_NAME/$DOCUMENT_ID. Finally, add the invokeProcedure request in your main.js file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function retrieveImageFromCloudant(){
	var config = {
			db : "demo",
			attachment : name,
			auth : btoa("ibmdemo:password123")
	};
	 WL.Client.invokeProcedure({
		adapter: 'cloudant',
		procedure: 'getImage',
		parameters: [JSON.stringify(config)],
		compressResponse : true
	})
		.then(function (res) {
              var responseJSON = JSON.parse(res.responseText);
              var data = JSON.parse(responseJSON.text);
			var img =document.getElementById('img');
			img.setAttribute("src", "data:image/png;base64,"+data.payload);
		})
		.fail(function (err) {
			console.log("Failed to read image");
		});
}

Putting it all together

For the UI I used two buttons, one button that will stored the image into Cloudant as a base64 string and the second button to retrieve the image using an HTTP adapter request. The final result should look like the following. missing_alt

Hopefully, this blog post will show you how easy it is to use Cloudant as your DBaaS without too much effort.

CloudantAdapter App

Resources

Official Cloudant JS Library Docs

Inclusive terminology note: The Mobile First Platform team is making changes to support the IBM® initiative to replace racially biased and other discriminatory language in our code and content with more inclusive language. While IBM values the use of inclusive language, terms that are outside of IBM's direct influence are sometimes required for the sake of maintaining user understanding. As other industry leaders join IBM in embracing the use of inclusive language, IBM will continue to update the documentation to reflect those changes.
Last modified on May 01, 2016