Sunday, April 6, 2014

Using Script mediators to build a url with optional parameters in a connector for WSO2 ESB

I thought to write another useful post that is related to connector development of WSO2 ESB. You may have seen many request URLs in APIs that mention a set of parameters. It's very important to identify the required and the optional parameters. In such cases we should know some efficient techniques on how to handle them inside the synapse template mediator.

Let me focus some real world REST API calls to explain these to you in this post. I use Linkedin API Reference for this post.

Handling request of a GET request with no URL parameters


Reference Link: 
https://developer.linkedin.com/documents/connections-api

Task:
Returns a list of 1st degree connections for a user who has granted access to his/her account.

E.g:
http://api.linkedin.com/v1/people/~/connections

If the request URL is as above where you see no any parameters mentioned but it  returns the authenticated user's list of first degree connection details, you can simply hard code the URL in the call mediator as below.

<template xmlns="http://ws.apache.org/ns/synapse" name="getConnections">
   <sequence>
      <call>
         <endpoint>
            <http method="get" uri-template="http://api.linkedin.com/v1/people/~/connections" />
         </endpoint>
      </call>
   </sequence>
</template>


Handling request of a GET method with required URL parameters


Reference Link: 
https://developer.linkedin.com/documents/connections-api

Task:
Returns the 1st degree connection's details with the specified id  for a user who has granted access to his/her account.

E.g:
http://api.linkedin.com/v1/people/id=12345/connections

Now we have a URL parameter that should be expected from the user through the proxy. It can be handled as below.

<template xmlns="http://ws.apache.org/ns/synapse" name="getConnections">
   <parameter name="id" description="Id of the member of whose connections are requested to be retrieved." />
   <sequence>
      <property name="uri.var.id" expression="$func:id" />
      <call>
         <endpoint>
            <http method="get" uri-template="http://api.linkedin.com/v1/people/id={uri.var.id}/connections" />
         </endpoint>
      </call>
   </sequence>
</template>


Handling request of a GET method with set of optional URL parameters


Reference Link: 

Task:
Returns the 1st degree connection's mentioned field details with the specified id  for a user who has granted access to his/her account.

E.g: 
http://api.linkedin.com/v1/people/~/connections:(headline,first-name,last-name)

This request is similar to retrieving  a list of 1st degree connections for a user who has granted access to his/her account. But this returns only the fields that are mentioned within the braces separated with commas.request will display only the headline,first-name and the last-name of the connections that are of 1st degree connections for the user who has granted access to his/her account. For an instance the above shown.

Let's assume the end users send the comma separated values to the above request as shown below through the proxy (assume it's an XML request),

<fieldSelectors>headline,first-name,last-name,id</fieldSelectors>

then the given URL can be constructed from the synapse template as shown below using a script mediator. Note that, I have mentioned how to construct it using javascript.

<template xmlns="http://ws.apache.org/ns/synapse" name="getConnections">
   <parameter name="fieldSelectors" description="Fields that are required tobe retrieved from the search result." />
   <sequence>
      <property name="uri.var.fieldSelectors" expression="$func:fieldSelectors" />
      <property name="uri.var.query" value="" />

      <script language="js"><![CDATA[
         //getting the comma separated values to a string variable
         var fieldSelectors = mc.getProperty('uri.var.fieldSelectors');

         //query variable is initially empty
         var query =mc.getProperty('uri.var.query');
   
         //if user has given some values to fieldSelectors variable through the proxy, then condition becomes true

         if (fieldSelectors != null && fieldSelectors != "") 
         {

          query=':'+'('+ mc.getProperty('uri.var.fieldSelectors')+')';

         }        
         mc.setProperty('uri.var.query', query);]]></script>

      <call>
         <endpoint>

            <!--since the fieldSelectors part of the URL is optional, it will  be set if an only f the user passes values to it. Else {uri.var.query} will be empty  -->
            <http method="get" uri-template="http://api.linkedin.com/v1/people/~/connections{uri.var.query}" />

         </endpoint>
      </call>
   </sequence>
</template>

Note: 

CDATA block within the script mediator must be used to avoid special characters to be encoded within the script mediator.

If you have any number of optional URL parameters in the request links, you can easily use script mediators as above in order to construct them as you wish. For better understanding see the below example as well where there are more number of parameters, how they should be concatenated with "&" symbol within the script mediator.

Reference Link: 

Task:
Covers all the URL GET requests of below shown calls of the connections API calls of Linkedin.

E.g: 

http://api.linkedin.com/v1/people/~/connections
http://api.linkedin.com/v1/people/id=12345/connections
http://api.linkedin.com/v1/people/~/connections:(headline,first-name,last-name)
http://api.linkedin.com/v1/people/url=http%3A%2F%2Fwww.linkedin.com%2Fin%2Flbeebe/connections

<template xmlns="http://ws.apache.org/ns/synapse" name="getConnections">

   <!--declarations of parameters -->
   <parameter name="id" description="Id of the member of whose connections are requested to be retrived." />
   <parameter name="publicProfileUrl" description="public url of the requested profile." />
   <parameter name="fieldSelectors" description="Fields that are required to be retrieved from the search result." />
   <parameter name="start" description="Start location within the result set for paginated returns." />
   <parameter name="count" description="The number of profiles to return.Values can range between 0 and 25." />
   <parameter name="modified" description="Values are updated or new." />
   <parameter name="modifiedSince" description="Value as a Unix time stamp of milliseconds since epoch." />

   <sequence>

      <!-- Setting the properties accepting the values passed through the proxy -->

      <property name="uri.var.id" expression="$func:id" />
      <property name="uri.var.publicProfileUrl" expression="$func:publicProfileUrl" />
      <property name="uri.var.fieldSelectors" expression="$func:fieldSelectors" />
      <property name="uri.var.start" expression="$func:start" />
      <property name="uri.var.count" expression="$func:count" />
      <property name="uri.var.modified" expression="$func:modified" />
      <property name="uri.var.modifiedSince" expression="$func:modifiedSince" />
      <property name="uri.var.query" value="" />

      <!-- Script Mediator block -->

      <script language="js"><![CDATA[//retrieving the values from the message context

         var apiUrl = 'http://api.linkedin.com'+'/v1/people/';
         var id = mc.getProperty('uri.var.id');
         var publicProfileUrl = 
                    mc.getProperty('uri.var.publicProfileUrl');               
         var fieldSelectors = mc.getProperty('uri.var.fieldSelectors');
         var query ='';
         var start = mc.getProperty('uri.var.start');
         var count = mc.getProperty('uri.var.count');
         var modified = mc.getProperty('uri.var.modified');
         var modifiedSince = mc.getProperty('uri.var.modifiedSince');

         if (id != null && id != "") 
         {
          query = apiUrl + 'id=' + mc.getProperty('uri.var.id') + '/connections';
         } 
         else if(publicProfileUrl != null && publicProfileUrl != "") 
         {
            //public profile url should be sent as a URL encoded string
            var encoded_publicProfileUrl =
                  encodeURIComponent(mc.getProperty('uri.var.publicProfileUrl'));                                               
            query = apiUrl + 'url=' + encoded_publicProfileUrl + '/connections';
         } 
          else 
          {
            query = apiUrl + '~/connections';
          }
      
          if (fieldSelectors != null && fieldSelectors != "") 
          {
            query = query + ':' + '(' + 
                          mc.getProperty('uri.var.fieldSelectors') + ')';        
          } 
  
          //since oauth2 access token should also be passed in every request url of Linkedin  
            query = query + '?oauth2_access_token=' + 
                          mc.getProperty('uri.var.accessToken');      
          //if 'start' optional param is provided append it to the URL with a '&' symbol
            if (start != null && start != "") 
            {
              query = query + '&start=' + mc.getProperty('uri.var.start');
            }
          //if 'count' optional param is provided append it to the URL with a '&' symbol
            if (count != null && count != "") 
            {
              query = query + '&count=' + mc.getProperty('uri.var.count');
            }
         //if 'modified' optional param is provided append it to the URL with a '&' symbol
            if (modified != null && modified != "") 
            {
              query = query + '&modified=' + 
                          mc.getProperty('uri.var.modified');      
            }
         //if 'modifiedSince' optional param is provided append it to the URL with a '&' symbol
            if (modifiedSince != null && modifiedSince != "") 
            {
              query = query + '&modified-since=' + 
                          mc.getProperty('uri.var.modifiedSince');
            }
  
         //set the query to message context's uri.var.query property 
              mc.setProperty('uri.var.query', query);]]></script>
      <call>
         <endpoint>
            <!--Since the entire URL is constructed above,it's just a matter of calling the endpoint as below. -->

            <http method="get" uri-template="{uri.var.query}" />

         </endpoint>
      </call>
   </sequence>
</template>



Hope it was useful to you. If so, please leave a comment.