About this tutorial
This tutorial shows how to extend XINS with your own custom protocol, step by step. The exercise should take you less than 15 minutes.
Prerequisites
You should have finished the XINS Primer as we will continue where the latter left off.
Use XINS 3.1.
Overview of contents
This article is divided in the following sections:
- 1. Introduction
- 2. Create the Java file (2 steps)
- 3. Analyze the incoming request (1 step)
- 4. Produce the response (1 step)
- 5. Register the calling convention (1 step)
- 6. Test (4 steps)
- 7. Finally
1. Introduction
The XINS framework supports different protocols, including XML-RPC, SOAP and POX-RPC. In XINS terminology, these are called calling conventions. Different calling conventions live next to each other at run-time. When a request comes in, the framework determines which calling convention should handle the request:
XINS can be extended with custom calling conventions. Such a calling convention has the following responsibilities:
- indicate which HTTP methods it supports (defaults to GET, HEAD and POST);
- indicate for a specific HTTP request whether it can handle it (optional);
- interpret an HTTP request and convert it to a XINS request object;
- convert a XINS response object to an HTTP response.
2. Create the Java file
Let's prepare to create the initial version of the Java source file. A minimal custom calling convention implementation must:
- extend class
CustomCallingConvention
; - have a public no-argument constructor;
- implement the
convertRequestImpl
method; - implement the
convertResultImpl
method.
Step 1. | Remember that the XINS Primer let you create a small XINS project? You need that now.
Under the |
Step 2. | In that cc directory, create a file called MyCallingConvention.java with the following contents:
package com.mycompany.cc; import java.io.IOException; import java.util.Iterator; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.xins.common.Log; import org.xins.common.collections.PropertyReader; import org.xins.common.servlet.ServletRequestPropertyReader; import org.xins.common.text.ParseException; import org.xins.server.CustomCallingConvention; import org.xins.server.FunctionNotSpecifiedException; import org.xins.server.FunctionRequest; import org.xins.server.FunctionResult; import org.xins.server.InvalidRequestException; public class MyCallingConvention extends CustomCallingConvention { public MyCallingConvention() { // empty } } |
3. Analyze the incoming request
The convertRequestImpl
method gets an HttpServletRequest
object as input, based on which it needs to determine:
- which function should be called;
- which parameters, if any, should be passed to that function;
- is there a data section to pass to the function?
This information will then be used to invoke the correct function, with the appropriate arguments.
Visually:
This information then needs to be returned to the XINS server engine in a FunctionRequest
object.
Our example implementation expects the function in an HTTP parameter named _function
and it uses the other HTTP parameters as parameters for the functions. For the sake of simplicity, it does not support an input data section.
Step 3. | Add the following code in your class:
protected FunctionRequest convertRequestImpl(HttpServletRequest httpRequest) throws InvalidRequestException, FunctionNotSpecifiedException { // Trace this method call Log.log_1053("In MyCallingConvention.convertRequestImpl(...)"); // Determine which function should be invoked String function = httpRequest.getParameter("_function"); // The function name must be specified if (function == null || "".equals(function)) { throw new FunctionNotSpecifiedException(); } // Directly convert the parameters to a PropertyReader object PropertyReader params; try { params = new ServletRequestPropertyReader(httpRequest); } catch (ParseException exception) { throw new InvalidRequestException("Failed to parse request.", exception); } // XXX: The input data section is not supported // Return an appropriate XINS request object return new FunctionRequest(function, params, null); } |
4. Produce the response
Once the function has processed the request, the framework hands the result to the convertResultImpl
method in the calling convention. The latter converts it to an HTTP response, which is then returned to the client:
The result information comes in as a FunctionResult
object.
Our example implementation will take the result and convert it to a simple HTML page that lists the output parameters with their respective values.
Step 4. | Now add the following code in your class:
protected void convertResultImpl(FunctionResult xinsResult, HttpServletResponse httpResponse, HttpServletRequest httpRequest) throws IOException { // Trace this method call Log.log_1053("In MyCallingConvention.convertResultImpl(...)"); // Generate HTML String html = generateHTML(xinsResult); // Set HTTP status and add headers httpResponse.setStatus(HttpServletResponse.SC_OK); httpResponse.setContentType("text/html; charset=UTF-8"); httpResponse.addIntHeader("Content-Length", html.length()); // Write the body, except for HTTP HEAD requests if (! "HEAD".equals(httpRequest.getMethod())) { httpResponse.getWriter().print(html); } } private String generateHTML(FunctionResult result) { // Determine error code and output parameters String error = result.getErrorCode(); PropertyReader params = result.getParameters(); // Top part String html = "<html><head><body>" + "<h3>Error code</h3>" + "The error code is: " + error + "<h3>Output parameters</h3>" + "The following output parameters are set:" + "<table border=1><tr>" + "<th>Name</th>" + "<th>Value</th>" + "</tr>"; // One table row per parameter Iterator names = params.getNames(); while (names.hasNext()) { String name = (String) names.next(); String value = params.get(name); html += "<tr><td>" + name + "</td><td>" + value + "</td></tr>"; } // Bottom part html += "</table></body></html>"; return html; } |
5. Register the calling convention
Each calling convention needs to have a unique name. This name can be passed in a request, to explicitly specify that this is the one to be used. The name may contain letters, digits and underscores, but it should start with a letter.
For this exercise we will use the name:
mycc
Edit impl.xml
In order for the framework to know about a custom calling convention, it must be registered in the impl.xml
file for the appropriate API.
Step 5 | Under the MyProject directory, edit the apis/myapi/impl/impl.xml file and add a <calling-convention> tag as follows:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE impl PUBLIC "-//XINS//DTD Implementation 2.0//EN" "http://www.xins.org/dtd/impl_2_0.dtd"> <impl> <calling-convention name="mycc" class="com.mycompany.cc.MyCallingConvention"/> </impl> |
6. Test
Step 6 | From the MyProject directory, start the internal server:
xins -Dorg.xins.server.config=xins.properties run-myapi In the startup log, notice the following entry: 3245 INFO Default calling convention is "mycc". Note: This log message is documented on-line, along with all other log messages produced by XINS. |
Step 7 | With a browser visit the following URL (assuming you deployed the application on the local machine):
Notice that the calling convention to use is explicitly set to You should see an XML document as output. Meanwhile, your log should show output similar to the following: 3521 INFO Received HTTP GET request from 127.0.0.1, path is "/myapi/", query string is "_function=SayHello&name=John&_convention=_xins-std".
|
Step 8 | Let's change the calling convention from _xins-std to mycc ; with your browser visit:
You should now see an HTML document, produced by your very own calling convention. |
Step 9 | Finally, let's see what XINS does when we do not explicitly indicate which calling convention should be used. Point your browser to:
As indicated in the startup log (see step 6), XINS defaults to the |
7. Finally
For more information about custom calling conventions, study the Javadoc API documentation for the CustomCallingConvention class.