Java RPC
Building a qooxdoo test application
The Java backend comes with a build.xml file that generates a web application archive (WAR). (In order to use this build file, you need to have Ant installed.) The resulting WAR contains all the necessary server-side and client-side classes to experiment with the RPC mechanism.
To build the test WAR, simply follow these steps on the command line:
cd /qooxdoo/frontend make build cd /qooxdoo/backend/java ant
Now deploy the WAR in a Java web server of your choice (e.g. Apache Tomcat). You can then point your browser to one of the RPC test pages (e.g. http://localhost:8080/qooxdoo/sample/html/test/RPC_1.html) to see the RPC mechanism in action.
Future qooxdoo releases may also include a pre-built Java backend.
Building your own applications
You can use the supplied build.xml as a starting point for your own applications. For example, you can modify it to include your own applications instead of the qooxdoo examples. Or you can modify it to build a JAR with the qooxdoo RPC classes and add that to an already existing webapp of yours. In this case, you have to add a mapping for the RpcServlet in your web.xml (see webapp/WEB-INF/web.xml in the Java backend).
For development, you can use cross-domain calls (see below). This way, you can load HTML and script files via file:// URLs, and only the server part needs to be packaged in a WAR. To see any client-side changes, simply reload the page. When you're ready to put the application into production, set cross-domain to false and add the client part to the WAR. There are also more sophisticated solutions (e.g. using a servlet and a custom classloader to load scripts), but these are beyond the scope of this article.
Writing your own service methods
Writing your own remotely callable methods is very easy. Just create a class like this:
package my.package; import net.sf.qooxdoo.rpc.RemoteService; import net.sf.qooxdoo.rpc.RemoteServiceException; public class MyService implements RemoteService { public int add(int a, int b) throws RemoteServiceException { return a + b; } }
All you need to do is include this class in your webapp (together with the qooxdoo backend classes), and it will be available for calls from JavaScript! You don't need to write or modify any configuration files, and you don't need to register this class anywhere. The only requirements are:
- The class has to implement the
RemoteServiceinterface. This is a so-called tagging interface, i.e. it has no methods. - All methods that should be remotely available must be declared to throw a
RemoteServiceException.
Both requirements are there to protect arbitrary Java code from being called.
Accessing the session
There is one instance of a service class per session. To get access to the current session, you can provide an injection method called setQooxdooEnvironment:
package my.package; import javax.servlet.http.HttpSession; import net.sf.qooxdoo.rpc.Environment; import net.sf.qooxdoo.rpc.RemoteService; import net.sf.qooxdoo.rpc.RemoteServiceException; public class MyService implements RemoteService { private Environment _env; public void setQooxdooEnvironment(Environment env) { _env = env; } public void someRemoteMethod() throws RemoteServiceException { HttpSession session = _env.getRequest().getSession(); } }
The environment provides access to the current request (via getRequest) and the RpcServlet instance that is handling the current call (via getRpcServlet).
Advanced Java topics
Automatic client configuration
The Java RPC backend contains an auto-config mechanism, mainly used for automatically detecting the server URL. You can access it by including the following script tag in your HTML page:
<html> <head> <!-- ... --> <script type="text/javascript" src=".qxrpc"></script> </head> </html>
Provided the HTML page is part of the webapp (and not loaded via file://…), and provided that you didn't change the default mapping of the RpcServlet (.qxrpc), any request to http://server/app/foo/bar.qxrpc (or anything else that ends with .qxrpc) will always be directed to the RpcServlet. The RpcServlet fills a structure with basic information about the server. It may answer with something like
qx.core.ServerSettings = {serverPathPrefix: 'http://server/app', ...}
and this is used by the makeServerURL() helper method in the RPC class. You can use this when instantiating an RPC instance:
var rpc = new qx.io.remote.Rpc( qx.io.remote.Rpc.makeServerURL(), "my.package.MyService" );
This way, you don't need to hardcode the URL of the service. Your client code will work without modifications, no matter what the name of your application is or where it is deployed. By generating absolute URLs you don't have to worry about moving around web pages and scripts in the directory structure, which is a common shortcoming of relative URLs. The auto-configration feature is also convenient if you need to embed a session id into the URL.
Subclassing RpcServlet
It can be useful to create your own version of qooxdoo's RpcServlet. Some of the benefits of subclassing it are:
- Custom object conversion: By creating your own subclass, you can provide code for custom conversion of objects. This is especially useful for classes that don't have a default constructor.
- Detailed server logging: You can hook your own code into the method calling mechanism, e.g. to provide detailed failure logging (the JavaScript side only receives rather generic errors).
- Property filtering: For methods that return JavaBeans, you can filter the properties that should be sent to the client. This can save a lot of bandwidth without having to completely wrap the result in a custom object.
- Class hinting: For security reasons, the class hinting mechanism isn't active by default (otherwise, client code could instantiate arbitrary server classes). By overriding a method, you can enable it on a case-by-case basis.
The following example code shows how all of this can be done:
package my.package; import java.lang.reflect.InvocationTargetException; import java.util.Calendar; import java.util.Map; import net.sf.qooxdoo.rpc.RpcServlet; import net.sf.qooxdoo.rpc.RemoteCallUtils; import org.json.JSONArray; public class MyRpcServlet extends RpcServlet { protected RemoteCallUtils getRemoteCallUtils() { return new RemoteCallUtils() { // log exceptions by overriding callCompatibleMethod protected Object callCompatibleMethod(Object instance, String methodName, JSONArray parameters) throws Exception { try { return super.callCompatibleMethod(instance, methodName, parameters); } catch (Exception exc) { exc.printStackTrace(); throw exc; } } // influence object conversion public Object toJava(Object obj, Class targetType) { // insert custom conversion to Java here // (default: call super method) return super.toJava(obj, targetType); } public Object fromJava(Object obj) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { // use Dates instead of Calendars (so that the // client code receives native JavaScript dates) if (obj instanceof Calendar) { return super.fromJava(((Calendar) obj).getTime()); } return super.fromJava(obj); } // filter unwanted bean properties protected Map filter(Object obj, Map map) { if (obj instanceof Date) { map.remove("timezoneOffset"); } return super.filter(obj, map); } // class hinting protected Class resolveClassHint(String requestedTypeName, Class targetType) throws Exception { // allow class hinting in some cases // (useful for methods that expect a superclass // of SubClassA and SubClassB) if (requestedTypeName.equals("my.package.SubClassA") || requestedTypeName.equals("my.package.SubClassB")) { return Class.forName(requestedTypeName); } else { return super.resolveClassHint(requestedTypeName, targetType); } } }; } }
To make use of class hinting on the client side, you have to send objects with a class attribute:
rpc.callAsync(handler, "testMethod", {"class": "my.package.SubClassA", property1: 123, property2: 456, /* ... */ });
Please note that class is a reserved word in JavaScript, so you have to enclose it in quotes.
