PSE/PSE Pro for Java API User Guide
This chapter provides information and instructions for using the class file postprocessor to make classes persistence-capable.
Under normal circumstances, you must postprocess together all class files in an application that you want to be persistence-capable or persistence-aware. Failure to do so can result in problems that are difficult to diagnose when you run your application. These failures generally manifest themselves as objects that are not being automatically fetched from the database when needed. Even if a particular class does not need to be persistence-capable, Object Design recommends that you postprocess it with all other class files in the application. Postprocessor options allow you to indicate which classes must be persistence-capable, which need to be persistence-aware, and which need not be annotated. If it is inconvenient or impossible to postprocess together all files in an application, you can postprocess a batch of files.
To store an object in a database, the object must be persistence-capable. For an object to be persistence-capable, it must include code that allows persistence. PSE/PSE Pro includes the class file postprocessor to automatically insert the required code, referred to as annotations, into your class files.
The command you use to run the class file postprocessor is osjcfp. The postprocessor provides a number of command options that allow you to tailor the results to your needs.
You can run the postprocessor on classes or class libraries that you create or that you purchase from a vendor. See COM\odi\demo\collections\README.htm for an example of making a third-party library persistence-capable.
The class file postprocessor annotates classes you define so that they are persistence-capable. This means that the postprocessor makes a copy of your class files, places them in a directory you specify (a directory other than the source directory), and adds lines of code (annotations) that are required for persistence. These annotations are
Description of the Process
Before you run the postprocessor, you must compile your source files. The set of files you run the postprocessor on can contain a combination of class files and zip files. The postprocessor generates annotated class files and places them in a directory that you specify. This destination directory is never the original directory. This avoids errors and provides both a persistence-capable and a transient version of the same class.
The postprocessor must operate on all files in an application in one execution. However, an application might use a file, perhaps a library, that is already annotated. You must not specify the annotated files when you run the postprocessor on the rest of the files in your application. Hence, the term batch means all files that the postprocessor must annotate in one execution of the osjcfp command. Each batch must have its own postprocessor destination directory for this to work correctly.
When you write a program that uses persistence, the program usually consists of a batch (a set) of classes, for example, classes A, B, and C. They are in files called A.java, B.java, and C.java. It is possible for each class to reference the other classes. For example, B might refer to C, and C might refer to B. There is no ordering or layering; there are no rules for references among the classes.
When this is the scenario, you must run the postprocessor on the whole batch at the same time. You cannot run the postprocessor on each file individually. This is because when the postprocessor operates on A, it might refer to B and C. The postprocessor must have information about B and C to correctly annotate A.
In relatively simple programs, there is only one batch involved. But sometimes there might be more than one batch in an application. Suppose, for example, that you want to write a persistent program that uses an existing library. An example of this is djgl, which is Object Design's persistence-capable version of ObjectSpace's JGL library. Your program consists of A, B, and C plus the JGL library.
Now, in a simple (one-batch) program, when you run the postprocessor, you always specify all files in your application. But in this case, you do not want the postprocessor to operate on JGL because it has already been postprocessed. In fact, you probably do not have the class files that have not been postprocessed.
It is correct to run the postprocessor on only A, B, and C. This is because there is a rule: JGL classes never know about A, B, and C. After all, JGL was written, finished, and put on the shelf before A, B, and C were created.
So there are two batches here:
Whenever you run the postprocessor you must run it on a whole batch. Each batch must have its own postprocessor destination directory.
In exceptional situations, you might want to insert all required annotations needed for persistence and not use the postprocessor at all. See Chapter 10, Manually Generating Persistence-Capable Classes, for instructions for manually annotating persistence-capable classes. You can also manually annotate your code to meet some persistence requirements and then run the postprocessor to insert the other annotations.
To make classes persistence-capable, compile the source files and then run the postprocessor on the resulting class files. You must run the postprocessor on all class files in your application at the same time. The topics discussed in this section are
Preparations
Before you run the postprocessor,
PATH=c:\winnt\system32;c:\winnt;c:\jdk102\bin;c:\pse\bin
On UNIX, it would be something like this:
PATH=/home/mydir:/usr/local/pse/bin:/usr/local/jdk102/bin
Requirements
The postprocessor requires specification of
For example:
osjcfp -dest osjcfpout COM.odi.demo.threads.Institution \
Banking.zip Account.class
You can specify additional options, which are described in Reference Information for Running the Postprocessor.
To make the Person class persistence-capable, enter a command like this:
osjcfp -dest ..\osjcfpout Person.class
The above command assumes that the Person.class file is in the current directory and the osjcfpout directory is a sibling to the current directory. If the postprocessor successfully generates an annotated version of the Person.class file, it also generates the PersonClassInfo.class file. This file contains information needed by PSE/PSE Pro to persistently store instances of Person.
The postprocessor places the annotated Person.class file and the PersonClassInfo.class file in a package-relative subdirectory of the osjcfpout directory. For example, suppose the Person class package name is COM.odi.demo.people. Further suppose that the osjcfpout directory is in the \users\kim directory.
The postprocessor writes the annotated class file to a file whose name is made up of the destination directory, the class package, the class name, and the .class extension. It writes the PersonClassInfo.class file to a similar location:
\users\kim\osjcfpout\COM\odi\demo\people\Person.class
\users\kim\osjcfpout\COM\odi\demo\people\PersonClassInfo.class
Note that both commands below have the same results, as specified above.
osjcfp -dest ..\osjcfpout Person.class
osjcfp -dest \users\kim\osjcfpout COM.odi.demo.people.Person
About the Destination Directory
The postprocessor never overwrites the class files specified on the postprocessor command line. If you specify a destination directory in such a way that it would store the annotated class file in the same location as the unannotated class file, the postprocessor displays an error message and terminates. It does not produce any output.
Consequently, after you run the postprocessor, you have a transient version of a class (your original file) and a persistence-capable version of the class (in the destination directory).
If there are no errors, the postprocessor places a version of all files specified on the command line in the destination directory. The postprocessor annotates those files that require annotations and does not modify those files that do not.
If a name you specify ends with .class the postprocessor assumes it is an explicit file name for a class file. If a name you specify ends with .zip the postprocessor assumes it is an explicit file name for a class zip file.
If a name you specify does not end with .class or .zip the postprocessor assumes it is a class name delimited with periods, for example, a.b.C. The postprocessor uses the CLASSPATH environment variable or the -classpath specification on the postprocessor command line to locate the class file, which can be in a zip file.
Here is an example of adding the-classpath option. It assumes that you are using PSE Pro on UNIX with the JDK installed in /usr/local/JDK-1.0.2.
osjcfp -dest osjcfpout-classpath \
/usr/pse:/usr/local/JDK-1.0.2/java/lib/classes.zip\
:/usr/pse/lib/pro.zip COM.odi.demo.threads.Institution Banking.zip \
Account.class
The postprocessor uses the class path you specify in the command line to locate the specified files. This is in place of the CLASSPATH environment variable. At run time, Java implementations append the location of the system classes to the end of the CLASSPATH environment variable. You must do this manually if you specify the -classpath option.
The postprocessor processes the class files in the order in which they appear on the command line and according to the persistence mode that is in effect when the postprocessor reaches the file name. The persistence mode indicates whether the postprocessor is
Persistence mode options
The default persistence mode is for the postprocessor to generate persistence-capable classes. Here are the options you can specify to determine the persistence mode:
If you specify a .class file or class name the postprocessor processes it according to the persistence mode that is in effect when the postprocessor reaches the file name. If you specify a zip file the postprocessor processes all class files in the zip file according to the persistence mode that is in effect when the postprocessor reaches the name of the zip file in the command line.
osjcfp -dest osjcfpout -persistaware Tent.class Family.class \
-persistcapable Campers.zip Site.class -copyclass Weather.class
After you run the postprocessor with the above command,
How the Postprocessor Handles Duplicate File Specifications
It is permissible for a class to be specified more than once in a command line. For example, a file can be in a zip file and you can also explicitly specify it. Or, a file can be included in a wildcard specification and you can also explicitly specify it. In the previous example, the Family class could be in the Campers.zip file. If it were, the postprocessor would annotate the Family class to be persistence-capable. This is because making a class persistence-capable supersedes making it persistence-aware. Likewise, making a class persistence-aware supersedes copying it as is to the destination directory.
If you specify the same class more than once on a command line both specifications must resolve to the same disk location. For example, suppose you specify both Person.class and COM.odi.demo.people.Person. This is allowed only if the class path causes COM.odi.demo.people.Person to resolve to the same Person.class that is explicitly specified.
The postprocessor must be able to find every file that you specify on the command line. If it cannot find one or more files it displays an error message and stops processing. It does not produce any output.
If a class originates in a zip file, either because you specify a zip file when you run the postprocessor or because the class path search locates the class in a zip file, the postprocessor writes the annotated class to the package-appropriate subdirectory of the destination directory.
The postprocessor cannot postprocess compressed zip files.
If the postprocessor previously annotated a .class file, you can only specify that .class file to be copied. You cannot specify it to be annotated. If you do, the postprocessor displays a message that states which specified class was already annotated and it terminates without producing any annotated files.
The JDK 1.1 imposes a memory limitation of 16 MB unless you override it. If you receive a java.lang.OutOfMemory error during postprocessing, edit osjcfp to increase the runtime memory pool. For example, enter:
java -mx32m
Managing Annotated Class Files
After you run the postprocessor, there are two versions of your class files:
It is important to keep these versions separate because
There are several ways to accomplish this. Object Design recommends that you
Ensuring That the Compiler Finds Unannotated Class Files
There are two ways in which the compiler can locate class files:
The former is convenient, but when you try to run your application PSE/PSE Pro finds the unannotated files before it finds the annotated files. The latter is more cumbersome to use since it means that the path to Java system classes must be listed explicitly in the argument, but it is safe. It ensures that the compiler does not operate on annotated class files.
For example, suppose PSE/PSE Pro is installed in c:\pse and you are building an application in c:\app. Your destination directory for annotated class files is c:\app\osjcfpout. Your CLASSPATH variable might look like this:
CLASSPATH=c:\pse\pse.zip;c:\app\osjcfpout;c:\app
When you run the compiler, specify the class path option with the path shown below. This removes the destination directory from the class look-up path and adds the Java classes to the path.
javac -classpath c:\app;c:\pse\pse.zip;c:\jdk102\lib\classes.zip App.java
Here is an example of why it is important for the compiler to operate on unannotated class files. Suppose you have two classes named X and Y in the same postprocessor batch. Neither of these classes is explicitly declared to inherit from COM.odi.Persistent. Now suppose you add the following two methods to class Y:
void foo(COM.odi.Persistent p) {}
void bar() { foo(new X()); } // Trying to pass an X instance to
// a function that is expecting COM.odi.Persistent
If you recompile only Y.java and the compiler finds the annotated classes, examination of the annotated class file allows the compiler to determine that X inherits from Persistent, which allows Y.bar() to compile. If you then recompile both X and Y, the compiler recognizes that X is not declared to inherit from COM.odi.Persistent and refuses to compile class Y, even though it successfully compiled earlier.
When you run your application, PSE/PSE Pro must find the annotated class files before it finds the unannotated class files. The recommended way to do this is to define a CLASSPATH environment variable that has the postprocessor destination directory before the source file directory. Consider the following example:
In this scenario, use the following CLASSPATH:
c:\app\osjcfpout;c:\app;c:\pse\pse.zip
After you modify your CLASSPATH environment variable, you can run the postprocessor with no special action. The postprocessor excludes the destination directory from the class path when it does classpath-based searches.
There are situations when you want the compiler to read in annotated class files. In these cases, the referenced classes are similar to an independent library on which you are building your application. The referenced classes form a batch, which is a group of class files that must be postprocessed together. The other files in your application form a second batch. For example, suppose this second batch is named X. Specify the -classpath option so that it points to the
This is the most common multiple-batch scenario. Your application is in one batch and the other batches are existing reusable libraries. Each batch has its own postprocessor destination directory.
Now suppose that you are not using an existing library. Your application itself contains a group of referenced classes (first batch) and then another group of classes (second batch) that reference the first batch. The instructions below show how to build your application in stages.
- Compile the source files and postprocess the class files in the first batch. This is the batch of files that are referenced by other classes in the application.
- Compile the source files in the second batch. You might not want to compile all files in this batch at the same time. So specify the -classpath option to point to the annotated class files in the first batch and any unannotated class files in the second batch.
- Run the postprocessor on the second batch. Specify a destination directory that is different from the destination directory that was specified when the postprocessor operated on the first batch. Alternatively, package the result of postprocessing the second batch in a zip file.
Alternatives for Finding the Right Files
In some circumstances, updating your CLASSPATH environment variable might be cumbersome or might not work well with your development environment. (This is true for the Symantec Cafe product.) In these cases, you can copy annotated files back to the building directory. However, if you do this, you must remove the annotated files before you recompile. This ensures that subsequent compilations and execution of the postprocessor operate on unannotated class files.
Two other alternatives are to
How the Postprocessor Determines Whether or Not to Generate an Annotated Class File
When you run the postprocessor, it checks if any annotated file it is going to create already exists. If an annotated file does not already exist, the postprocessor generates it. If an annotated file does exist, the postprocessor compares the date on the compiled input file with the date on the annotated output file. If the input file date is after the output file date, the postprocessor generates a new output file. If the input file date is before the output file date, the postprocessor does not generate a new file. It assumes that the annotated file that already exists is still valid.
This works fine when you run the postprocessor repeatedly with the same command line. However, when you change input parameters to the postprocessor, it is a good idea to remove the previously annotated class files from the destination directory. The reason for this is that a comparison of dates might not cause a new annotated file to be generated when the specification of a new input parameter requires a new annotated file to be generated.
To force the postprocessor to overwrite existing annotated files, specify the -f or -force option.
If you know that a class will never need to be stored persistently you can run the postprocessor to make the class persistence-aware. A persistence-aware class can operate on persistent objects but it cannot be persistent itself.
Persistence-aware annotations require less space than persistence-capable annotations. The postprocessor only adds calls to Persistent.fetch() and Persistent.dirty() where they are needed to operate on persistent objects. When the postprocessor makes a class persistence-aware it does not annotate that class's superclass.
You must make a class persistence-aware (or persistence-capable) when it
includes methods that obtain arrays from persistent objects.
To create a persistence-aware class specify the -pa or -persistaware option followed by the names of the classes that you want to be persistence-aware. For example:
osjcfp -dest osjcfpout -persistaware Compute.class
The command line above annotates Compute.class so that it has calls to the fetch() and dirty() methods.
Another reason to make a class persistence-aware is that making a class persistence-aware does not require changing its superclasses. This is important for classes such as java.lang.Thread, whose superclass should not be modified. java.lang.Thread is inherently transient so it makes no sense for it to become persistent because it is not useful when you take it out of the database. Typically, Java system classes are restricted from annotations by the postprocessor.
For an example of how you might use persistence-aware classes, see COM\odi\demo\pport\README.htm.
This section describes postprocessor behavior relative to various components in your application. It is important to be familiar with the information here so that the postprocessor produces the results you expect. The topics covered in this section are
Ensuring Consistent Class Files
When you run the postprocessor on more than one class file at a time it is important that all specified files were compiled together. This ensures that they are consistent. The postprocessor does not detect inconsistencies among files it operates on. For example, suppose you modify and recompile a class without also recompiling its subclasses. This can cause inconsistencies, which the postprocessor does not detect when it annotates the class files.
When you run the postprocessor to make classes persistence-capable, it generates annotated class files for the specified classes and for any superclasses that are in the same packages as the specified classes. PSE/PSE Pro requires annotations to superclasses for all classes that the postprocessor makes persistence-capable. If a superclass is not in the same package as one of its subclasses that is being made persistence-capable, you must explicitly specify the superclass on the postprocessor command line.
If a class that the postprocessor is annotating has no superclass, other than java.lang.Object, the postprocessor annotates the class to inherit from either COM.odi.Persistent or COM.odi.util.HashPersistent. The postprocessor chooses the superclass by examining the class being annotated and any subclasses. If any of these classes is nonabstract and inherits the hashCode() method from java.lang.Object, then the postprocessor makes the COM.odi.util.HashPersistent class the superclass. Otherwise, COM.odi.Persistent becomes the superclass.
Each alternative has its advantages. Here is some background so that you can decide which alternative is preferable.
Every class inherits from the Object class. Object defines the hashCode() method and provides a default implementation. For a persistent object, this default implementation almost always returns a different value for the same persistent object (the object on the disk) at different times. This is because PSE/PSE Pro fetches the persistent object into different Java objects at different times (in different transactions or different invocations of Java).
This is not a problem if you never put the object into a persistent hash table as a key or other structure that uses the hashCode() method to locate objects. For example, the pport example in the demo directory uses the strategy below. It prevents the postprocessor from inserting a hashCode() method for you.
public int hashCode() {
return super.hashCode();
}
Solving the problem
If you do put instances in hash tables as keys or something similar, the hash table or other structure that relies on the hashCode() method might become corrupted when you bring the objects back from the database. There are two ways to solve this problem:
Inheritance from the Persistent or HashPersistent class is mandatory for objects that you want to be persistent. You must define classes so that if they inherit from another class, it is a class that can inherit from Persistent or HashPersistent.
When you run the postprocessor you must specify a destination directory with the -dest option. The postprocessor uses the destination directory as the root directory of the class hierarchy of annotated files. The postprocessor places the annotated class file in the package-appropriate subdirectory of the destination directory. With the destination directory specified in your CLASSPATH environment variable, Java can find the annotated classes.
For example, suppose you specify osjcfpout as the destination directory. When you run the postprocessor on the Person.class file, which is in the COM.odi.demo.people package, the postprocessor places the annotated file in
osjcfpout\COM\odi\demo\people\Person.class
The package name of the annotated class file remains the same, unless you specify an option to change it. The class name of the annotated class file is always the same as the unannotated class file.
If an error occurs while the postprocessor is running, it terminates without producing any output.
For any warnings from the postprocessor, you might determine that you can safely ignore the warning. In this case, you can stop the postprocessor from warning you about the field in question. To do so, specify the -quietfield option followed by the fully qualified name of the field for which you want to suppress warnings. Alternatively, you can specify -quietclass to suppress warnings on the class.
You cannot make final fields persistent. If you try, the postprocessor displays a warning message and treats the fields marked as final as though you declared them to be transient. To allow such fields to be stored persistently, you must remove the final keyword.
The postprocessor also displays a warning for a static field that can hold potentially persistent values. The postprocessor cannot determine the type of the object that will actually be pointed to. Consequently, depending on the type of object referenced, the warning might not be applicable. For example, suppose you have a persistence-capable class named X. The X class has a static member named y of type COM.odi.Persistent. When you run the postprocessor, it displays a warning like this:
X.y is a static field of type COM.odi.Persistent that might refer to a persistent object. If this field does refer to a persistent object it must be user maintained.
A database root with a value to update the static variable would avoid the problem.
PSE/PSE Pro does not make the object referenced by X.y persistent if it is only reachable from X.y. If PSE/PSE Pro makes it persistent because it is reachable from some other point, the object referenced by X.y might become stale at the end of the transaction in which it becomes persistent. If it does, and if the object referenced by X.y does become persistent, it is possible that the application might try to use the stale version of the object. To avoid this inadvertent use of stale objects, update X.y at transaction boundaries. Set X.y to null or to some other value to ensure that if a stale object is referenced by X.y, it is no longer accessible through X.y. Then you can suppress the warning with the -quietfield option.
How can X.y be reachable from some other point? Perhaps some other persistent object or an object that is going to be persistent refers to the object that the static data member is referring to. When PSE/PSE Pro commits the transaction and performs transitive persistence it finds the object that the static data member is referring to.
The postprocessor is a Java program; it requires a Java virtual machine to run. It uses the first Java executable that it finds in your PATH environment variable. If you want the postprocessor to use some other Java executable, set the OSJCFPJAVA environment variable to the name of the Java executable you want the postprocessor to use.
The class file postprocessor annotates methods with VM instructions for automatically performing fetch() and dirty() operations on objects. It does this in such a way that the debugging information in the class files remains intact. For the most part, the annotations are invisible to an application. However, it is possible to encounter them under certain circumstances when using a debugger. For example, you might encounter the following when you use the Step into command:
x = foo(y.m);
Stepping into the statement that follows that statement might cause you to enter the PSE/PSE Pro code that causes the contents of the y object to be fetched. In such a situation, use the Step out command to leave the PSE/PSE Pro code. Then use the Step into command again, which should then step into the call to the foo() method.
You should rely on the Step over command whenever possible. However, there will be situations where you must use the Step into command. If you inadvertently step into a PSE/PSE Pro method, step out of the PSE/PSE Pro code and return to your own code by doing one of the following:
Line-Number and Local-Variable Information
When the postprocessor annotates a class file it maintains any existing line-number and local-variable information.
After you run the postprocessor, the annotated class files are in the package-appropriate subdirectory of the destination directory (root directory) you specified. You might want other class files in this destination directory. These could be nonpersistence-capable class files or files that have already been annotated. To copy these files to the destination directory along with the annotated files, specify the -copyclass option followed by the name of the file you want to copy. For example:
osjcfp -dest osjcfpout a.zip -copyclass b.class
In this example, the postprocessor annotates the files in a.zip and copies them to the package-appropriate subdirectory of the osjcfpout directory. The postprocessor also copies b.class to the osjcfpout directory but it does not modify the b.class file.
You can follow the -copyclass option with one or more .class file names, class names, or zip file names. This option applies to each name that follows it.
Classes for which you specify the -copyclass option can overlap with classes for which you specify the -persistcapable or -persistaware option. For example:
osjcfp -dest osjcfpout -copyclass *.class -persistcapable a.class
This allows you to keep all files in a package together and only annotate the classes that need to be annotated. You need not partition classes into those that need annotations and those that do not. You can specify the same file with more than one persistence mode option because the -persistcapable option and the -persistaware option override the -copyclass option.
If you have a persistence-capable class, class A, and all access to nontransient data members is through methods on A, and there is never any direct access to those data members from other classes that are not either persistence-capable or persistence-aware, you do not need to postprocess any other classes that refer to A. The methods of A will be properly annotated. Since all other classes only use A's methods, the other classes do not need to be persistence-aware.
An important exception to this is that if a class manipulates an array object (specifically, setting and getting array elements), that class must be annotated to be persistence-aware. However, if the code that provides access to the array is annotated to access the values of the array, you can avoid making the class persistence-aware. It is difficult to reliably implement this in the general case.
If you compile with optimization the classes that use the methods that get and set array values, the compiler might inline the get and set methods. In this case, you must make the class that uses the get and set methods persistence-aware.
Normally, the postprocessor places the annotated files in a package-relative subdirectory of the destination directory and the annotated files have the same package names as the original files. However, there is an option that allows you to change the package name of files specified in the postprocessor command line. The -translatepackage option modifies the package name so that the persistence-capable version of the class is in one package and the transient version (the original) is in another package.
To create persistence-capable classes whose package name is different from the original package name, specify the -translatepackage option followed by the current package name and then the new package name. The format for this option is
{ -translatepackage | -tp } orig_pkg_name new_pkg_name
For example, suppose you have the a.b.C class and you want to create the d.e.C persistence-capable class. Run the postprocessor like this:
osjcfp -dest osjcfpout -translatepackage a.b d.e C.class
Exact match required
The specification for the original package name must exactly match the package name of the specified file. If there is not an exact match the postprocessor does not place the annotated file in the new package. For example, suppose you have two classes named COM.odi.demo.New and COM.odi.Old. You want to move COM.odi.Old to the COM.odi.beta package and you specify the following command:
osjcfp -dest osjcfpout -tp COM.odi COM.odi.beta
COM.odi.demo.New COM.odi.Old
The postprocessor places the annotated file for the COM.odi.Old class in COM.odi.beta.Old in the package-relative subdirectory of the osjcfpout directory (osjcfpout\COM\odi\beta\COM.odi.beta.Old.class).
The postprocessor does not place the annotated file for COM.odi.demo.New in a different package because the original package name is COM.odi.demo and not just COM.odi. The postprocessor annotates COM.odi.demo.New and places it in osjcfpout\COM\odi\demo\COM.odi.demo.New.class.
The postprocessor applies the -translatepackage specification to
When copying files
It does not matter whether or not the postprocessor is making any other changes to the specified files. The postprocessor changes the package names of files for which the -copyclass option is specified right along with new persistence-capable or persistence-aware files.
You can specify this option more than once on a command line to specify several package translations. If you accidentally specify more than one translation for the same package the postprocessor performs the last translation you specify in the command line.
A change to the package name of a class requires updating all references to that class to reflect the new name. The postprocessor updates the references in classes that it is currently operating on. This includes each class specified on the command line and each class found in a zip file that is specified on the command line. The postprocessor cannot detect if there are class files for which the postprocessor was not called that refer to the renamed package. You must either call the postprocessor on the complete set of class files or modify the Java source of any files that the postprocessor is not annotating.
You might want a class to refer to both the transient and persistence-capable versions of some other class.
It is not possible for the postprocessor to determine which references should be to persistence-capable objects. Because of this, you must code the class so it uses the full path name of the different versions of the class. This is the only way to clarify which version of the class is wanted. However, this technique works correctly only when you are operating across batches. It does not work when you are within the same batch.
Here is an example of what that means. Suppose you have a utility class called a.b.C. You want to have both a transient and a persistence-capable version of a.b.C. So when you run the postprocessor you specify -translatepackage to create a persistence-capable version called y.z.C. Then, in another class called a.b.D, you try to use both versions of the class. You write source code in a.b.D that explicitly refers to y.z.C, something like
int n= y.z.C.countThem()
When you try to compile a.b.D, compilation can only succeed if you put the annotated classes into the class path of the compiler. Otherwise, the compiler reports an error because there is no such thing as y.z.C.
Also, it is not possible for a.b.C and a.b.D to be in the same batch because the -translatepackage option would apply to a.b.D. This would make all of a.b.D's calls go to the persistence-capable version, which is not what you want.
To use persistence-capable and transient versions of the same class, follow these steps:
- Create a utility library. This is the first batch. The library creates both persistence-capable and transient versions of a class.
- Run the postprocessor and put the two different versions of the class in two different packages.
- Use the library from an application. The application is the second batch.
- Compile the application with the annotated files of the first batch, but not the second batch, in the compiler's class path.
Creating Persistence-Capable Classes with Transient Fields
You can create a persistence-capable class with transient fields. A transient field is a field that is not stored in the database. The postprocessor ignores transient fields. Use the transient keyword to create a transient field. For example:
class A {
transient java.awt.Component myVisualizationComponent;
int myValue;
...
}
In this class, the myVisualizationComponent field is declared to be a transient reference to java.awt.Component. java.awt is a package containing GUI classes that do not lend themselves to being persistence-capable.
There are several ways you can customize persistence-capable and persistence-aware annotations. You can implement your own versions of methods that the postprocessor typically adds. You can implement hook methods that PSE/PSE Pro calls at specified points. You can define a hollow object constructor in place of the hollow object constructor the postprocessor typically defines. You can also insert your own fetch() and dirty() calls.
The three methods described below are among the annotations that the postprocessor adds to persistence-capable classes.
- The initializeContents() method loads real values into hollow instances of your persistence-capable class.
- The flushContents() method copies values from a modified instance (active persistent object) back to the database.
- The clearContents() method resets the values of an instance to the default values.
Alternatives
If you want to, there are two ways that you can customize the behavior of these methods.
Warning
The body of a hook method must not call any methods of the class and must not start or end a transaction. This is because the class methods are annotated and consequently make calls to fetch() and dirty(). Such calls in the middle of initializing or writing the object are not allowed because they might cause the virtual machine to encounter a stack overflow..
Here is an example of a program that implements these hook methods.
import COM.odi.*;
/**
* PColor provides a persistent representation of colors that can be
* used with the Java AWT package. The java.awt.Color class itself
* cannot be stored persistently, because some of its internal state
* depends on the particular kind of color display being used. If a
* java.awt.Color were created on a computer that used a 24-bit deep
* color monitor, stored in a database, and then retrieved and used
* on a different computer that had a gray-scale monitor, it would not
* function correctly. PColor stores the color value as three
* integers, and then recreates the java.awt.Color object whenever
* the PColor object is brought into Java from persistent storage.
*
* For expository purposes, this example pretends that the value of a
* java.awt.Color object can change after the object is created. The
* real java.awt.Color class is immutable, and so the setBlue method
* below would not work, and the preFlushContents method would
* not actually be needed.
*/
public class PColor {
/* These instance variables are stored persistently. They represent
the color value. */
int red;
int green;
int blue;
/* This instance variable is declared transient, so it is not stored
persistently. It is managed by the methods below. */
transient java.awt.Color color;
PColor(int r, int g, int b) {
red = r;
green = g;
blue = b;
color = new java.awt.Color(r, g, b);
}
/* When a PColor is brought into Java from persistent storage, the
java.awt.Color object is created. Note that this method runs
after the initializeContents, so that it can use the values
of the persistent instance variables. */
public void postInitializeContents() {
color = new java.awt.Color(red, green, blue);
}
/* When a PColor is sent out from Java to persistent storage, the
color value from the java.awt.Color object is copied into the
persistent instance variables, so that it will be saved.
Note that this method runs before flushContents, so that it
can set up the values of the persistent instance variables. */
public void preFlushContents() {
red = color.getRed();
green = color.getGreen();
blue = color.getBlue();
}
/* When clearContents happens, this method sets the color
instance variable to null, so that this PColor object won't be
stopping the java.awt.Color object from being reclaimed. */
public void preClearContents() {
color = null;
}
/* Equality for PColor objects is the same as equality of the
underlying java.awt.Color objects. */
public boolean equals(Object obj) {
if (obj instanceof PColor) {
return color.getRGB() == ((PColor)obj).color.getRGB();
}
return false;
}
public java.awt.Color getColor() {
return color;
}
public int getBlue() {
return color.getBlue();
}
public int setBlue(int b) {
color.setBlue(b);
}
/* and so on.... */
}
Creating a Hollow Object Constructor
For each persistence-capable class, the postprocessor finds or generates a hollow object constructor. The hollow object constructor takes a single argument whose type is COM.odi.ClassInfo.
Why might you define your own hollow object constructor?
Creation steps
When the postprocessor creates the hollow object constructor it follows these steps:
- The postprocessor selects an appropriate superclass hollow object constructor. If the superclass has an accessible constructor that takes a single COM.odi.ClassInfo argument, or if it will have one because the postprocessor will add it during this execution of the tool, the postprocessor uses that constructor. The postprocessor reports an error if it cannot find an accessible constructor.
- The postprocessor creates a protected constructor that
You can define the hollow object constructor instead of allowing the postprocessor to do it. If you define one, the postprocessor does not generate one.
Before an application can access the contents of a persistent object, it must call the Persistent.fetch() method to read the object or the Persistent.dirty() method to modify the object. These calls make the contents of the object available to your application. The postprocessor inserts these calls in methods of classes that it makes persistence-capable or persistence-aware. However, the postprocessor might not annotate your code for best performance. You might find that you can improve performance by inserting the fetch() and dirty() calls yourself.
If you insert a fetch() or dirty() call in a method, the postprocessor does not add any additional fetch() or dirty() calls.
The way to proceed is to first allow the postprocessor to add the fetch() and dirty() calls. Then run and monitor your program. If you want to try to improve performance, add the calls to your source file and recompile. When you run the postprocessor again it recognizes that the fetch() or dirty() call is already in place and does not add any fetch() or dirty() calls to any methods that already contain such a call.
If you do this annotation, keep in mind that the objects being accessed should be declared in your source file as inheriting from the Persistent class. When this is the case the compiler can effectively use the multiple overloadings of the fetch() and dirty() methods, which take COM.odi.Persistent arguments. Also, the compiler can generate more efficient code when you declare Persistent as the superclass in your source.
An important consideration when annotating by hand is that the compiler might inline this code into calling methods. This makes it appear to the postprocessor that the code annotations are in the calling method, which might not be true.
When using the JDK javac compiler this occurs when you specify the -O (capital O, as in Oscar) option.
To ensure that the postprocessor functions correctly, you must do one of the following:
Specifying the Number of Array Dimensions in Persistence-Capable Classes
By default, three is the maximum number of dimensions in a persistent array. If you need a persistent array that has more than three dimensions you can run the postprocessor with the -a or -arraydims option followed by an integer, which specifies the new maximum number of array dimensions. This option applies to all classes that the postprocessor annotates during this execution of the tool. It does not apply to a class that the postprocessor does not annotate. For example:
osjcfp -dest osjcfpout -a 4 Person.class Pet.class -copyclass
Car.class
This allows arrays of type Person and Pet to have as many as four dimensions. The maximum number of array dimensions does not change for the Car class because the postprocessor does not annotate that file, it only copies it to the destination directory.
An alternative, and far more complex, way to increase the number of allowed array dimensions is to manually implement the class of the ClassInfo instance associated with the persistence-capable class.
You can usually store interfaces persistently without additional steps because interfaces do not contain fields and cannot have instances created. However, you must perform additional steps to persistently store arrays that you declare to contain interface type elements, for example, java.lang.Cloneable[].
To make an interface persistence-capable, define it, compile it, and then run the postprocessor on it. The -persistcapable option is in effect by default. The postprocessor generates a ClassInfo subclass for the interface in the same package as the persistent interface. It also generates the annotated interface file. The postprocessor places both the annotated interface file and the ClassInfo subclass's class file in the package-appropriate subdirectory of the destination directory you specify.
To make a class persistence-capable when it includes a data member of an interface type, you must ensure that several elements are persistence-capable. For example, suppose you define class X to have a data member named y. Further suppose that y is of type Y, which is an interface type. To make X persistence-capable, you must ensure that all of the following are persistence-capable:
If you run the postprocessor to make X persistence-capable and you do not also specify Y to be persistence-capable, and you did not previously make Y persistence-capable, the postprocessor displays an error message that indicates that Y must be persistence-capable. It then terminates without producing any output. This rule also applies to arrays of interface types of any number of dimensions.
You can run the postprocessor without actually updating any files. The tool performs all processing and error checking and can display messages that indicate what it is doing. This allows you to make corrections before creating the persistence-capable versions of your classes.
To perform a test run of the postprocessor specify the -nowrite option on the command line. For example:
osjcfp -dest osjcfpout -nowrite classes.zip
This command processes all class files in the zip file and displays any error messages. To view information messages from the postprocessor include the -verbose option. For example:
osjcfp -dest osjcfpout -nowrite -verbose classes.zip
It does not matter where you place the -nowrite or -verbose option in the command line. Wherever you place them, they apply to all files that the postprocessor processes.
To suppress nonfatal warning messages, specify the -quiet option. The -quiet and -verbose options are mutually exclusive. The last one used on the command line applies to the entire execution. For example, the line below suppresses warning messages during the processing of all specified files because the -quiet option comes after the -verbose option.
osjcfp -dest osjcfpout -nowrite -verbose classes.zip -quiet more.zip
You can also suppress some warnings but not all warnings. Specify the -quietclass option followed by the fully qualified name of a class to suppress warnings for that class. Specify the -quietfield option followed by the fully qualified name of a field to suppress warnings that pertain to that field. These options apply only to the element whose name immediately follows the option. If the -verbose option is also specified, these options take precedence.
When you are running the postprocessor on a lot of files and specifying many options the command line can be very long. As a convenience, you can enter the options and file names in a file and then specify the file name as a postprocessor option. Be sure to prefix the file name with the @ symbol.
You can include comments in the input file. You can place items on different lines and line continuation symbols are not required. Otherwise, enter data in the input file exactly as you would enter it on the command line.
Indicate comments with a # sign. The postprocessor ignores any subsequent characters on the same line as the # sign.
For example, suppose you enter some postprocessor options and files for the postprocessor to operate on in an input file named optionsAndFiles. You specify this file as follows:
osjcfp @optionsAndFiles
You can intersperse input file specifications with options and files that you enter on the command line. For each specified input file, the postprocessor removes any comments from the input file and replaces the input file specification with the data in the input file. The postprocessor then begins to process the command line. For example:
osjcfp -dest osjcfpout @file1 -tp old.pack new.pack @file2
The postprocessor
- Replaces @file1 with the contents of file1.
- Replaces @file2 with the contents of file2.
- 10 Executes the command line starting with the -dest option.
You cannot nest input file specifications. That is, you cannot include the @ file_name option in an input file. Also, you cannot use wildcards in an input file. The postprocessor does not expand them.
There are some annotations that the postprocessor either cannot perform or does not perform because of execution performance considerations. You must include these annotations when you code your source files.
Keep in mind that when you add even one fetch() or dirty() call to a method the postprocessor recognizes that the method is already annotated and does not add any other fetch() or dirty() calls. So if you do annotate a method be sure to add all required calls.
It is possible for a method in a persistence-capable class to pass a persistent object to a nonpersistent method. When this happens you must ensure that there is a fetch() or dirty() call for the persistent object before it is passed to the nonpersistent method.
If all access to persistent objects is through annotated methods (methods in persistence-capable or persistence-aware classes) then manual annotations are not required. For arrays, there is no way to define a class so that arrays of that class can only be accessed by persistence-aware classes. You must be sure to call the fetch() or dirty() method on a persistent array before passing it to a method in a nonpersistent class.
The postprocessor cannot analyze or annotate native methods. If your code passes a persistent object to a native method, and if the native code might try to access the object other than through annotated methods, be sure to insert a call to fetch() or dirty() for the persistent object before it is passed. In cases where native code might access and/or navigate among persistent objects, you must do one of the following:
Annotating Subclasses
After you create a persistence-capable or persistence-aware class, you can define a subclass of that class. Doing so does not make the subclass persistence-capable or persistence-aware. You must run the postprocessor on the subclass.
If you forget to run the postprocessor on a subclass and if the subclass is reachable from a persistent root (other than through a transient field) PSE/PSE Pro might try to migrate instances of the subclass to the database. This attempt causes an error because the subclass is not persistence-capable.
A class can include nonstatic (instance) fields that contain initializer expressions in their declarations. Postprocessor-generated ClassInfo constructors do not run these initializers. Normally, this is not a problem. The constructor allows hollow object initialization and the initializeContents() method overwrites these fields when the object is fetched. However, there might be transient nonstatic fields that have initializer expressions or fields that are treated as transient by your implementation of the ClassInfo type and the initializeContents() and flushContents() methods. In this case, you must manually implement the hollow object constructor or PSE/PSE Pro does not run the initializer. It is impossible for the postprocessor to detect such cases and so no warning message can be provided.
In your application, you might pass an array to a nonpersistent method but the nonpersistent method is defined as having a parameter of type java.lang.Object. In this situation, the postprocessor cannot determine that it should insert fetch() or dirty() calls for the array in the calling method before passing the array. You must annotate the calling method yourself.
If the called method is declared to accept an array argument then the postprocessor recognizes that a fetch() call might be needed and inserts it.
You can use the java.lang.reflect.Field class to get and set fields of persistence-capable objects. To do so, you must
Class File Postprocessor Limitations
It is possible to cause invalid references when you run the postprocessor and rename the package. In an annotated class, the postprocessor locates and updates class names if they are in field, method, or class references. The postprocessor cannot locate and update strings arguments to Class.forName() if the name specifies a class whose package has been renamed.
The postprocessor can accept a command line that intersperses file names, options, and input file specifications.
The table below describes the options you can specify.
Option
|
Description
|
---|
@ input_file
|
Causes the contents of the named input file to replace this argument in the command line. The postprocessor does this before any other argument processing. You can specify this option multiple times on one command line to include multiple files. You cannot nest this option. That is, the postprocessor ignores this argument if it appears in an input file.
|
{ -a | -arraydims } num_array_dimensions
|
Specifies the maximum number of dimensions that PSE/PSE Pro allows for persistent arrays of objects whose classes are annotated during this execution of the postprocessor. If you do not specify this option the default is 3 dimensions.
|
{ -cis | -classinfosuffix } suffix_string
|
Specifies the suffix that the postprocessor adds to the name of the ClassInfo subclass that the postprocessor generates for each class it makes persistence-capable. By default, the suffix is ClassInfo. This is useful when you need to limit the number of characters in file names. For all batches in an application, you must specify the same suffix if you do not use the default.
|
{ -cpath | -classpath } class_path
|
Specifies the path by which to locate class files for postprocessing. If you specify this option PSE/PSE Pro uses it in place of the CLASSPATH environment variable. The -classpath option does not affect the class path used for the execution of the postprocessor.
|
{ -cc | -copyclass }
|
Copies classes to the destination directory without updating them. This option applies to class names, class files, and zip files that you specify on the command line after the -copyclass option and before the next -persistcapable or -persistaware option or the end of the command line. This option is useful when you want nonpersistent classes or classes that have already been annotated to be in the same directory as persistence-capable or persistence-aware classes being created.
If you specify the -persistaware or -persistcapable option for any file for which you also specify the -copyclass option, the postprocessor ignores the -copyclass option for that file.
|
{ -d | -dest } destination_dir
|
This option is required. The postprocessor uses the directory you specify for destination_dir as the root for locating the annotated files. The postprocessor places each class file it operates on in the package-appropriate subdirectory of the destination directory, as though the destination directory were in your class path.
If the destination directory specification would cause the postprocessor to overwrite an original file, the postprocessor reports an error and terminates without producing any output.
|
{ -f | -force }
|
Forces the postprocessor to overwrite existing annotated .class and ClassInfo files.
|
-modifyjava
|
Allows the postprocessor to modify classes in standard Java packages. The default is that the postprocessor does not modify standard Java classes.
|
-nowrite
|
Performs process and error checking but does not actually annotate class files. This option allows a test run of the postprocessor. You use it to determine whether or not all specified classes are accessible, whether or not additional options are needed, and if you specify -v (verbose) you can see where the resulting files would be located.
|
{ -pa | -persistaware }
|
Causes subsequent class files and zip files on the command line to be persistence-aware. This means that instances of the classes can operate on persistent objects but cannot be persistent. The postprocessor annotates persistence-aware classes so that there are calls to Persistent.fetch() and Persistent.dirty() where needed during operations on potentially persistent objects and arrays that might be used by the persistence-aware class. This option applies to class names, class files, and zip files that you specify on the command line after the -persistaware option and before the next -persistcapable or -copyclass option or the end of the command line. In other words, the -persistcapable option or the -copyclass option alters this mode. The -pc option is in effect by default.
|
{ -pc | -persistcapable }
|
Causes subsequent class files and zip files on the command line to be persistence-capable. This option applies to class names, class files, and zip files that you specify on the command line after the -persistcapable option and before the next -persistaware or -copyclass option or the end of the command line. The -pa (persistence-aware) option or the -copyclass option alters this mode. The -pc option is in effect by default.
|
{ -q | -quiet }
|
Causes the postprocessor to refrain from displaying warnings. A warning message provides information about something that the postprocessor recognizes as a possible problem, but cannot confirm as actually being a problem. This option cancels a previous -verbose option, if you specified one.
|
{ -qc | -quietclass } class_name
|
Causes the postprocessor to refrain from displaying warnings for the specified class. This option applies only to the name that immediately follows it. Specify a fully qualified class name. If the postprocessor is renaming the class, it does not matter whether you specify the old name or the new name. If you specify -verbose in the same command, this option takes precedence for the specified class. A warning message provides information about something that the postprocessor recognizes as a possible problem, but cannot confirm as actually being a problem.
|
{ -qf | -quietfield } member_name
|
Causes the postprocessor to refrain from displaying warnings for the specified class field. This option applies only to the name that immediately follows it. Specify a fully qualified class field name. If the postprocessor is renaming the class, it does not matter whether you specify the old name or the new name. If you specify -verbose in the same command, this option takes precedence for the specified class field. A warning message provides information about something that the postprocessor recognizes as a possible problem, but cannot confirm as actually being a problem.
|
{ -tp | -translatepackage } orig_pkg_name new_pkg_name
|
Renames classes that belong to orig_pkg_name so that they belong to new_pkg_name. The original class files remain in the original location and the postprocessor does not annotate them. For example, suppose the postprocessor makes a class named a.b.C persistent with -tp a.b a.b.x. The persistent class has the name a.b.x.C.
A package specification of "." implies the default unnamed package. For example, the option -tp . persist causes the unpackaged class name C to be renamed persist.C.
orig_pkg_name must exactly match the package name of the class being annotated. For example, for a file named a.c.D, a specification of -tp a a.b does not translate the package name. The package of a.c.D is a.c, not a.
The postprocessor changes the package name of all classes in the original package that it can locate through the CLASSPATH environment variable or, if it is specified, the -classpath option.
|
{ -v | -verbose }
|
Causes the postprocessor to write descriptions of its actions to standard output. This option cancels a previous -quiet option, if you specified one.
|
You can specify any number of class names, .class files, or .zip files on the command line. The postprocessor recognizes files as follows:
Because the postprocessor recognizes filename.class as well as filename, you can run a command such as
osjcfp -dest osjcfpout *.class
You do not need to derive qualified class names from the file paths.
[previous] [next]
doc@odi.com
Copyright © 1997 Object Design, Inc. All rights
reserved.
Updated: 05/13/97 12:18:58