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:
-
Add a projection entity to a Caché class definition.
-
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.
-
In the Caché Studio, establish a connection to the
class' namespace and open the class.
-
Add a Java projection definition to the class using the Caché
Studio's New Projection Wizard: Invoke the
choice
from the
submenu in the
menu.
-
-
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).
-
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.
The following sections describe the details of how the various aspects
of a Caché class definition are projected to Java.
A Caché identifier, such as class or method name, is usually
projected to a corresponding Java identifier with the same name.
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.
import com.intersys.objects.*;
which includes all the classes in the Caché %Library package.
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.
Typically, method names are mapped directly, without changes. Exceptions
are:
-
If the method name starts with
%, this is replaced
by
sys_.
-
If the method name is a Java reserved word,
_ is
prepended to the name.
-
For methods of classes that are part of the %Library package,
the leading
% is replaced with a
_ and the first
letter is converted to lower case.
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.
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.
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.
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
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.
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.
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:
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...
}
}
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.
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:
-
Lists are ordered collections of information, where each list
element is identified by its position (slot) in the list. You can set the
value of a slot's data or insert data at a slot. If you set a new value for
a slot, that value is stored in the list. If you set the value for an already-existing
slot, the new data overwrites the previous data and the slot assignments are
not be modified. If you insert data at an already-existing slot, the new list
item increments the slot number of all subsequent slots; inserting a new item
in the second slot slides the data currently in the second slot to the third
slot, the object currently in the third slot to the fourth slot, and so on.
There are lists of data types and lists of objects.
-
Arrays are unordered collections of information (unlike lists,
which are ordered). An array consists of one or more name-value pairs, where
the name of the element serves as a key and the value is data associated with
that key. There is no syntactic difference between adding a new element and
changing the data contained in an existing element. There are arrays of data
types and arrays of objects.
Operations on a Java client usually assume the collection's prior existence.
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:
-
-
-
-
-
-
-
Counting the number of elements:
_count()
Navigational actions include:
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:
-
-
-
-
-
-
-
Counting the number of elements:
_count()
Navigational actions include:
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:
-
-
-
-
Going to the stream's beginning:
_rewind
-
Check
if the current position is at the end of the stream:
atEnd
-
Getting the size of the stream:
_sizeGet
-
Checking if the stream has content or not:
isNull
-
Erasing the stream's content:
_clear
The process for using a stream in Java is:
When creating a Java client, define and initialize a variable of the
appropriate typeeither 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.
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 predefined query, use the
CacheQuery (no
accent on the
e) class:
-
Establish a connection to a Caché server (see
the next chapter for details on this process).
-
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.
-
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.
The Java binding also supports dynamic queries through JDBC. To run
this type of query:
-
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.
-
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.
-
ResultSetMetaData rsmd = rs.getMetaData();
-
Use the Java
ResultSet object to manipulate
the returned data.
int colnum = rsmd.getColumnCount();