Chapter 46. JavaRPC

Table of Contents

1. <javarpc>
2. Attributes
2.1. Read-only properties
3. Events
4. Methods
4.1. load()
4.2. unload()
5. Security
6. Type Mapping
6.1. Type mapping of parameters from JavaScript to Java
6.2. Type mapping of return types from Java to JavaScript

JavaRPC is a feature that allows server-side Java objects and methods to be accessed from a client application. The <javarpc> element is used to declare a JavaRPC object in LZX. JavaRPC is part of the OpenLaszlo RPC family and shares similar APIs with SOAP and XML-RPC. See the RPC chapter for details.

1. <javarpc>

[Warning]
[Proxied]

Proxied only: The features described in this section only work in applications compiled to Proxied. They do not work in applications compiled to other runtimes.

Declare a javarpc element to create a JavaRPC object in LZX. The remoteclassname attribute specifies what class javarpc represents. To use a class, place it in WEB-INF/classes or, if it exists in a jar, in WEB-INF lib. This will ensure that the class is accessible to the OpenLaszlo Server.

<javarpc remoteclassname="..."
     attributename="..."
     scope="[session|webapp|none]"
     loadoption="[loadcreate|loadonly|create]"
     createargs="..."
     objectreturntype="[pojo|javabean]"
     autoload="[true|false]"
     secure="[true|false]"
     secureport="...">

2. Attributes

remoteclassname: (String) the remote java class to instantiate in the server, or if scope is 'none', the static stub to return to the client associated to the remoteclassname. This is a required attribute.

attributename: (String) the key to use for the server-side object (see scope attribute). Attributename (or name) is required if scope is 'session' or 'webapp'. Defaults to the name attribute of this object.

scope: (String) one of 'session', 'webapp', or 'none'. Session scope means that the server object will be saved in a session attribute (see javax.servlet.http.HttpSession). If scope is webapp, the server object is saved in a web application context (see javax.servlet.ServletContext). For session and webapp scopes, client-side remote methods will always invoke the same saved server objects. The objects are saved in a java.util.Map that is placed in an attribute called "__lzobj". The map key for the object is the attributename of the client-side javarpc object. If scope is none, no object is saved in the server and only public static methods are defined in the client-side javarpc object. This is a required attribute.

loadoption: (String) one of 'loadcreate', 'loadonly', or 'create'. Loadcreate tries to load javarpc object if it exists in the server, else it creates it before loading. 'Loadonly' will only load object if it exists, else an error is returned. 'Create' will always create the object in the server. Default is 'loadcreate'.

createargs: (Array) valid only when loadoption='loadcreate' or loadoption='create'. The array consists of parameters to construct server-side object, for example [1, 'mystring', 1.45] would instantiate an object using a constructor that takes an integer, a string, and a double. Default is null.

objectreturntype: (String) one of 'pojo' or 'javabean'. If an object is returned from the server, 'pojo' will return only the public member values of that object and 'javabean' will return member values for members that have corresponding getters. See examples/javarpc/returnpojo.lzx and examples/javarpc/returnjavabean.lzx for usage example.

autoload: (Boolean) if true, calls to load client proxy during init stage. If false, the proxy must be loaded using the load() method. Default is true.

secure: (Boolean) if true, creates a secure HTTPS connection between the client and OpenLaszlo Server. Also see secureport below. Default is false.

secureport: (Number) valid only when secure attribute is set to true. The secure port to use. There is no client-side default. Most servers use port 443 as the default HTTPS port.

2.1. Read-only properties

proxy: (Object) this is the object containing function stubs. It is created by calling load() (which happens during init if autoload is true). If scope is session or webapp, proxy contains all public methods described in the server class (see remoteclassname attribute). If scope is none, proxy contains all public static methods described in the server class. Note that proxy is not defined until the onload event is sent, thus function stubs cannot be invoked until onload. Each function stub requires two arguments: an array of parameters and delegate. You can unload the proxy (i.e., set to null) by calling the unload() method. Calling unload will also remove the server-side object if scope is session or webapp. See the proxy section in the RPC chapter for details.

3. Events

Note: event handler methods must be declared in the body of <javarpc>. Attribute event handlers will not work.

onload: this event is triggered when the proxy is returned to the client.

onunload: this event is triggered when the proxy is unloaded from the client.

ondata: this event is triggered when a declared <remotecall> doesn't handle its ondata events. See the <remotecall> section for details.

onerror: this event is triggered if there was a problem loading or unloading the stub, or if a declared <remotecall> didn't handle its onerror event. See the <remotecall> section in the RPC chapter for details.

4. Methods

4.1. load()

Load() is responsible for setting up the proxy property. When loading a session scoped or webapp scoped object, a tuple of arguments may be passed in to instantiate the server-side java object using the createargs attribute. Createargs is an array of values and must match the server-side parameter signature of the constructor. When the call returns, an onload event is sent and the proxy will contain function stubs that mirror the public methods in the Java class specified.

Example 46.1. Loading an object

<canvas debug="true" width="100%">

    <!-- The security describes what classes are accessible or are allowed to be
         instantiated in the server. See the security section in this chapter
         for more information. -->
    <security>
        <allow>
            <pattern>^examples\.ConstructExample</pattern>
        </allow>
    </security>

    <!-- See $LPS_HOME/WEB-INF/classes/ConstructExample.java for java
        source. -->
    <javarpc name="ce" scope="session" autoload="false" remoteclassname="examples.ConstructExample" createargs="[1, 'a string', 1.45]">

        <handler name="onerror" args="err">
            Debug.debug("----------");
            Debug.debug("onerror: %w", err)
        </handler>

        <handler name="onload">
            Debug.debug("----------");
            Debug.debug("constructed with %w", this.createargs);
            Debug.debug("%w", this.proxy);
        </handler>

    </javarpc>
    
    <simplelayout inset="10" spacing="5"/>

    <button x="10" text="construct">
        <handler name="onclick">
            canvas.ce.load();
        </handler>
    </button>

    <button x="10" text="getinfo">
        <handler name="onclick">
            this.getInfo.invoke();
        </handler>

        <remotecall funcname="getInfo" remotecontext="$once{canvas.ce}">
            <handler name="ondata" args="res">
                Debug.debug("----------");
                Debug.debug("method: %w", this.name)
                Debug.debug("return type: %w", typeof(res))
                Debug.debug("return value: %w", res);
            </handler>
        </remotecall>
    </button>
</canvas>

The Java source for the previous example can be found in the $LPS_HOME/WEB-INF/classes/examples directory. Here's what it looks like:

package examples;

public class ConstructExample {

    int mInt = 0;
    String mString = "";
    double mDouble = 0.0;

    public ConstructExample(int i) {
        mInt = i;
    }

    public ConstructExample(int i, String s, double d) {
        mInt = i;
        mString = s;
        mDouble = d;
    }

    public String getInfo() {
        return "int: " + mInt + "\n"
            + "string: " + mString + "\n"
            + "double: " + mDouble + "\n";
    }
}

See RPC chapter for information on remotecall and other details about the load() method.

4.2. unload()

This method unloads the proxy from the RPC object and sets it to null. Also, the associated java object is removed from the server-side map. When the call returns, an onunload event is sent.

unload() implementation in javarpc.lzx

Example 46.2. Unloading proxy from the RPC object

<canvas debug="true" height="300" width="100%">
 
    <debug x="100" y="10" height="275"/>

    <security>
        <allow>
            <pattern>^examples\.ConstructExample</pattern>
        </allow>
    </security>

    <!-- See $LPS_HOME/WEB-INF/classes/ConstructExample.java for java
        source. -->
    <javarpc name="ce" scope="session" remoteclassname="examples.ConstructExample" createargs="[1, 'a string', 1.45]">

        <handler name="onerror" args="err">
            Debug.debug("onerror: %w", err)
        </handler>

        <handler name="onload">
            Debug.debug("loaded proxy: %w", this.proxy);
        </handler>

        <handler name="onunload">
            Debug.debug("unloaded proxy: %w", this.proxy);
        </handler>

    </javarpc>
    
    <simplelayout spacing="10"/>

    <button text="load" onclick="canvas.ce.load()"/>

    <button text="unload" onclick="canvas.ce.unload()"/>

    <button text="proxy" onclick="Debug.debug('proxy is %w', canvas.ce.proxy)"/>

</canvas>

5. Security

Java classes used in an application must be declared in a security element. Classes not defined in a security element are not allowed to be accessed or instantiated. The format of the security element looks like:

<security>
    <allow>
        <pattern>CLASS1</pattern>
        <pattern>CLASS2</pattern>
        ...
        <pattern>CLASSN</pattern>
    </allow>
</security>

Each <pattern> is a regular expression.

Example 46.3. Allow classes that start with org.openlaszlo

<security>
    <allow>
        <pattern>^org\.openlaszlo</pattern>
    </allow>
</security>

A javarpc object who's class is not declared in a security tag will result in a load error.

6. Type Mapping

This section describes how types are mapped from JavaScript function stub parameters to Java method parameter and from Java return type to JavaScript return type.

6.1. Type mapping of parameters from JavaScript to Java

JavaScript data type Parameter types expected by java method
Number (int) int
Number (double)* double
lz.rpc.DoubleWrapper double
Boolean boolean
Array Vector
Object Hashtable

* Any floating point number with a zero decimal value is considered to be an integer, i.e., 1.0 is really 1. Use lz.rpc.DoubleWrapper to ensure a number is considered a double. For example:

    // assume myrpc is a javarpc object and myrpc.proxy.myMethod is a function
    // that expects a single double as a parameter
    var mydouble = new lz.rpc.DoubleWrapper(1.0);
    myrpc.proxy.myMethod([ mydouble ], new LzDelegate(...));

lz.rpc can be found in $LPS_HOME/lps/components/rpc/library/rpc.js.

Example 46.4. Passing different parameter types

<canvas debug="true" height="300" width="100%">

    <security>
        <allow>
            <pattern>^examples\.TypesExample</pattern>
        </allow>
    </security>

    <!-- See WEB-INF/classes/TypesExample.java for java source. -->
    <javarpc name="types_example_rpc" scope="none" remoteclassname="examples.TypesExample">

        <handler name="onload">
            // Set buttons visible only after JavaRPC object loads
            canvas.buttons.setAttribute('visible', true);
        </handler>

        <handler name="ondata" args="res">
            Debug.debug('(types ondata) response is: %w', res);
        </handler>

        <handler name="onerror" args="errmsg">
            Debug.debug('(types onerror) error: %w', errmsg);
        </handler>

        <!-- Declaratively pass an integer. -->
        <remotecall funcname="passInteger">
            <param value="42"/>
        </remotecall>

        <!-- Declaratively pass a double. Note that we name this function pd1
             because we have multiple remotecall declarations that call
             passDouble but with different parameters. -->
        <remotecall name="pd1" funcname="passDouble">
            <param value="42.1"/>
        </remotecall>

        <!-- Declaratively pass a double with 0 decimal. The 0 decimal will
             truncate and the number will become an integer type when it reaches
             the server. This call will fail. -->
        <remotecall name="pd2" funcname="passDouble">
            <param value="42.0"/>
        </remotecall>

        <!-- Declaratively pass a double with 0 decimal. Wrapping the double in
             DoubleWrapper will ensure the value will remain a double when
             reaching the server. -->
        <remotecall name="pd3" funcname="passDouble">
            <param> 
                <method name="getValue">
                    return new LzRPC.DoubleWrapper(42.0);
                </method>
            </param>
        </remotecall>

    </javarpc>

    
    <view name="buttons" visible="false" layout="spacing: 10">

        <button text="pass integer" onclick="types_example_rpc.passInteger.invoke()"/>

        <button text="pass double" onclick="types_example_rpc.pd1.invoke()"/>

        <button text="pass double (will fail)" onclick="types_example_rpc.pd2.invoke()"/>

        <button text="pass double w/LzRPC.DoubleWrapper" onclick="types_example_rpc.pd3.invoke()"/>

        <button text="pass boolean" onclick="this.passBoolean.invoke()">
            <!-- This is a way to declare a remotecall closer to where it's being
                 used. The remotecontext must be set. -->
            <remotecall funcname="passBoolean" remotecontext="$once{ types_example_rpc }">
                <param value="true"/>
            </remotecall>
        </button>

        <button text="pass array" onclick="this.passArray.invoke()">
            <remotecall name="passArray" funcname="passClientArray" remotecontext="$once{ types_example_rpc }">
                <param value="[1, 'a string', 4.5, false]"/>
            </remotecall>
        </button>

        <button text="pass hash" onclick="this.passObject.invoke()">
            <remotecall name="passObject" funcname="passClientObject" remotecontext="$once{ types_example_rpc }">
                <param value="{ a: 1, b: 3.14159, c: 'a string value', d: true}">
                </param>
            </remotecall>
        </button>

    </view>

</canvas>

The java source code used by the previous example can be found in $LPS_HOME/WEB-INF/classes/examples and looks like:

package examples;

import java.util.Vector;
import java.util.Hashtable;

public class TypesExample {

    public static String passInteger(int i) {
        return "got integer parameter: " + i;
    }

    public static String passDouble(double d) {
        return "got double parameter: " + d;
    }

    public static String passBoolean(boolean b) {
        return "got boolean parameter: " + b;
    }

    public static String passClientArray(Vector v) {
        return "got vector parameter: " + v;
    }

    public static String passClientObject(Hashtable t) {
        return "got hashtable parameter: " + t;
    }

}

6.2. Type mapping of return types from Java to JavaScript

Server-side Java method return type Client-side JavaScript function stub return type
int/java.lang.Integer Number+
short/java.lang.Short Number+
long/java.lang.Long Number+
float/java.lang.Float Number+
double/java.lang.Double Number+
byte/java.lang.Byte Number+
boolean/java.lang.Boolean Boolean
char/java.lang.Character String
java.lang.String String
"Array"/java.util.List* Array
java.util.Map/java.lang.Object* Object

+ From "JavaScript The Definitive Guide" - O'Reilly. 

In JavaScript all numbers are floating-point numbers.

JavaScript uses the standard 8 byte IEEE floating-point numeric format, which
means the range is from:

+/- 1.7976931348623157x10^308 - very large, and +/- 5x10^-324 - very small.

As JavaScript uses floating-point numbers the accuracy is only assured for
integers between: -9,007,199,254,740,992 (-2^53) and 9,007,199,254,740,992
(2^53)
* User-defined objects returned only contain public members. Also, the
object isn't saved anywhere in the server.

Example 46.5. Java to JavaScript

<canvas debug="true" height="450" width="100%">

    <security>
        <allow>
            <pattern>^examples\.ReturnTypeExample</pattern>
        </allow>
    </security>

    <!-- See WEB-INF/classes/ReturnTypeExample.java for java source. -->
    <javarpc name="return_type_example_rpc" scope="none" remoteclassname="examples.ReturnTypeExample">
        <handler name="onload">
            // Set buttons visible only after JavaRPC object loads
            canvas.buttons.setAttribute('visible', true);
        </handler>
        <handler name="ondata" args="res">
            Debug.debug('(return type ondata) value: %w, type: %w', res, typeof(res));
        </handler>
        <handler name="onerror" args="errmsg">
            Debug.debug('(return type onerror) error: %w', errmsg);
        </handler>
    </javarpc>

    
    <view x="10" y="10" name="buttons" visible="false" layout="spacing: 5">

        <view layout="axis: x; spacing: 2">

            <button text="integer" onclick="this.returnInteger.invoke()">
                <remotecall funcname="returnInteger" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="integer object" onclick="this.returnIntegerObject.invoke()">
                <remotecall funcname="returnIntegerObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="short" onclick="this.returnShort.invoke()">
                <remotecall funcname="returnShort" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="short object" onclick="this.returnShortObject.invoke()">
                <remotecall funcname="returnShortObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="long" onclick="this.returnLong.invoke()">
                <remotecall funcname="returnLong" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="long object" onclick="this.returnLongObject.invoke()">
                <remotecall funcname="returnLongObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="float" onclick="this.returnFloat.invoke()">
                <remotecall funcname="returnFloat" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="float object" onclick="this.returnFloatObject.invoke()">
                <remotecall funcname="returnFloatObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="double" onclick="this.returnDouble.invoke()">
                <remotecall funcname="returnDouble" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="double object" onclick="this.returnDoubleObject.invoke()">
                <remotecall funcname="returnDoubleObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

        </view>

        <view layout="axis: x; spacing: 2">

            <button text="byte" onclick="this.returnByte.invoke()">
                <remotecall funcname="returnByte" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="byte object" onclick="this.returnByteObject.invoke()">
                <remotecall funcname="returnByteObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="boolean" onclick="this.returnBoolean.invoke()">
                <remotecall funcname="returnBoolean" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="boolean object" onclick="this.returnBooleanObject.invoke()">
                <remotecall funcname="returnBooleanObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="character" onclick="this.returnCharacter.invoke()">
                <remotecall funcname="returnCharacter" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="character object" onclick="this.returnCharacterObject.invoke()">
                <remotecall funcname="returnCharacterObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="string" onclick="this.returnString.invoke()">
                <remotecall funcname="returnString" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="coordinate object" onclick="this.rco.invoke()">
                <remotecall name="rco" funcname="returnCoordinateObject" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

        </view>

        <view layout="axis: x; spacing: 2">

            <button text="integer array" onclick="this.returnIntegerArray.invoke()">
                <remotecall funcname="returnIntegerArray" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="string array" onclick="this.returnStringArray.invoke()">
                <remotecall funcname="returnStringArray" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="coordinate object array" onclick="this.returnCoordinateObjectArray.invoke()">
                <remotecall funcname="returnCoordinateObjectArray" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="integer list" onclick="this.returnIntegerList.invoke()">
                <remotecall funcname="returnIntegerList" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="integer map" onclick="this.rim.invoke()">
                <remotecall name="rim" funcname="returnIntegerMap" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

        </view>

        <view layout="axis: x; spacing: 2">

            <button text="coordinate object list" onclick="this.returnCoordinateObjectList.invoke()">
                <remotecall funcname="returnCoordinateObjectList" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

            <button text="coordinate object map" onclick="this.rcom.invoke()">
                <remotecall name="rcom" funcname="returnCoordinateObjectMap" remotecontext="$once{ return_type_example_rpc }"/>
            </button>

        </view>

    </view>

</canvas>

The Java source code used by the previous example looks like:

package examples;

import java.util.List;
import java.util.Vector;
import java.util.Map;
import java.util.HashMap;

public class ReturnTypeExample {

    public static int returnInteger() {
        return 1;
    }

    public static Integer returnIntegerObject() {
        return new Integer(2);
    }

    public static short returnShort() {
        return 3;
    }

    public static Short returnShortObject() {
        return new Short((short)4);
    }

    public static long returnLong() {
        return 5;
    }

    public static Long returnLongObject() {
        return new Long(6);
    }

    public static float returnFloat() {
        return 7;
    }

    public static Float returnFloatObject() {
        return new Float(8);
    }

    public static double returnDouble() {
        return 3.14159;
    }

    public static Double returnDoubleObject() {
        return new Double(3.14159);
    }

    public static byte returnByte() {
        return (byte)11;
    }

    public static Byte returnByteObject() {
        return new Byte((byte)12);
    }

    public static boolean returnBoolean() {
        return true;
    }

    public static Boolean returnBooleanObject() {
        return new Boolean(false);
    }

    public static char returnCharacter() {
        return 'a';
    }

    public static Character returnCharacterObject() {
        return new Character('b');
    }

    public static String returnString() {
        return "returing a string";
    }

    public static Coordinate returnCoordinateObject() {
        return new Coordinate(4,2);
    }

    public static int[] returnIntegerArray() {
        int[] intarr = { 1, 2, 3, 4, 5 };
        return intarr;
    }

    public static String[] returnStringArray() {
        String[] strarr = { "one", "two", "three", "four", "five" };
        return strarr;
    }

    public static Coordinate[] returnCoordinateObjectArray() {
        Coordinate[] coarr =  { new Coordinate(1,1), 
                                new Coordinate(2,2),
                                new Coordinate(3,3),
                                new Coordinate(4,4),
                                new Coordinate(5,5) };
        return coarr;
    }

    public static List returnIntegerList() {
        List list = new Vector();
        list.add(new Integer(1));
        list.add(new Integer(2));
        list.add(new Integer(3));
        list.add(new Integer(4));
        list.add(new Integer(5));
        return list;
    }

    public static Map returnIntegerMap() {
        Map map = new HashMap();
        map.put("one", new Integer(1));
        map.put("two", new Integer(2));
        map.put("three", new Integer(3));
        map.put("four", new Integer(4));
        map.put("five", new Integer(5));
        return map;
    }

    public static List returnCoordinateObjectList() {
        List list = new Vector();
        list.add(new Coordinate(1,1));
        list.add(new Coordinate(2,2));
        list.add(new Coordinate(3,3));
        list.add(new Coordinate(4,4));
        list.add(new Coordinate(5,5));
        return list;
    }

    public static Map returnCoordinateObjectMap() {
        Map map = new HashMap();
        map.put("one", new Coordinate(1,1));
        map.put("two", new Coordinate(2,3));
        map.put("three", new Coordinate(5,8));
        map.put("four", new Coordinate(13,21));
        map.put("five", new Coordinate(34,55));
        return map;
    }

    static public class Coordinate {
        public int x;
        public int y;

        public Coordinate(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public String toString() {
            return "x: " + this.x + ", y: " + this.y;
        }
    }
}