In addition, the JCL offers other important features necessary to support Object-Relational DBMSs like PREDATOR. Specifically, the JCL supports:
ServerSite S = new ServerSite("turing.csuglab.cornell.edu", 9700);identifies a database server accepting connections on port 9700 of turing.csuglab.cornell.edu. If the client entity is an applet, it will only be able to make a connection to a database server running on the host from which it was downloaded; this is a restriction imposed by the Java security manager. In this case, the client should supply the following information to the constructor:
ServerSite S = new ServerSite(getCodeBase().getHost(), 9700);(Note that it is possible to run a session with a server which does not run on the machine from which the applet was downloaded. This is achieved by using a JCLReflector object, which acts like a proxy for the client.) Once the database server's site has been identified, a Connection object should be created. Then, open the connection, supplying the site constructed above:
Connection C = new Connection(); try { C.open(S); } catch (java.net.UnknownHostException X) { // The supplied host is unknown; did you spell the host's name correctly? } catch (CannotConnectException X) { // The connection could not be established; is this a PREDATOR server? } catch (ConnectionAlreadyOpenException X) { // The connection object is already open; close it first }
try { Query Q = new Query("SELECT R.pic, R.name FROM Renoir R"); } catch (BadQueryException X) { // The query is improperly formatted }A query is improperly formatted if a semicolon (;) appears anywhere in the query. Semicolons are disallowed because only one query can be associated with a Query object. (Since semicolons delimit queries, a concatenation of two of more queries includes at least one semicolon.)
Then, the query can be submitted via a call to processQuery(Query, boolean):
QueryResult R; try { R = C.processQuery(Q, false); } catch (NoOpenConnectionException X) { // The connection must first be opened } catch (UnterminatedQueryException X) { // The previous query was not terminated via a call to endQuery() }After the query has been processed, the query's transactional context at the server has not yet been deleted, i.e., the transaction has not yet been committed or aborted. This permits callbacks to be guaranteed the same ACID properties queries have. To end the query's transaction at the server, a call must be made to endQuery(). Until this call is made, no new queries can be processed, nor can the connection be closed. (If callback properties are unimportant, the boolean parameter can be set to true, and the query will be ended automatically, as soon as all of the results have been read completely.)
if (R == null) { // There was no result from the query } else if (R instanceof RecordStreamResult) { RecordStreamResult RSR = (RecordStreamResult)R; // Process the record stream } else if (R instanceof UnformattedResult) { UnformattedResult UR = (UnformattedResult)R; // Process the unformatted result }The two result types, RecordStreamResult and UnformattedResult, correspond to the two types of responses sent by a PREDATOR server. When the result is a relation, a RecordStreamResult is returned. When the result is textual data, an UnformattedResult is returned. For example, if the query "SELECT * FROM _STABLES" is submitted, the result is a relation. But is the query "list rels" is submitted, the result is textual.
UnformattedResult UR = (UnformattedResult)R; String Text = UR.toString(); System.out.println("Result: " + Text);
It is important to note that the ADT classes are not values. That is to say, the ADT_int object maintained by the JCL is not an integer. Rather, it is a container for information about integers. ADT objects also provide methods to create new DataObjects, which are value objects. For example, the JCL uses the ADT_image object it maintains to create a new ImageObject, which represents an actual image. (A JCL client is never called upon to create new objects; all objects are returned via calls to RecordStreamResult.getNextRecord(SubclassSelector). However, it is important to understand the conceptual distinction between ADT classes and the objects they manage.)
As previously mentioned, the result tuples from queries are arrays of DataObjects. These objects are only minimally functional insofar as their true properties are hidden. The DataObject class defines a single method, toString(ADTMetaInfo), which prints a textual version of the object. For instance, an DoubleObject will print its value, e.g., "3.141592654". (The parameter ADTMetaInfo is mandatory, and is explained below. If this parameter is omitted, the method invoked will be the toString() method of the Java Core Object class, giving unexpected results.)
To exploit hidden functionality of DataObjects, the Java operator instanceof must be used. Although clients may check to see if a specific type has been returned, e.g., an ImageObject, this is not recommended. To permit extensibility, common functionality that data objects offer has been grouped into interfaces, which the data objects can implement. These interfaces include:
DataObject tuple[] = RSR.getNextRecord(null); for (int i = 0; i < tuple.length; i++) { ADTMetaInfo MetaInfo = RSR.getSchema().getAttribute(i).getMetaInfo(); ADT TypeADT = RSR.getSchema().getAttribute(i).getTypeADT(); if (tuple[i] instanceof DisplayObject) { ((DisplayObject)tuple[i]).display(MetaInfo); } else if ((tuple[i] instanceof MIMETypeObject) && (tuple[i] instanceof CallbackObject)) { try { getAppletContext(). showDocument(C.getCallbackObjectURL((CallbackObject)Object, TypeADT, MetaInfo), "Requested Object"); } catch (Exception X) { // Something went wrong } } else System.out.println(tuple[i].toString(MetaInfo)); }
DataObject tuple[] = RSR.getNextRecord(null); for (int i = 0; i < tuple.length; i++) { ADTMetaInfo MetaInfo = RSR.getSchema().getAttribute(i).getMetaInfo(); ADT TypeADT = RSR.getSchema().getAttribute(i).getTypeADT(); DataObject Object = tuple[i]; /* * Now MetaInfo is the meta-information object corresponding to * Object, which is an object of type TypeADT. */ }Meta-information is used in PREDATOR to allow ADTs to maintain domain information. For instance, an attribute of a result relation might contain an attribute of the Image type. The database server knows that objects in the column are images, but nothing more. (This is an artifact of the structure of PREDATOR; the database server knows nothing about images. All work with image objects is performed by the image ADT, which contains all the domain semantics.) Thus, if some semantic information is common to all the objects, it must be stored in every object. To prevent such a waste of space, ADTs are permitted to create meta-information for every attribute. In this object, which is stored with the attribute, the ADT can store information common to all the objects in that column of the relation. For instance, if a table contains a column of images which are known to have the same size and format, e.g., 100 KB JPEG images, this information is stored in the meta-info by the ADT. As a result, whenever one works with data objects, the meta-info of that object must also be present. (Otherwise, all the necessary information would not be available to the ADT when working with the object.)
When the JCL opens a connection, the database server sends over a list of type names which corresponds to the types supported by the server. Using these names, the JCL queries the web server from which it was loaded for the corresponding ADT classes. So, for example, when the database server sends over the type name "audio", the JCL attempts to load the class "PREDATOR.ADT.ADT_audio". If the load is successful, the client can now work with that type. If not, result values of that type are ignored by the JCL.
In the (hopefully, rare) instance that the JCL experiences an error not caused by faulty use, a InternalError is thrown. If an internal error occurs in your application, please contact PREDATOR support.
If a result DataObject implements the CallbackObject interface, it is a callback object. To access the object's full value, a callback request must be made. For instance, to access the actual image in a result ImageObject, the server must be contacted.
Callback requests occur via HTTP requests. (Interestingly, these requests are made not of a web server, but directly of the PREDATOR server. The database server recognizes HTTP requests as callbacks, decodes them, and serves the required objects.) To create a connection by which the object can be accessed, a URL is necessary. The JCL provides a way to obtain a URL for a CallbackObject through the getCallbackObjectURL(CallbackObject, ADT, ADTMetaInfo) method.
Once the URL is obtained, it can be used in many ways to fetch the "full value". If the object is a MIME type object and is to be displayed, the URL can be handed to the browser, which will display the object. If the object is to be fetched into memory, a java.net.URLConnection can be created, the the stream can be read into an internal array.
The JCL accomplishes interface extensibility by forcing each ADT's Java class to export to the system the minimum functionality class (MFC) for objects of that type, so that the data type will operate properly. For instance, the polygon data type specifies that all objects it deals with must at least provide the functionality of the PolygonObject class. Interfaces are free to use any subclass of this class they desire. (The MFC is exported by the method getObjectClass(ADTMetaInfo).)
To implement interface extensibility, interfaces decide on the extended functionality they require from the data types. For instance, in the above example, the interface designer might define an interface as below:
public interface CaliforniaObject { public void DrawYourself(CaliforniaMap C); }The designer then procedes to subclass the MFC of each ADT from which she desires the added functionality. Continuing the example, the designer would implement a class as below:
public class CaliforniaPolygon extends PolygonObject implements CaliforniaObject { public void DrawYourself(CaliforniaMap C) { /* ... */ } }for each of the data types which will support the new functionality. These classes are called added functionality classes (AFC). Finally, the designer must inform the JCL object that the AFCs should be used as a substitutes for the MFCs they correspond to. This is accomplished by providing a SubclassSelector to calls to getNextRecord(SubclassSelector) that maps MFCs to their corresponding AFCs. For instance, the mapping might be, "Given class foo, if the class P(foo) exists, use it instead." The appropriate precautions to insure the mapping preserves each ADT's minimum functionality are taken, and then the AFC objects are substituted for objects of the MFC.