This chapter describes the details of how the Caché Java binding projects Caché classes as Java classes.
The Caché Java binding takes a class defined in a Caché database and creates a corresponding Java class from it. This generated class provides remote access to a Caché class from Java.
Similarly, the Caché EJB binding projects a class defined within Caché as an EJB Entity Bean. For the most part, the details Caché to Java projection is identical for both the Java binding and EJB binding.
The basic process for creating a projected Java class is:
  1. Add a projection entity to a Caché class definition.
  2. Compile the Caché class, which generates the Java class.
Adding a Projection Entity to a Caché Class
To create a Java projection of a Caché class, specify that the Caché class automatically creates a Java class whenever it is compiled. To do this, add a projection definition to the Caché class. This projection definition specifies that the Class Compiler performs the additional operation of generating Java code for this class whenever the class is compiled.
The process is:
  1. In the Caché Studio, establish a connection to the class' namespace and open the class.
  2. Add a Java projection definition to the class using the Caché Studio's New Projection Wizard: Invoke the New Projection choice from the Add submenu in the Class menu.
  3. After choosing a name for the projection, specify its type as %Projection.Java .
  4. The ROOTDIR parameter, if present, specifies the directory under which the generated Java class will appear. If you leave ROOTDIR empty (which is preferred), your Java classes will be created under the default directory specified by your Caché (using the Java tab of the Configuration Manager).
  5. Compile the Caché class. This generates the Java projection of the class, whether compilation occurs in the Studio or at the Caché command line (in the Terminal).
At this point, you have added a line of code to your class definition similar to the following:
Projection PrjName As %Projection.Java;
where PrjName is the name of the projection.
You can now compile the Java class using either the javac command or some other tool.
Whenever you modify and compile the Caché, its projection automatically updates the projected Java class.
Java Projection Details
The following sections describe the details of how the various aspects of a Caché class definition are projected to Java.
Entity Names
A Caché identifier, such as class or method name, is usually projected to a corresponding Java identifier with the same name.
Class Names
All class names are unchanged. All Caché packages become Java packages; the “%” characters within a package name are translated to “_”. Note that, in your code, the Caché and Java package names must match each other exactly.
The only exception is the %Library package, it becomes “com.intersys.objects”. Hence, the %Library.Persistent class would be mapped as “com.intersys.objects.Persistent”. This is why an exported Java file includes the statement
import com.intersys.objects.*;
which includes all the classes in the Caché %Library package.
Property Names
You can refer directly to Caché properties. To conform to Java property-handling style, the projection of each Caché property includes two accessor methods: getProp and setProp, where “Prop” is the name of the projected property. If the property name starts with “%”, it is replaced by “sys_”. Hence, the projection of the Color property would include getColor and setColor methods; the projection of a %Concurrency property would be get_Concurrency and set_Concurrency methods.
Method Names
Typically, method names are mapped directly, without changes. Exceptions are:
Formal Variable Names
If a variable within a method formal list starts with “%,” it is replaced by “_”. If the name is a Java reserved word, “_” is prepended to the name.
Classes
The projections of Caché classes receive names as Java classes in accordance with the naming conventions listed above. The type of a class (such as persistent or serial) determines its corresponding Java superclass. For example, persistent classes have corresponding Java classes derived from the Java Persistent class.
Packages
In general, the Caché package name for a class is used as its Java package name. If a Caché class defines a class parameter, JAVAPACKAGE, then the Java Generator uses the parameter value for a package name.
Data Types
Caché uses various literal data types (such as strings or numbers) for properties, method return types, and method arguments. Every Caché data type has an associated client data type; this client data type specifies the Java class to which a variable is mapped. Hence, using its client data type, every Caché data type is represented using an appropriate Java object such as Integer or String.
Regardless of a property's type, if it has unset value, then Java represents it using the null keyword. For instance, suppose you create a new object with an age property that is of type Integer; prior to setting this property's value, invoking the getAge method returns null.
The Java object that represents a Caché data type is determined via the data type class' CLIENTDATATYPE keyword value. The following table describes this correspondence:
Client Data Type to Java Correspondence
ClientDataType Java Class (and notes, if any) Java Package Holder Class
BINARY byteArray null ByteArrayHolder
BOOLEAN Boolean java.lang BooleanHolder
CURRENCY BigDecimal java.math BigDecimalHolder
DATE Date java.sql DateHolder
DOUBLE Double java.lang DoubleHolder
ID Id (a Caché-provided class that represents an Object ID within an extent) com.intersys.objects IdHolder
INT Integer java.lang IntegerHolder
LIST SysList (A Java implementation of Caché $List structure) com.intersys.objects SysListHolder
LONG Long java.lang LongHolder
LONG VARCHAR String java.lang StringHolder
NUMERIC BigDecimal java.math BigDecimalHolder
OID Oid (a Caché-provided class that represents a complete Object ID) com.intersys.objects OidHolder
STATUS StatusCode (a Caché-provided class that represents the status) com.intersys.objects StatusCodeHolder
TIME Time java.sql TimeHolder
TIMESTAMP TimeStamp java.sql TimeStampHolder
VARCHAR String java.lang StringHolder
Void void null null
Object-valued types (references to other object instances) are represented using the corresponding Java class. Certain Caché objects are treated as special cases; for instance, streams are mapped to the Java stream object and collections are projected as collection objects, a class created by InterSystems for Java client use.
If a method argument is passed by reference then a “holder” class is used, such as IntegerHolder or StringHolder.
Methods
Methods of Caché classes are projected to Java as stub methods of the corresponding Java classes. Instance methods are projected as Java instance methods; class methods are projected as Java static methods. When called on the client, a method invokes the actual method implementation on the Caché server.
If a method signature includes arguments with default values, Caché generates multiple versions of the method with different numbers of arguments to simulate default argument values within Java.
For example, suppose you define a simple Caché class with one method as follows:
Class MyApp.Simple Extends %RegisteredObject
{
Method LookupName(id As %String) As %String
{
    // lookup a name using embedded SQL
    Set name = ""
    &sql(SELECT Name INTO :name FROM Person WHERE ID = :id)
    Quit name
    }
}
The resulting projected Java class would look something like:
public class Simple extends Object {
    //...
    public String LookupName(String id) throws CacheException {
        // generated code to invoke method remotely...
        // ...
        return typedvalue;
    }
}
When a projected method is invoked from Java, the Java client first synchronizes the server object cache, then invokes the method on the Caché server, and, finally, returns the resulting value (if any). If any method arguments are specified as call-by-reference then their value is updated as well.
System Methods
In addition to any methods defined by a Caché class, the projected Java class includes a number of automatically generated system methods to perform various services:
_close Shuts down an object on the server from the client (by invoking the object's %Close method).
_open For persistent objects, open an instance of object stored within the database using the instance's OID as a handle.
_openId For persistent objects, open an instance of object stored within the database using the instance's class ID value as a handle.
Properties
You can refer to each of a projection's properties using its two accessor methods: get<Property> method to get its value and a set<Property> method to set its value.
The values for literal properties (such as strings or integers) are represented using the appropriate Java data type classes (such as String or Integer).
The values for object-valued properties are represented using the appropriate projected Java class. In addition to the get and set methods, an object-valued property has additional methods that provide access to the persistent object ID for the object: idset<PropertyName> and idget<PropertyName>.
For example, suppose you have defined a persistent class within Caché containing two properties, one literal and the other object-valued:
Class MyApp.Student Extends %Persistent [ClassType = persistent]
{

/// Student's name
Property Name As %String;

/// Reference to a school object
Property School As School;
}
The Java representation of MyApp.Student contains get and set accessors for both the Name and School properties. In addition, it provides accessors for the Object Id for the referenced School object.
public class Student extends Persistent {
    // ...

    public String getName() throws CacheException {
        // implementation...
    }

    public void setName(String value) throws CacheException {
        // implementation...
    }

    public School getSchool() throws CacheException {
        // implementation...
    }

    public void setSchool(School value) throws CacheException {
        // implementation...
    }

    public Id idgetSchool() throws CacheException {
        // implementation...
    }

    public void idsetSchool(Id value) throws CacheException {
        // implementation...
    }
}
Property Caching
When a projected Java object is instantiated within Java, it fetches a copy of its property values from the Caché server and copies them into a local Java-side cache. Subsequent access to the object's property values are made against this cache, reducing the number of messages sent to and from the server. Caché automatically manages this local cache and ensures that it is synchronized with the corresponding object state on the Caché server.
Note that property values for which you have defined a get or set method within your Caché class definition (such as for a property whose value depends on other properties) are not stored within the local cache. Instead, when you access such properties the corresponding accessor method is invoked on the Caché server. As this can entail higher network traffic, exercise care when using such properties in a Java environment.
Collections
Caché supports two kinds of collections: lists and arrays. These are two different kinds of groupings of elements of a single type. A list maintains an order to its elements, while an array is simply a set of name-value pairs. More formally:
Operations on a Java client usually assume the collection's prior existence.
List Methods
Lists consist of a sequence of individual items, each of which consists of a key (an integer specifying a position in the list) and an element value (specifying its value). A list maintains its keys so that the largest key value is equivalent to the number of items in the list. You can add items either at the end of the list or in the middle (which increments the key value for all the subsequent items).
Each list method operates on either the list as a whole or an individual item.
Supported operations include:
Navigational actions include:
Array Methods
Arrays consist of an unordered group of items, each of which consists of a key (a string identifying the item) and an element value (specifying its value).
Note:
Arrays differ from lists because they have no inherent ordering. Hence, if you attempt to add an item with a key name that is already in use, the _setAt method overwrites the old value of the item's element. This differs from a list, which increments the key value for the already-existing item (and any subsequent items).
Each array method operates on either the list as a whole or an individual item.
Supported actions for array elements include:
Navigational actions include:
Streams
Caché allows you to create properties that hold large sequences of characters, either in character or binary format; these sequences are known as “streams.” Character streams are long sequences of text, such as the contents of a free-form text field in a data entry screen; binary streams are usually image or sound files, and are akin to BLOBs (binary large objects) in other database systems.
When you are writing to or reading from a stream, Caché monitors your “position” within the stream, so that you can move backwards or forwards.
The basic tasks involving streams are:
The process for using a stream in Java is:
When creating a Java client, define and initialize a variable of the appropriate type—either GlobalBinaryStream or GlobalCharacterStream. For instance, if you are using a character stream, define a variable such as:
GlobalCharacterStream localnotes = null; 
You can then read content from an instantiated class' stream:
localnotes = myTest.getNotes();
Once you have a local copy of the stream, you can read from it
IntegerHolder len = new IntegerHolder( new Integer(8)) ;
while (len.value.intValue() != 0 ) {
System.out.println(  localnotes._read( len)  );
} ;
The _read method's argument is an integer hold specifying how many characters to read and its return value is the characters that it reads; it also places the number of characters successfully read (whether the number specified or fewer) in the integer hold variable that was its argument.
Class Queries
Caché allows you to define queries as part of a class. These queries are then compiled and can be invoked at run time.
To invoke a pre–defined query, use the CacheQuery (no accent on the “e”) class:
  1. Establish a connection to a Caché server (see the next chapter for details on this process).
  2. Create an instance of a CacheQuery object using code such as:
    myQuery = new CacheQuery(factory, classname, queryname);
    where factory specifies the existing connection, classname specifies the class on which the query is to be run, and queryname specifies the name of the pre-defined query that is part of the class.
  3. Once you have instantiated the CacheQuery object, you can invoke the pre-defined query:
    java.sql.Result ResultSet = myQuery.execute(parm1);
    This method accepts up to three arguments, which it passes directly to the query; if the query accepts four or more arguments, you can pass in an array of argument values. The method returns an instance of a standard JDBC ResultSet object.
Dynamic Queries
The Java binding also supports dynamic queries through JDBC. To run this type of query:
  1. Connect to Caché using the JDBC Connection object, with code such as:
    String user = "_SYSTEM";
    String password = "SYS";
    String url = "jdbc:Cache://127.0.0.1:1972/SAMPLES";
    //...
    Class.forName ("com.intersys.jdbc.CacheDriver");
    Connection conn = DriverManager.getConnection(url, user, password);
    This code initializes variables, loads the JDBC driver, and establishes the connection to the Caché server. For more details on this process, see the JDBC Connections section.
  2. Specify the query that you wish to run. This involves creating the statement object (using the newly created Connection object) and establishing the statement's content:
    Statement stmt = conn.createStatement();
    java.sql.ResultSet rs = stmt.executeQuery(stQuery);
    
    In the above code, the stQuery variable contains a valid SQL query.
  3. Execute the statement.
    ResultSetMetaData rsmd = rs.getMetaData();
  4. Use the Java ResultSet object to manipulate the returned data.
    int colnum = rsmd.getColumnCount();