Monday, December 4, 2017

Writing a Gmail Connector with Ballerina


In a previous post, I explained how to configure the Gmail API console to send an email. Now, I will write a simple program using Ballerina language to send an email using the Gmail API. Here, the use case is to check if the access token is valid and if not retrieve a new access token and call the API. Lets see how we can achieve this in Ballerina. I have used Ballerina 0.95.2 version.

Creating a Gmail Connector

First of all, I have created a config file named ballerina.conf which is residing in the root location of the project and added all the confidential information we require to call the Gmail API. These values are accessed when required with the capability of the Config API of Ballerina.

gmail_refresh_token=xxxxxxxxxxxxxxxxxxxxx
gmail_client_id=xxxxxxxxxxxxxxxxxxx
gmail_client_secret=xxxxxxxxxxxxxxxxxxxx
gmail_access_token=xxxxxxxxxxxxxxxxxxx

Following is the code for the Gmail connector in Ballerina. First, I check whether the access token has expired and get a new access token if it has expired. Then, I construct the message body of the request and encode it with base64 as it is expected by the API. I have already explained the details regarding this in the previous blog. Then, the connector will call the Gmail API and send the email or return an error if Ballerina couldn’t connect to the endpoint. The inline comments will guide you to understand the solution more.

import ballerina.net.http;
import ballerina.util;
import ballerina.config;
import ballerina.log;


string access_token = config:getGlobalValue("gmail_access_token");


public connector GmailConnector () {


  action sendMail (string to, string subject, string from, string messageBody, string cc, string bcc, string id,
                   string threadId) (json responseToSend, http:HttpConnectorError err) {


      //creating an endpoint where we indicate the endpoint we are interacting with
      endpoint<http:HttpClient> gmailEP {
          string baseURL = "https://www.googleapis.com/gmail";
          gmailEndpoint = create http:HttpClient(baseURL, {});
      }


      boolean isExpired;
      error e;
      error errAccess;


      //check if the access token has expired and get a new access token if expired
      isExpired, e = isAccessTokenExpired();


      if (isExpired) {
          access_token, errAccess = getNewAccessToken();
      } else if (!isExpired) {
          log:printInfo("Access token has not expired");
      } else {
          log:printError("Error: " + e.msg);
      }


      //create the message body of the request
      http:Request request = {};
      http:Response response = {};
      string concatRequest = "";


      if (to != "null") {
          concatRequest = concatRequest + "to:" + to + "\n";
      }


      if (subject != "null") {
          concatRequest = concatRequest + "subject:" + subject + "\n";
      }


      if (from != "null") {
          concatRequest = concatRequest + "from:" + from + "\n";
      }


      if (cc != "null") {
          concatRequest = concatRequest + "cc:" + cc + "\n";
      }


      if (bcc != "null") {
          concatRequest = concatRequest + "bcc:" + bcc + "\n";
      }


      if (id != "null") {
          concatRequest = concatRequest + "id:" + id + "\n";
      }


      if (threadId != "null") {
          concatRequest = concatRequest + "threadId:" + threadId + "\n";
      }


      if (messageBody != "null") {
          concatRequest = concatRequest + "\n" + messageBody + "\n";
      }


      //encoding the message and create a json message expected by the API
      string encodedRequest = util:base64Encode(concatRequest);
      json sendMailRequest = {"raw":encodedRequest};
      
      string sendMailPath = "/v1/users/me/messages/send";
      request.setHeader("Authorization", "Bearer " + access_token);
      request.setHeader("Content-Type", "application/json");
      request.setJsonPayload(sendMailRequest);


      if (errAccess == null) {
          log:printInfo("Access token is " + access_token);
          response, err = gmailEP.post(sendMailPath, request);
          responseToSend = response.getJsonPayload();
      }


      return;
  }


}


This is the function to check if the access token has expired. This function will return true if the access token is valid.

function isAccessTokenExpired () (boolean, error) {
  endpoint<http:HttpClient> gmailTokenEP {
      create http:HttpClient("https://www.googleapis.com/oauth2/v1", {});
  }


  http:Request request = {};
  http:Response response = {};
  http:HttpConnectorError err;
  error msg;
  string res;
  boolean isExpired = false;


  string body = "access_token=" + access_token;
  request.setStringPayload(body);
  request.setHeader("content-type", "application/x-www-form-urlencoded");


  response, err = gmailTokenEP.post("/tokeninfo", request);
  json jResponse = response.getJsonPayload();


  try {
      if ((error)err == null) {
          //check if an error returns as the response, meaning the token has expired
          res, _ = (string)jResponse.error;
          isExpired = true;
          log:printInfo("Access Token has been expired");
      } else {
          msg = {msg:"Error occurred when sending a request to retrieve the access token"};
      }
  } catch (error e) {
      msg = {msg:"Error getting the access token"};
  }


  return isExpired, msg;
}



This function is to generate a new access token if it has expired.

function getNewAccessToken () (string, error) {
  endpoint<http:HttpClient> gmailTokenEP {
      create http:HttpClient("https://www.googleapis.com/oauth2/v4", {});
  }


  string refresh_token = config:getGlobalValue("gmail_refresh_token");
  string client_id = config:getGlobalValue("gmail_client_id");
  string client_secret = config:getGlobalValue("gmail_client_secret");


  http:Request request = {};
  http:Response response = {};
  http:HttpConnectorError err;
  error msg;
  string body = "grant_type=refresh_token&client_id=" + client_id + "&client_secret=" + client_secret + "&refresh_token=" + refresh_token;
  request.setStringPayload(body);


  request.setHeader("content-type", "application/x-www-form-urlencoded");


  response, err = gmailTokenEP.post("/token", request);
  json jResponse = response.getJsonPayload();


  try {
      if ((error)err == null) {
          access_token, _ = (string)jResponse.access_token;
          log:printInfo("New access token is generated");
      } else {


          msg = {msg:"Error occurred when sending a request to retrieve the access token"};
      }
  } catch (error e) {
      msg = {msg:"Error getting the access token"};
  }


  return access_token, msg;


}


Invoking the connector


Now, we will invoke the connector. The following is a main method where the connector is being invoked.
function main (string[] args) {


  endpoint<conn:GmailConnector> ep {
      create conn:GmailConnector();
  }


  var mailSent, ex = ep.sendMail("dilinig@wso2.com", "This is the subject", "dilinisg@gmail.com", "This is the message", "", "", "", "");


  if (ex == null) {
      log:printInfo("Mail successfully sent");
  }
  else {
      error err = (error)ex;
      log:printError("Error occured when sending the mail: " + err.msg);
  }
}


This simple connector can be improved a lot and errors can be handled better. This is just a guideline for you to start implementing your own connector. Happy coding with Ballerina :)



No comments:

Post a Comment