Chapter 5. Behavioral Analysis
Prev | Part IV. Kodo JDO Tutorial | Next

## Chapter 5. Behavioral Analysis

Often, pet stores sell snakes as well as rabbits and dogs. Pet stores are primarily concerned with a snake's length; much like rabbits, pet store operators usually sell them all for a flat rate.

This chapter demonstrates more queries, schema manipulation, one-to-many relations, and many-to-many relations.

Provided with this tutorial is a file called `Snake.java` which contains a sample Snake implementation. Let's get it compiled and loaded:

1. Examine and Compile Snake.java

   `javac Snake.java`

2. Add `tutorial.Snake` to `tutorial.jdo`

       <class name="Snake" persistence-capable-superclass="Animal" />

3. Enhance the class

   `jdoc tutorial.jdo` (or `jdoc Snake.class`)

4. Refresh the database

   As we have created a new persistence-capable class, we must change the database schema to match. So run `schematool -action refresh tutorial.jdo` (or `schematool -action refresh Snake.class`) to propagate the changes to the database.

Once you have compiled everything, add a few snakes to the database using `java tutorial.AnimalMaintenance add Snake "name" <length>`, where `<length>` is the length in feet for the new snake. Run `java tutorial.AnimalMaintenance list Snake` to see the new snakes in the database.

Unfortunately for the massively developing rabbit population, snakes often eat rabbits. Any good inventory system should be able to capture this behavior. So, let's add some code to `Snake.java` to support the snake's eating behavior.

First, let's modify `Snake.java` to contain a list of eaten rabbits.

1. Add the following code snippet to `Snake.java`

   This is the 'many' side of a one-to-many relation.

         // *** add this member variable declaration ***
         // This list will be persisted into the database as
         // a one-to-many relation.
         private Set giTract = new HashSet ();

       ...

         // *** modify toString (boolean) to output the giTract list ***
         public String toString (boolean detailed)
         {
           StringBuffer buf = new StringBuffer (1024);
           buf.append ("Snake ").append (getName ());

           if (detailed)
           {
             buf.append (" (").append (length).append (" feet long) sells for ");
             buf.append (getPrice ()).append (" dollars.");
             buf.append ("  Its gastrointestinal tract contains:\n");
             for (Iterator iter = giTract.iterator (); iter.hasNext ();)
             {
               buf.append ("\t").append (iter.next ()).append ("\n");
             }
           }
           else
           {
             buf.append ("; ate " + giTract.size () + " rabbits.");
           }

           return buf.toString ();
         }

       ...

         // *** add these methods ***
         /**
          *  Kills the specified rabbit and eats it.
          */
         public void eat (Rabbit dinner)
         {
           // Consume the rabbit.
           dinner.kill ();
           dinner.eater = this;
           giTract.add (dinner);
           System.out.println ("Snake " + getName () + " ate rabbit "
             + dinner.getName () + ".");
         }

         /**
          *  Locates the specified snake and tells it to eat a rabbit.
          */
         public static void eat (String filter)
         {
           PersistenceManager pm = JDOFactory.getPersistenceManager ();

           // Find the desired snake(s) in the data store.
           Query query = pm.newQuery (Snake.class, filter);
           Collection results = (Collection) query.execute ();

           if (results.size () > 0)
           {
             Iterator iter = results.iterator ();
             Query uneatenQuery = pm.newQuery (Rabbit.class, "isDead == false");

             Transaction transaction = pm.currentTransaction ();
             transaction.begin ();

             while (iter.hasNext ())
             {
               // Find a rabbit to eat.
               Random random = new Random ();

               // Run a query for a rabbit whose 'isDead' field indicates
               // that it is alive.
               List menu = new ArrayList ();
               menu.addAll ((Collection) uneatenQuery.execute ());
               if (menu.size () == 0)
               {
                 System.out.println ("No live rabbits in DB.");
               }

               // Select a random rabbit from the list.
               Rabbit dinner = (Rabbit) menu.get (random.nextInt
                 (menu.size ()));

               // Perform the eating.
               Snake snake = (Snake) iter.next ();
               System.out.println (snake + " is eating:");
               snake.eat (dinner);
             }

             transaction.commit ();
           }
           else
           {
             System.out.println ("no snakes matching '" + filter
               + "' found in persistence manager");
           }
           pm.close ();
         }

         public static void main (String [] args)
         {
           if (args.length == 2 && args[0].equals ("eat"))
           {
             eat (args[1]);
             return;
           }

           // If we get here, something went wrong.
           System.out.println ("Usage:");
           System.out.println ("  java tutorial.Snake eat \"snakequery\"");
         }

2. Add an eater field to `Rabbit.java`

   This is the 'one' side of a one-to-many relation. Notice that we are making this field `protected`. This demonstrates that it is possible to enhance any field; you need not provide getters and setters as we have done in previous examples. This is not recommended practice, because it means that all classes that access this field directly must be JDO enhanced, even if they are not persistence-capable.

   Add the following member variable to `Rabbit.java`:

         protected Snake eater;

3. Add metadata to `tutorial.jdo`

   Notice the `giTract` declaration: it is a simple Java list declaration. As with the many-to-many declarations in `Rabbit.java`, we will add metadata to the `tutorial.jdo` file.

       <class name="Snake" persistence-capable-superclass="Animal" >
         <field name="giTract">
           <collection element-type="Rabbit"/>
           <extension vendor-name="kodo" key="inverse" value="eater"/>
         </field>
       </class>

   Note that we specified an `inverse` attribute in this example. This is because the relation is a two-sided one; that is, the rabbit has knowledge of which snake ate it. We could have left out the eater field and instead created a one-sided many-to-many relation. The metadata might have looked like this:

       <class name="Snake" persistence-capable-superclass="Animal" >
         <field name="giTract">
           <collection element-type="Rabbit"/>
         </field>
       </class>

   For more information on types of relations, see the metadata section of the Kodo JDO Reference Guide.

4. Compile `Snake.java` and `Rabbit.java` and jdo-enhance the classes

   `javac Snake.java Rabbit.java`

   `jdoc tutorial.jdo` (or `jdoc Snake.class Rabbit.class`)

5. Refresh the database

   `schematool -action refresh tutorial.jdo`

Now, experiment with the following classes: `java tutorial.Snake eat` and `java tutorial.AnimalMaintenance details Snake`.

Complex Queries

Imagine that one of the snakes in the database was named 'Killer'. To find out which rabbits Killer ate, we could run either of the following two queries:

- `java tutorial.AnimalMaintenance details Snake "name == \"Killer\""`

- `java tutorial.AnimalMaintenance list Rabbit "eater.name == \"Killer\""`

The first query is Snake-centric -- the query runs against the `Snake` class, looking for all snakes named 'Killer' and providing a detailed listing of them. The second is Rabbit-centric -- it examines the rabbits in the database for instances whose eater field is named 'Killer'. This second query demonstrates the that simple java 'dot' syntax is used when traversing an Object field in a query.

It is also possible to traverse Collection fields. Imagine that there was a rabbit called Roger in the data store and that one of the snakes ate it. In order to determine who ate Roger Rabbit, you could run a query like this:

- `java tutorial.AnimalMaintenance details Snake "giTract.contains (animal) && animal.name == \"Roger\""`

Note the use of the `animal` variable that was registered with the query. To add support for this variable, uncomment the commented-out `declareVariables` call in the `getAnimals` method in `AnimalMaintenance.java`:

1. `AnimalMaintenance.getAnimals` should look like this:

         /**
          *  Return a collection of animals that match the specified query filter.
          *
          *  @param  filter      the JDO filter to apply to the query
          *  @param  cls         the class of animal to query on
          *  @param  pm          the PersistenceManager to obtain the query from
          */
         public static Collection getAnimals (String filter, Class cls,
           PersistenceManager pm)
         {
           // Get a query for the specified class and filter.
           Query query = pm.newQuery (cls, filter);

           // Add a single variable of type 'Animal' to this query to allow
           // for some reasonably powerful queries. This will be uncommented
           // in Chapter V.
           query.declareVariables ("Animal animal;");

           // Execute the query.
           return (Collection) query.execute ();
         }

2. Recompile `AnimalMaintenance.java`

   `javac AnimalMaintenance.java`

The `animal` variable is now available to use to represent any Animal contained in a Collection. As `animal` was declared as type Animal, we cannot directly use fields in our Animal subclasses. However, the JDO specification provides support for casting in queries, which lets us run queries like `java tutorial.AnimalMaintenance details Snake "giTract.contains (animal) && ((Rabbit)animal).isFemale == false"`, which prints details about all snakes that have eaten male rabbits.

Prev | Up | Next
Chapter 4. Inventory Growth | Home | Chapter 6. Extra Features