Writing a Custom Host Object
- Ruchira Wageesha
- Software Engineer - WSO2
Contents
- Introduction
- What will you be creating?
- Where the Host Object code will reside?
- How WSO2 Mashup Server will know about newly added Host Objects?
- Writing our HostOjbect code
- Compiling, Packaging and Deploying the Sample Code
- Conclusion
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. - 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 withinjsFunction_foo()
Java method. - A Host Object property named
bar
should implement two methods namedjsSet_bar()
andjsGet_bar()
where the code for value set operation should go withinjsSet_bar
and the code for property get operation should go withinjsGet_bar
. For a read-only property,jsSet_bar()
should be omitted.
JavaScript-HostObject: org.wso2.carbon.mashup.Foo,org.wso2.carbon.mashup.Bar
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