2009/12/02
2 Dec, 2009

Writing a Custom Host Object

  • Ruchira Wageesha
  • Software Engineer - WSO2

Contents

 

Introduction

One of the powerful features of WSO2 Mashup Server is its extensibility by using Javascript Host Objects. Using a Javascript Host Object, the process of creating a Mashup can be made easier by mapping Java logic to Javascript. While WSO2 Mashup Server ships with several useful Host Objects by default, you can enhance the functionality by writing your own as well. This article focuses on a step by step process of creating a new Host Object.

What will you be creating?

In the following tutorial, we will walk through the process of creating a new Host Object named SimpleHttpClient. The SimpleHttpClient Host Object will allow a Mashup author to specify a URL and an HTTP method which it will then use to talk to the server and give the response. Below is the summary of methods we will implement in this Host Object.

NOTE: All our code goes within a Java class named SimpleHttpClientHostObject under the package named org.wso2.carbon.mashup.javascript.hostobjects.simplehttpclient. (A CarbonException is thrown with the error message when an error condition is detected. This will help us debug what went wrong in the Java code and where).

Member Description
Scriptable SimpleHttpClient(optional, [String method, String url]) This is the constructor of the Host Object. Constructor can be empty or passed two string parameters for HTTP method and URL. var simpleHttpClient1 = new SimpleHttpClient(); var simpleHttpCleint2 = new SimpleHttpClient("POST", "https://wso2.org");
int execute() This executes the HTTP request with the specified method and url. Further, this returns the HTTP status returned by the server. simpleHttpClient.execute();
void releaseConnection() This releases the connection acquired with the server simpleHttpClient.releaseConnection();
property Object credentials This property can be used to provide a JavaScript object with the username and password credentials needed to set for HTTP basic Authentication enabled urls. simpleHttpclient.credentials={username:"user", password:"pass"};
property String httpMethod This property can be used to provide the HTTP method that need to be executed. It might contain one of "GET" and "POST". simpleHttpClient.httpMethod="POST";
property String url This property can be used to provide the URL that need to be executed. simpleHttpClient.url="https://wso2.org";
readonly property String response This property provides the response after fetching the specified URL. simpleHttpClient.response
public class SimpleHttpClientHostObject extends ScriptableObject {

    public String getClassName() {
    }

    public SimpleHttpClientHostObject() {
    }

    public static Scriptable jsConstructor(Context cx, Object[] args, Function ctorObj, boolean inNewExpr) 
                                                                                    throws CarbonException {
    }

    public static int jsFunction_execute(Context cx, Scriptable thisObj, Object[] args, Function funObj) 
                                                                                  throws CarbonException {
    }

    public static void jsFunction_releaseConnection(Context cx, Scriptable thisObj, Object[] args, Function funObj) 
                                                                                             throws CarbonException {
    }

    public void jsSet_credentials(Object object) throws CarbonException {
    }

    public Scriptable jsGet_credentials() throws CarbonException {
    }

    public void jsSet_httpMethod(Object param) throws CarbonException {
    }

    public Object jsGet_httpMethod() throws CarbonException {
    }

    public void jsSet_url(Object param) throws CarbonException {
    }

    public Object jsGet_url() throws CarbonException {
    }

    public String jsGet_response() throws CarbonException {
    }
}

The code above will give you an idea of all the methods that we are going to implement in our new Host Object.

Where will the Host Object code reside?

Version 2.0.0 of WSO2 Mashup Server is based on the WSO2 Carbon Platform. Every feature is developed as an OSGi bundle. Therefore our Host Object code will also reside inside an OSGi bundle. An OSGi bundle is also a .jar file. However, in order to act as an OSGi bundle, it needs to have several headers in its MANIFEST.MF file. A more descriptive article about creating an OSGi bundle can be found here. A simple counter method have been illustrated under the title Compiling, Packaging and Deploying Sample Code.

How will WSO2 Mashup Server know about newly added Host Objects?

At server startup, the WSO2 Mashup Server checks all active bundle headers to detect Host Object implementations. If a bundle has Host Objects implemented, a header should be added to the manifest file specifying the fully qualified class names of the Host Object implementation. Multiple Host Object classes can be added separated by commas.

For example, if org.wso2.carbon.mashup.Foo and org.wso2.carbon.mashup.Bar are two Host Object implementations, then a header should be added to the manifest as follows.

JavaScript-HostObject: org.wso2.carbon.mashup.Foo,org.wso2.carbon.mashup.Bar

Writing our Host Object code

All Host Object implementations should extend org.mozilla.javascript.ScriptableObject class as the WSO2 Mashup Server Host Objects are based on Mozilla Rhino's Host Objects concept.

Name of the Host Object

public String getClassName() {
    return "SimpleHttpClient";
}

org.mozilla.javascript.ScriptableObject class has an abstract method named getClassName(), which needs to be implemented within our Host Object. It should just return the name of our Host Object. The code above will define a Host Object named SimpleHttpClient which can be initiated as

var client = new SimpleHttpClient();

using Javascript within a mashup.

NOTE: Java class name and the name of the Host Object does not have to be the same.

Java class Constructor

public SimpleHttpClientHostObject() {
        MultiThreadedHttpConnectionManager connectionManager =
                new MultiThreadedHttpConnectionManager();
        httpClient = new HttpClient(connectionManager);
}

This is the constructor of our Java class. We will instantiate an instance of SimpleHttpClientHostObject class within the jsConstructor method below where this constructor gets called.

Host Object Constructor

The implementation of the jsConstructor method in our Host Object class will act as the constructor. This is the mapping of Javascript constructor of the Host Object to our Java class.

public static Scriptable jsConstructor(Context cx, Object[] args, Function ctorObj, boolean inNewExpr) 
                                                                               throws CarbonException {

    SimpleHttpClientHostObject simpleHttpClient =  new SimpleHttpClientHostObject();

    if(args.length == 0) {
        ......
    } else if(args.length == 1) {
        ......
    } else if(args.length == 2) {
        ......
    } else if(args.length == 3) {
        ......
    } else {
        throw new CarbonException("Invalid number of parameters in the constructor");
    }
}

When the Host Object constructor is called within a mashup, this method gets called directly. Several parameters are passed to this method by Mozilla Rhino, which will be needed to implement our logic. The Object array named args, which is passed to the constructor contains all the arguments specified within the Javascript constructor. Using this array, it is possible to detect whether this function call is valid and whether the needed parameters have been passed to the constructor. With the use of if-else code fragment, we can implement our logic as needed. Another useful feature is the ability to define optional parameters easily.

cx The Context of the current thread
args The array of arguments for this call
ctorObj The function object of the invoked JavaScript function. This value is useful to compute a scope using Context.getTopLevelScope()
inNewExpr Whether the expression that caused the call is a new one or not (This supports defining a function that has different behavior when called as a constructor than when invoked as a normal function call)

Adding methods to our Host Object

If a method needs to be added to the Host Object, just create a Java method with the name of the expected method prefixed by jsFunction_ . For example, if we need to have a method named execute() for our Host Object, all we have to do is create a Java method named jsFunction_execute() and put our logic within this method.

public static int jsFunction_execute(Context cx, Scriptable thisObj, Object[] args, Function funObj) 
                                                                              throws CarbonException {

        // initializes a pointer to the hostObject instance in the current context
        SimpleHttpClientHostObject simpleHttpClient = (SimpleHttpClientHostObject) thisObj;

        if (args.length == 0) {
            // this method call does not contain input parameters
            ......
        } else if(args.length == 1) {
            ......
        } else if  (args.length == 2) {
            ......
        } else {
            throw new CarbonException("Invalid usage of arguments");
        }
}

This jsFunction_execute() method maps Java logic inside it into the execute() Javascript method of the Host Object. This also contains the Context object, an Object array and a Function object as in the jsConstructor() method above. In addition to that, a Scriptable object named thisObj is also passed, which refers the Javascript this value.a thisObj is the instance of the SimpleHttpClientHostObject which is then cast into a SimpleHttpClientHostObject object. The rest of the code is the same as in the jsConstructor(). The args array is analysed to detect whether the passed parameters are as expected.

Adding Properties to our Host Object

If a property needs to be added to the Host Object, just create two Java methods with the name of the expected property prefixed by jsSet_ and jsGet_ . For instance, if we need to have a property named credentials for our Host Object, all we have to do is create two Java methods named jsSet_credentials() and jsGet_credentials().

Java code for the set operation should go inside jsSet_credentials method, where an object parameter is passed. This object parameter refers to the value of the property set by Javascript. Its type is analysed and needed data will be assigned to the corresponding Java properties.

Java code for the get operation goes inside jsGet_credentials method. If a particular property has only a jsGet_ implementation, then it is a readonly property.

public void jsSet_credentials(Object object) throws CarbonException {
        if (object instanceof NativeObject) {
            .......
        } else {
            throw new CarbonException("Variable should be an Array");
        }
}

public Scriptable jsGet_credentials() throws CarbonException {
        return this.nativeCredentials;
}

This pair of functions is similar to Java Bean's getX and setX methods. By having these two methods, we can define a property in our Host Object which can be assigned or accessed using simpleHttpClient.credentials. Any Javascript data type can be set as a property using these two methods. Here, in the credentials property of our Host Object, it is a Javascript object which can be set with Javascript as below

simpleHttpClient.credentials = {username:"user", password:"pass"};

At the end of the first section of this tutorial, the key characteristics of Javascript Host Objects can be summarised as follows.

  • Java code for the Host Object should go within an OSGi bundle
  • A header should be added into the .jar manifest specifying fully qualified class names of the Host Objects implemented within the bundle. Multiple class names are separated by commas.
  • JavaScript-HostObject: org.wso2.carbon.mashup.Foo,org.wso2.carbon.mashup.Bar

  • The Host Object class should extend org.mozilla.javascript.ScriptableObject class. Name of the Host Object does not need to be the same as its Java class.
  • Host Object name should be returned by the implementation of abstract method, getClassName() of ScriptableObject.
  • Host Object constructor code should go within the jsConstructor() Java method.
  • A Host Object method named foo() should go within jsFunction_foo() Java method.
  • A Host Object property named bar should implement two methods named jsSet_bar() and jsGet_bar() where the code for value set operation should go within jsSet_bar and the code for property get operation should go within jsGet_bar. For a read-only property, jsSet_bar() should be omitted.

Compiling, packaging and deploying the sample code

Compiling

In our sample Host Object, I have used Apache Http Client 3.x library which can be downloaded from here. Further we need the Mozilla Rhino and Carbon Utils libraries. For your convenience, all the required library jars are attached below.

In order to compile the sample source files, download the attached zip file, unzip it and go to the samples directory. There are a lib folder which contains all needed jars, org folder contains Java source, at the root folder of our sample package, there is file named manifest.txt which contains the bundle headers that need to be added. Assuming that you are in the samples directory, please execute the following command (if you are in windows, please replace ":" in the command with ";").

javac -classpath lib/js-core-1.6R7.jar:lib/org.wso2.carbon.utils-2.0.1.jar:lib/commons-httpclient-3.1.jar org/wso2/carbon/mashup/javascript/hostobjects/simplehttpclient/SimpleHttpClientHostObject.java

Packaging

As mentioned earlier, Host Objects should reside within an OSGi bundle. A bundle can have many Host Objects. The bundle manifest should contain a header with the class names of its Host Objects. A more detailed tutorial on creating an OSGi bundle for the WSO2 Carbon Framework can be found at the link provided under the section Where will the Host Object code reside? above. Another method is described below.

The headers that need to be added into the bundle manifest in order to act as a bundle can be put in a file and that file can be specified to be used as MANIFEST.MF at the packaging phase of our Java classes using the command below.

jar cmf <path-to-manifest-file> <bundle-name> <package-root-folder>

NOTE: Before using this command you should change your directory to the same directory as your package root directory.

For our sample code, assuming that you are in the samples folder, please execute the following command.

jar cmf manifest.txt org.wso2.carbon.mashup.javascript.hostobjects.simplehttpclient.jar org/

Deploying

If you haven't downloaded WSO2 Mashup Server yet, download 2.0 release at https://wso2.org/projects/mashup and unzip. Copy the above packaged jar or the org.wso2.carbon.mashup.javascript.hostobjects.simplehttpclient.jar which is in the samples/compiled directory into the $wso2mashup-2.0.0/repository/components/dropins directory and restart WSO2 Mashup Server.

NOTE: The dropins folder is created only when you have started WSO2 Mashup Server at least once. So when you download the distribution and extract it to your local file system, you will need to create a new folder named dropins there.

Using the Host Object

Create a new Mashup and paste the following code into it. Go to the Tryit page for this new Mashup and enter https://wso2.org as the url and GET as the method. Check whether it gives the content of the page at https://wso2.org

function getContent(url, method) {
    var simpleHttpClient = new SimpleHttpClient(); // new SimpleHttpClient(method, url)
    simpleHttpClient.httpMethod = method;
    simpleHttpClient.url = url;
    
    var status = simpleHttpClient.execute();
    var response;
    if(status == "200") {
        response = simpleHttpClient.response;
    } else {
        response = "Error Occured. Server returned " + status;
    }
    simpleHttpClient.releaseConnection();

    return response;
}

Conclusion

This tutorial demonstrates a step by step process in creating a custom Host Object for WSO2 Mashup Server. In this way, one can easily extend the functionality of WSO2 Mashup Server giving its users the maximum flexibility to create mashups.

Author

Ruchira Wageesha, Software Engineer, WSO2 Inc. ruchira at wso2 dot com

 

About Author

  • Ruchira Wageesha
  • Software Engineer
  • WSO2