Using Dynamic Object Gateways
This chapter describes how to implement Object Gateways using dynamic proxy objects. In previous releases, static proxies had to be generated separately, compiled, and stored in the class library just like any other ObjectScript class (see “Importing Java Classes” in the previous chapter).
Dynamic proxy objects do not require a separate import process before the business operation can start. The business operation simply creates and uses them as needed during runtime.
Introducing Dynamic Gateways — provides an overview of dynamic proxies.
Creating and Using a Dynamic Object Gateway — describes how to use dynamic proxies and gives specific usage examples.
Array Access — describes how Java arrays are implemented as dynamic proxies.
Gateway Reentrancy — describes how to ObjectScript and Java code can run safely using the same connection and context.
Example Classes — contains detailed information on the classes used as examples in this chapter.
Introducing Dynamic Gateways
All versions of the Object Gateway can instantiate an external Java object and manipulate it as if it were a native InterSystems IRIS object. This is accomplished by creating a proxy object that provides all of the same methods and properties as the corresponding Java object. Any call on the proxy object triggers the corresponding method or property of the Java object. There are currently two kinds of proxy objects:
Static proxies are standard ObjectScript classes, which must be individually generated and compiled before they can be used. If a new version of the Java class changes the interface in any way, the corresponding proxy class must be regenerated and recompiled to match it.
Dynamic proxies are created at runtime by introspecting the current version of the Java class. All dynamic proxies are instances of %Net.Remote.Object. Although each Object instance appears to have the same methods and properties as the corresponding Java object, it actually uses an internal map of the interface to simulate these features.
In use, static and dynamic proxies are identical except for the way they are instantiated. For example:
// Static proxy: call %New() on a pregenerated class set newFruit = ##class(Fruit).%New() // Dynamic proxy: create an Object instance and specify the class to introspect set newFruit = ##class(%Net.Remote.Object).%New(gateway,"Fruit")
When the code is used in a business production, the only noticeable difference is that dynamic gateways eliminate the import process.
Creating and Using a Dynamic Object Gateway
This section describes the complete procedure for creating and running an Object Gateway that uses dynamic proxies:
Get the server definition and test the server — the server definition is required for methods that monitor and control the server.
Define the location of your Java classes and create a Gateway connection — connection information must include the path to each .class or .jar file you intend to use.
Create a proxy and call some methods — after they’ve been created, static and dynamic proxies work the same way.
Disconnect and shut down — the server will continue to run until you explicitly stop it.
The Service class provides methods to start, stop, and test the server. Most of these methods require the server definition contained in an ObjectGateway object. In the following example, Service.OpenGateway() returns an ObjectGateway instance defining a server named JavaGate. Service.IsGatewayRunning() uses the information to test the current state of the server, and Service.StartGateway() can start it if necessary.
try { // Get a ObjectGateway instance that defines the JavaGate server set st = ##class(%Net.Remote.Service).OpenGateway("JavaGate",.OG) write !, "Using "_OG.Name_" server definition" // Test to make sure the server is running. If not, start it if ('##class(%Net.Remote.Service).IsGatewayRunning(OG.Server,OG.Port,,.status)) { // Instantiate the server on the host machine set status = ##class(%Net.Remote.Service).StartGateway(OG.Name) } } catch ex { write !,$system.OBJ.DisplayError(ex.AsStatus()) }
Your ObjectScript code should always use try/catch for Object Gateway error handling. When the class running on the host machine encounters an exception, the dynamic proxy will throw %Net.Remote.Exception. In addition to standard status details, the Exception object contains call stack information, which is returned by calling method Exception.StackAsArray(.array). The information may be different between Java and .NET.
Each dynamic proxy will access the corresponding Java object through a Gateway object connected to the server. The Gateway object needs a path to each .class or .jar file containing a class you want to use. In this example, the location of the .jar file containing class Fruit is specified in the last argument to Gateway.%Connect().
// define the location of the file containing the Fruit class set fruitPath = ##class(%ListOfDataTypes).%New() do fruitPath.Insert("C:\Dev\Fruit.jar") // Connect a Gateway instance to server JavaGate on the host machine set GW = ##class(%Net.Remote.Gateway).%New() set st = GW.%Connect(OG.Server, OG.Port, "USER",,fruitPath)
This example only adds one path, but you can insert a separate path for each .class or .jar file you want to use.
In this example, an new instance of Object is created, specifying Fruit as the Java class to introspect. The resulting dynamic proxy will communicate with the Java object through Gateway instance GW (created in the previous example). Once the dynamic proxy is instantiated, it works just like a static proxy.
// Use GW connection to create a proxy for the Fruit class set proxyFruit = ##class(%Net.Remote.Object).%New(GW,"Fruit") write !,proxyFruit.id() write !,"Current fruit preference: ",!," "_proxyFruit.getFruit() do proxyFruit.setFruit("Jujube") write !,"Fruit preference has been reset: ",!," "_proxyFruit.getFruit()
Prints:
This Java class displays your favorite fruit. Current fruit preference: UglyFruit Fruit preference has been reset: Jujube
The connection remains open and the server continues to run until you explicitly disconnect and shut down. In this example, the Gateway.%Disconnect() method breaks the connection, but the server won’t release the port it uses until the call to Service.StopGatewayObject() stops it.
// Disconnect from the server set st = GW.%Disconnect() // Stop the server set st = ##class(%Net.Remote.Service).StopGatewayObject(OG)
Array Access
When a property is declared as an array, it is projected to the proxy as an instance of %Net.Remote.Object. To create a new array, call the Object.%New() method and use array class name syntax in place of the regular class name. Arguments are used as the initial values for the array. Here are some examples:
Array can be initialized by rank or elements:
// by rank set newArray = ##class(%Net.Remote.Object).%New(gateway,"String[2]") // by elements set newArray = ##class(%Net.Remote.Object).%New(gateway,"String[]","A","B","C")
When rank and elements are both specified, rank must match element count.
// both rank and elements specified set newArray = ##class(%Net.Remote.Object).%New(gateway,"int[3]",11,22,33)
Besides supporting all the normal properties and methods available on an array object, Dynamic Gateway supports two special methods for accessing array elements:
%get(index) — object.%get(index)is translated to object[index]
%set(index,value) — object.%set(index,value) is translated to object[index] = value.
For example:
set max = 5 set arrSize = "String["_(max+1)_"]" // "String[6]" set arr = ##class(%Net.Remote.Object).%New(gateway,arrSize) // Populate the array using %set() for idx=0:1:max { set val = $CHAR(65+idx) // $CHAR(65)=A do arr.%set(idx,val) } // Print the array using %get() for idx=0:1:max { set val = arr.%get(idx) write "arr("_idx_")="_val_" " }
Prints:
arr(0)=A arr(1)=B arr(2)=C arr(3)=D arr(4)=E arr(5)=F
Gateway Reentrancy
Reentrancy means that when InterSystems IRIS code sends a request to Java, the Java code can respond with a request of its own using the same physical connection and context as the original request. The following is a typical call sequence:
Starting from the InterSystems IRIS side, a connection is made to Java using a %Net.Remote.Gateway object, allowing us to create proxy objects that can make calls to Java methods.
From a proxy, the Gateway.GatewayContext.getIRIS() method can be called to create an instance of jdbc.IRIS. The IRIS object uses the same connection and context as the proxy, and provides access to all the methods for invoking Native API calls (see “Using the InterSystems Native API for Java”).
The IRIS object can call back into InterSystems IRIS, where it can get and set global values or call classmethods and functions. Making one of these calls puts us back in InterSystems IRIS code again.
From here, we can make another inner call back to Java using the same Gateway connection. Method ##class(%Net.Remote.Gateway).%GetContextGateway() returns the Gateway object for the current context.
Calls back and forth between InterSystems IRIS and Java can go on indefinitely, always using the same connection and context.
The following example gets an IRIS object in Java, and uses it to read a global value from the InterSystems database:
IRIS native = GatewayContext.getIRIS(); // Read the value of global ^GlobalName("value2") if ( native != null ) { String globalVal = native.getString("GlobalName","value2"); }
If the code is not invoked inside a valid Gateway context, the getIRIS() method will return a null object.
This example gets a %Net.Remote.Gateway object for the current context, and uses it to create a proxy for a Java object in the same context:
set gateway = ##class(%Net.Remote.Gateway).%GetContextGateway() if gateway'=$$$NULLOREF { set objFruit = ##class(%Net.Remote.Object).%New(gateway,"Fruit") }
If the context is not valid, the Gateway object will be $$$NULLOREF.
In general, reentrancy support is not compatible with prior versions of the Object Gateway .
The Java Fruit Class
This section lists the Fruit class used by most of the examples in this chapter. This trivial Java class has all the features needed to demonstrate a simple proxy object: static class method id(), and property accessors getFruit() and setFruit().
package demo; public class Fruit { public static String id() { return "This Java class displays your favorite fruit."; } public String fruit; public Fruit () { fruit = "Uglyfruit"; } public void setFruit(String newFruit) { fruit = newFruit; } public String getFruit() { return "My favorite fruit is "+fruit; } public static void main(String []args) { System.out.println("\nGenerating output from Java class Fruit: "); Fruit myFruit = new Fruit (); System.out.println(myFruit.getFruit()); } }