The StateTracker program allows you to use nearly all of the explicit and implicit operations of JDO while monitoring the persistent, transactional, and unmanaged fields of persistent, transactional, and unmanaged apples.
Figure 8-1 diagrams the relationships of the main classes and interfaces of the StateTracker program. The StateTracker class implements the user interface and is the client of the Monitor and StateHandler services. It creates new apples and worms and modifies the state of existing ones. The source file StateTracker.java contains all of the command classes of the StateTracker program. The application classes and interfaces shown in Figure 8-1 are contained in the com.ysoft.jdo.book.statetracker and com.ysoft.jdo.book.statetracker.client packages.
Figure 8-1: The classes and interfaces of the StateTracker program
Figure 8-1 diagrams ten classes and interfaces. There are two application data classes, Apple and Worm. These have an n-m relationship to each other. The worms in the StateTracker program have the transcendental ability to exist in more than one apple at a time. The major purpose of the worms is to provide persistent fields that are not in the apple's default fetch group. The Apple class divides its fields into three groups, persistent, transactional, and unmanaged. The persistent group has the five fields shown in Listing 8-9. The fields were selected to give a representative sample of persistent fields. The first three fields are in the default fetch group, while the remaining two are not. The set of transactional (but not persistent) fields and the set of unmanaged fields are identical in type and similarly named.
Listing 8-9: The Persistent Fields of the Apple Class
private String persistentName; private int persistentSize; private Date persistentPicked; private HashSet persistentWorms; private Worm persistentHeadWorm;
The remaining classes and interfaces in Figure 8-1 serve the following purposes. The Apple class implements the Monitored interface. The Monitor service uses the Monitored interface to determine the apple's management state without affecting it. The Monitored interface also ensures that Apple has a public clone method that is used by the StateTracker to snoop on the state of an apple without affecting the apple's managed state. The StateHandler is the application service that uses the persistence manager. It handles objects, and knows nothing about apples and worms. The Apple class implements InstanceCallbacks. These callback methods serve two purposes: they capture the persistent object's identity string, and they provide notification to the user interface when the callbacks occur.
To build the StateTracker program, go to the bookants directory and type ant statetracker. This build is part of the ant learning-programs build. Listing 8-10 shows some of the expected output from the build when using the reference implementation.
Listing 8-10: Expected Output from Running ant statetracker
statetracker: [javac] Compiling 7 source files to E:\Bookcode\build [echo] returned from com/ysoft/jdo/book/statetracker/build.xml [copy] Copying 1 file to E:\Bookcode\enhanced\com\ysoft\jdo\book [java] done. [echo] creating runStatetracker.bat BUILD SUCCESSFUL
To run the StateTracker program, go to the bookcode-home directory and type runStatetracker.
If you enter the help command after starting the StateTracker program, you see all the commands that it supports. The expected output is shown in Listing 8-11.
Listing 8-11: Example of Help Output from the StateTracker Program
E:\Bookcode>runstatetracker Using adaptor class: com.ysoft.jdo.book.factory.jdori.JDORIAdaptor The database (FOStoreTestDB.btd) exists Using URL: (fostore:FOStoreTestDB) enter command: --> help commands: quit begin commit rollback active find all add apple select apple modify apple add worm delete worm snoop view get JDO state make persistent delete persistent make transactional make nontransactional make transient evict evict all refresh refresh all retrieve tickle default fetch group dirty toss exception configure configuration open is open close enter command: -->
As Listing 8-11 shows, the program recognizes a large number of commands. The commands begin, commit, rollback, and active allow you to control and monitor transactional boundaries. A good place to start is to add a few worms. Their only attribute is a name. For example, the following interaction adds one new worm named Henry:
enter command: --> add worm Enter worm's name: --> Henry Okay, but worms are made persistent only by being in a persistent apple enter command: -->
A new worm remains unmanaged. It becomes persistent only when it is reached from a persistent apple. After creating a few worms, make a new apple. Listing 8-12 shows a sample interaction that adds a new McIntosh apple with three worms.
Listing 8-12: User Commands to Create a New McIntosh Apple with Three Worms
enter command: --> add apple Enter apple's name: --> McIntosh Enter apple's size (> 0): --> 3 Enter date picked (mm-dd-yy): --> 10-15-02 Date accepted:Tue Oct 15 00:00:00 EDT 2002 Add a worm?: 1. true 2. false Enter selection: --> 1 Pick a worm: 1. Worm Henry 2. Worm Martha 3. Worm Jack Enter selection: --> 1 Add a worm?: 1. true 2. false Enter selection: --> 1 Pick a worm: 1. Worm Martha 2. Worm Jack Enter selection: --> 1 Add a worm?: 1. true 2. false Enter selection: --> 1 Pick a worm: 1. Worm Jack Enter selection: --> 1 Add a worm?: 1. true 2. false Enter selection: --> 2 Pick the head worm: 1. Worm Henry 2. Worm Martha 3. Worm Jack Enter selection: --> 2 Okay, the new transient apple has been added to the selection list enter command: -->
The new apple remains unmanaged. To see this, first use the select apple command to select the apple from the current list of apples. Then use the get JDO state command to determine the current JDO management state of the selected apple.
enter command: --> select apple Select an apple: 1. Apple transientName: McIntosh Enter selection: --> 1 Okay enter command: --> get JDO state Apple transientName: McIntosh is in JDO state transient enter command: -->
Because all actions so far have occurred on unmanaged state, there has been no reason to start a transaction.
Next, execute the begin and the make persistent commands to make the unmanaged McIntosh apple persistent. Now execute the view and get JDO state commands to determine the unmanaged, transactional, and persistent state of the apple and to determine its management state. Using any implementation, you should see output like the following, which was produced by the reference implementation:
enter command: --> view Viewing managed state for: OID: 105-12 [JVM ID:4066855] transient state: McIntosh, 3, 10-15-02, Worm Martha, 3 worms {Worm Martha,Worm Jack,Worm Henry} transactional state: McIntosh, 3, 10-15-02, Worm Martha, 3 worms {Worm Henry,Worm Martha,Worm Jack} persistent state: McIntosh, 3, 10-15-02, Worm Martha, 3 worms {Worm Henry,Worm Martha,Worm Jack} enter command: --> get state OID: 105-12 [JVM ID:4066855] is in JDO state persistent-new enter command: -->
Notice that all three states are the same. That is because the values entered when the apple was created were copied to all three sets of fields, as a way to reduce the amount of user input. As expected, the management state after the make persistent command is persistent-new.
If you have not executed the configure command, then all transactional attributes are off at this point, and the configuration commands returns the following output:
--> configuration Current transaction properties: active, !Opt, !RetainV, !RestoreV, !NTR, !NTW enter command: -->
At this point, commit the transaction and ask for the apple's management state. You should see output that looks like the interaction in Listing 8-13, which was produced by the reference implementation.
Listing 8-13: Sample Output from Committing the New McIntosh Apple
enter command: --> commit Synchronization.beforeCompletion called OID: 105-12 [JVM ID:4066855] jdoPreStore OID: 105-12 [JVM ID:4066855] jdoPreClear Synchronization.afterCompletion called with status: committed Okay enter command: --> get state OID: 105-12 [JVM ID:4066855] is in JDO state hollow enter command: -->
After committing the transaction, you will get an exception if you execute the view command because a transaction is not active. Instead run the snoop command. The snoop command produces a view of the object without affecting the managed state or requiring a transaction. The expected output will look like the output in Listing 8-14.
Listing 8-14: Sample Output from Snooping on the Hollow Apple
enter command: --> snoop Viewing raw state for: OID: 105-12 [JVM ID:8083121] transient state: McIntosh, 3, 10-15-02, OID: 106-21, 3 worms {OID: 106-21, OID: 106-22, OID: 106-20} transactional state: null, 0, no date, null, null worms persistent state: null, 0, no date, null, null worms enter command: -->
There are four things to notice in Listing 8-14. One, the JVM ID has changed from Listing 8-13. The change occurs because the snoop command views a clone of the original apple. (Remember from Chapter 5 that, by default, cloning a persistent object gets a snapshot of the current memory state without invoking transparent persistence.) Two, the persistent state has Java default values. This is expected since the object is in the hollow management state. Three, the transactional state also has Java default values. This is an unexpected outcome. The specification describes eviction only in terms of clearing the persistent state. It says nothing about clearing the nonpersistent and transactional state. In fact, the specification strongly implies that eviction does not clear the nonpersistent and transactional state. This behavior has been reported as a bug in the JDO reference implementation at the Java bug parade (http://developer.java.sun.com/developer/bugParade).
Finally, note that the transient state is not changed, except that all of the worms are now unnamed. The transientWorms field and the transientHeadWorm field both point to persistent objects even though the fields are unmanaged. When the new apple was created, the same worms were used to set the unmanaged, transactional, and persistent fields. As a result, all worm fields are referring to the same worms, which became persistent when the apple became persistent. When the snoop command executes, it clones the apple, but it does not clone the worms. The worm's toString method catches the exception that results from trying to examine the persistent name of a Worm outside of a transaction, and it recovers from the exception by returning the worm's identity string instead of its name.
The previous section describes many commands that the StateTracker program recognizes. This section describes the remaining commands.
Many of the command strings are self-describing. For example, the make transactional command will make a nontransactional object transactional. In some cases, for the command to succeed, the implementation must support the appropriate implementation options. An unmanaged object cannot be made transactional unless the implementation supports, as the reference implementation does, the javax.jdo.option.TransientTransactional feature. Most commands operate on the selected apple, but some operate on a set of apples. The evict all command evicts all persistent-clean apples (and worms) and the refresh all command refreshes all transactional apples and worms.
The configure command sets the five properties of the Transaction object: Optimistic, RetainValues, RestoreValues, NontransactionalRead, and NontransactionalWrite. The is open command checks whether the persistence manager is open. The open and close commands open and close the persistence manager.
The toss exception command sets a flag in the transaction's Synchronization object that will cause it to throw a JDOUserException on the next commit. It's a one-shot setting that does not block a subsequent commit. The tickle default fetch group command reads a couple of fields of the default fetch group and outputs a message with their values. It will cause the default fetch group to load in most cases. The dirty command will call the makeDirty method in JDOHelper for the selected managed field.
The StateTracker program has been invaluable in writing this book, and you will find it very useful for test driving your selected JDO implementation.