Skip Headers
Oracle® Coherence Tutorial for Oracle Coherence
Release 3.5

Part Number E14527-01
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

4 Loading Data Into a Cache

In this chapter, you will learn how to populate a Coherence cache with domain objects that are read from text files.

This chapter contains the following sections:

4.1 Introduction

Until now, you have been putting and retrieving objects individually. Each put can result in increased network traffic, especially for partitioned and replicated caches. Additionally, each call to put returns the object it just replaced in the cache, which adds more unnecessary overhead. Loading the cache can be made much more efficient by using the putAll method instead.

This chapter assumes that you have completed "Creating and Caching Complex Objects". It also assumes that you are familiar with using java.io.BufferedReader to read text files, java.lang.String.split method to parse text files, and java.text.SimpleDateFormat to parse dates.

4.2 Populating a Cache with Domain Objects

This exercise demonstrates how to create a console application that populates a Coherence cache with domain objects. The application can use the Coherence com.tangosol.io.pof.PortableObject implementation.

This exercise has three parts. You will create a key that will help to obtain the Contact object, a generator to provide data for the cache, and a loader to load the cache.

To populate a cache with domain objects:

  1. Create a new project called Loading.

    See "Creating and Caching Complex Objects" for information on creating a new project.

  2. Use the Address Phone, and Contact classes that you created in an earlier exercise (Contacts). Add c:\home\oracle\labs\Contacts\classes to the project classpath (Hint: right-click the project and choose Project Properties>Libraries and Classpath. You can find more detailed information in "Adding JARS and Libraries to the Project Classpath".)

  3. Create a contact ID class that provides a key to the employee for whom information is tracked. See "Creating a Java Class" if you need detailed information on creating a Java class.

    Create the contact ID based on the employee's first name and last name. This object acts as the key to obtain the Contact object.

    Since this class will use POF serialization, it must implement PortableObject and provide implementations for the writeExternal and readExternal PortableObject methods and the equals, hashCode, and toString object methods.

    Note:

    Cache keys and values must be serializable (for example, java.io.Serializable). Cache keys must also provide an implementation of the hashCode() and equals() methods, and those methods must return consistent results across cluster nodes. This implies that the implementation of hashCode() and equals() must be based solely on the object's serializable state (that is, the object's non-transient fields); most built-in Java types, such as String, Integer and Date, meet this requirement. Some cache implementations (specifically the partitioned cache) use the serialized form of the key objects for equality testing, which means that keys for which equals() returns true must serialize identically; most built-in Java types meet this requirement as well.

    Example 4-1 illustrates a possible solution.

    Example 4-1 Simple Contact ID Class

    package com.oracle.coherence.handson;
    
    import com.tangosol.io.pof.PofReader;
    import com.tangosol.io.pof.PofWriter;
    import com.tangosol.io.pof.PortableObject;
     
    import com.tangosol.util.Base;
    import com.tangosol.util.HashHelper;
     
    import java.io.IOException;
     
    /**
    * ContactId is a key to the person for whom information is
    * tracked.
    */
    public class ContactId
            implements PortableObject
        {
        // ----- constructors ---------------------------------------------------
     
        /**
        * Default constructor (necessary for PortableObject implementation).
        */
        public ContactId() {
            }
     
        /**
        * Construct a contact person.
        *
        */
        public ContactId(String FirstName, String LastName)
            {
            super();
            this.FirstName = FirstName;
            this.LastName  = LastName;
            }
     
        // ----- accessors ------------------------------------------------------
     
        /**
        * Return the first name.
        *
        */
        public String getFirstName()
            {
            return FirstName;
            }
     
        /**
        * Return the last name.
        *
        */
        public String getLastName()
            {
            return LastName;
            }
     
        // ----- PortableObject interface ---------------------------------------
     
        public void readExternal(PofReader reader)
                throws IOException
            {
            FirstName = reader.readString(0);
            LastName = reader.readString(1);
            }
     
        public void writeExternal(PofWriter writer)
                throws IOException
            {
            writer.writeString(0, FirstName);
            writer.writeString(1, LastName);
            }
     
        // ----- Object methods -------------------------------------------------
     
        public boolean equals(Object oThat)
            {
            if (this == oThat)
                {
                return true;
                }
            if (oThat == null)
                {
                return false;
                }
     
            ContactId that = (ContactId) oThat;
            return Base.equals(getFirstName(), that.getFirstName()) &&
                   Base.equals(getLastName(),  that.getLastName());
            }
     
        public int hashCode()
            {
            return HashHelper.hash(getFirstName(),
                   HashHelper.hash(getLastName(), 0));
            }
     
        public String toString()
            {
            return getFirstName() + " " + getLastName();
            }
     
        // ----- data members ---------------------------------------------------
     
        /**
        * First name.
        */
        private String FirstName;
     
        /**
        * Last name.
        */
        private String LastName;
        }
    
  4. Add a <user-type> entry for ContactID to the contacts-pof-config.xml file.

    Example 4-2 POF Configuration File with the ContactId Entry

    <?xml version="1.0"?>
     
    <!DOCTYPE pof-config SYSTEM "pof-config.dtd">
     
    <pof-config>
      <user-type-list>
     
        <!-- coherence POF user types -->
        <include>coherence-pof-config.xml</include>
     
        <!-- com.tangosol.examples package -->
        <user-type>
          <type-id>1001</type-id>
          <class-name>com.oracle.coherence.handson.Contact</class-name>
        </user-type>
        <user-type>
          <type-id>1002</type-id>
          <class-name>com.oracle.coherence.handson.Address</class-name>
        </user-type>
        <user-type>
            <type-id>1003</type-id>
          <class-name>com.oracle.coherence.handson.Phone</class-name>
        </user-type>
        <user-type> 
         <type-id>1004</type-id>
          <class-name>com.oracle.coherence.handson.ContactId</class-name>
        </user-type>  
       </user-type-list>
      <allow-interfaces>true</allow-interfaces>
      <allow-subclasses>true</allow-subclasses>
    </pof-config>
    
  5. Create a Java class named DataGenerator to generate random contact names and addresses. See "Creating a Java Class" if you need detailed information on creating a Java class.

    Hint: Use the Address, Phone, and Contact classes that you created in the earlier exercise. Use java.util.Random to generate some random names, addresses, telephone numbers, and ages.

    Example 4-3 illustrates a possible solution. This solution creates a text file, contacts.cvs, that contains the employee contact information. The file will be stored in the root of the project directory, in this case, C:\home\oracle\labs\Loading

    You might see an error in JDeveloper caused by the LoaderExample.FILENAME declaration. This can be ignored—you will create LoaderExample in the next step.

    Example 4-3 Sample Data Generation Class

    package com.oracle.coherence.handson;
     
    import com.oracle.coherence.handson.Address;
    import com.oracle.coherence.handson.Contact;
    import com.oracle.coherence.handson.Phone;
    import com.tangosol.util.Base;
     
    import java.io.BufferedWriter;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
     
    import java.sql.Date;
     
    import java.util.Collections;
    import java.util.Random;
     
     
    /**
    * DataGenerator is a generator of sample contacts.
    */
    public class DataGenerator
        {
        // ----- static methods -------------------------------------------------
     
        /**
        * Generate contacts.
        */
        public static void main(String[] asArg)
                throws IOException
            {
            String       sFile = asArg.length > 0 ? asArg[0] : LoaderExample.FILENAME;
            int          cCon  = asArg.length > 1 ? Integer.parseInt(asArg[1]) : 1000;
            OutputStream out   = new FileOutputStream(sFile);
     
            generate(out, cCon);
            out.close();
            }
     
        /**
        * Generate the contacts and write them to a file.
        */
        public static void generate(OutputStream out, int cContacts)
                throws IOException
            {
            PrintWriter writer = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(out)));
     
            for (int i = 0; i < cContacts; ++i)
                {
                StringBuffer sb = new StringBuffer(256);
     
                //contact person
                sb.append("John,")
                  .append(getRandomName())
                  .append(',');
     
                // home and work addresses
                sb.append(Integer.toString(Base.getRandom().nextInt(999)))
                  .append(" Beacon St.,,") /*street1,empty street2*/
                  .append(getRandomName()) /*random city name*/
                  .append(',')
                  .append(getRandomState())
                  .append(',')
                  .append(getRandomZip())
                  .append(",US,Yoyodyne Propulsion Systems,")
                  .append("330 Lectroid Rd.,Grover's Mill,")
                  .append(getRandomState())
                  .append(',')
                  .append(getRandomZip())
                  .append(",US,");
     
                // home and work telephone numbers
                sb.append("home,")
                  .append(Base.toDelimitedString(getRandomPhoneDigits(), ","))
                  .append(",work,")
                  .append(Base.toDelimitedString(getRandomPhoneDigits(), ","))
                  .append(',');
     
                // random birth date in millis before or after the epoch
                sb.append(getRandomDateInMillis());
     
                writer.println(sb);
                }
            writer.flush();
            }
     
        /**
        * Return a random name.
        *
        */
        private static String getRandomName()
            {
            Random rand = Base.getRandom();
            int    cCh  = 4 + rand.nextInt(7);
            char[] ach  = new char[cCh];
     
            ach[0] = (char) ('A' + rand.nextInt(26));
            for (int of = 1; of < cCh; ++of)
                {
                ach[of] = (char) ('a' + rand.nextInt(26));
                }
            return new String(ach);
            }
     
        /**
        * Return a random phone muber.
        * The phone number includes access, country, area code, and local
        * number.
        *
        */
        private static int[] getRandomPhoneDigits()
            {
            Random rand = Base.getRandom();
            return new int[] {
                11,                   // access code
                rand.nextInt(99),     // country code
                rand.nextInt(999),    // area code
                rand.nextInt(9999999) // local number
                };
            }
     
        /**
        * Return a random Phone.
        *
        */
        private static Phone getRandomPhone()
            {
            int[] anPhone = getRandomPhoneDigits();
     
            return new Phone((short)anPhone[0], (short)anPhone[1],
                    (short)anPhone[2], anPhone[3]);
     
            }
        /**
        * Return a random Zip code.
        *
        */
        private static String getRandomZip()
            {
            return Base.toDecString(Base.getRandom().nextInt(99999), 5);
            }
     
        /**
        * Return a random state.
        *
        */
        private static String getRandomState()
            {
            return STATE_CODES[Base.getRandom().nextInt(STATE_CODES.length)];
            }
     
        /**
        * Return a random date in millis before or after the epoch.
        *
        */
        private static long getRandomDateInMillis()
            {
            return (Base.getRandom().nextInt(40) - 20) * Contact.MILLIS_IN_YEAR;
            }
     
        /**
        * Generate a Contact with random information.
        *
        */
        public static Contact getRandomContact()
            {
            return new Contact("John",
                    getRandomName(),
                    new Address("1500 Boylston St.", null, getRandomName(),
                        getRandomState(), getRandomZip(), "US"),
                    new Address("8 Yawkey Way", null, getRandomName(),
                        getRandomState(), getRandomZip(), "US"),
                    Collections.singletonMap("work", getRandomPhone()),
                    new Date(getRandomDateInMillis()));
            }
     
        // ----- constants ------------------------------------------------------
     
        /**
        * US Postal Service two letter postal codes.
        */
        private static final String[] STATE_CODES = {
                "AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "OF", "DC",
                "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY",
                "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE",
                "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR",
                "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VI",
                "VA", "WA", "WV", "WI", "WY"
            };
        }
    
  6. Create a console application (Java class) called LoadingExample. See "Creating a Java Class" if you need detailed information.

    Use input streams and buffered readers to load the entire employee information contained in the contacts.csv file into a single Coherence cache.

    The application should contain the code to parse the employee information in the data file. Once you have this information, construct the individual contacts to put into the cache. To conserve processing effort and minimize network traffic, use the putAll method to load the cache.

    Example 4-4 illustrates a possible solution.

    Example 4-4 Sample Cache Loading Program

    package com.tangosol.examples.contacts;
     
    import com.tangosol.net.CacheFactory;
    import com.tangosol.net.NamedCache;
     
    import com.tangosol.examples.model.ContactId;
    import com.tangosol.examples.model.Address;
    import com.tangosol.examples.model.Phone;
    import com.tangosol.examples.model.Contact;
     
    import java.io.BufferedReader;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
     
    import java.sql.Date;
     
    import java.util.HashMap;
    import java.util.Map;
     
     
    /**
    * LoaderExample loads contacts into the cache from a files.
    *
    */
    public class LoaderExample
        {
        // ----- static methods -------------------------------------------------
     
        /**
        * Load contacts.
        */
        public static void main (String[] asArg)
                throws IOException
            {
            String sFile  = asArg.length > 0 ? asArg[0] : FILENAME;
            String sCache = asArg.length > 1 ? asArg[1] : CACHENAME;
     
            System.out.println("input file: " + sFile);
            System.out.println("cache name: " + sCache);
     
            new LoaderExample().load(CacheFactory.getCache(sCache),
                    new FileInputStream(sFile));
            CacheFactory.shutdown();
            }
     
        /**
        * Load cache from stream.
        *
        */
        public void load(NamedCache cache, InputStream in)
                throws IOException
            {
            BufferedReader reader   = new BufferedReader(new InputStreamReader(in));
            Map            mapBatch = new HashMap(1024);
            String         sRecord;
            int            cRecord  = 0;
     
            while ((sRecord = reader.readLine()) != null)
                {
                // parse record
                String[]      asPart     = sRecord.split(",");
                int           ofPart     = 0;
                String        sFirstName =  asPart[ofPart++];
                String        sLastName  =  asPart[ofPart++];
                ContactId          id    = new ContactId(sFirstName, sLastName);
                Address       addrHome   = new Address(
                                             /*streetline1*/ asPart[ofPart++],
                                             /*streetline2*/ asPart[ofPart++],
                                             /*city*/        asPart[ofPart++],
                                             /*state*/       asPart[ofPart++],
                                             /*zip*/         asPart[ofPart++],
                                             /*country*/     asPart[ofPart++]);
                Address       addrWork   = new Address(
                                             /*streetline1*/ asPart[ofPart++],
                                             /*streetline2*/ asPart[ofPart++],
                                             /*city*/        asPart[ofPart++],
                                             /*state*/       asPart[ofPart++],
                                             /*zip*/         asPart[ofPart++],
                                             /*country*/     asPart[ofPart++]);
                Map           mapTelNum  = new HashMap();
     
                for (int c = asPart.length - 1; ofPart < c; )
                    {
                    mapTelNum.put(/*type*/ asPart[ofPart++], new Phone(
                        /*access code*/  Short.parseShort(asPart[ofPart++]),
                        /*country code*/ Short.parseShort(asPart[ofPart++]),
                        /*area code*/    Short.parseShort(asPart[ofPart++]),
                        /*local num*/    Integer.parseInt(asPart[ofPart++])));
                    }
                Date dtBirth = new Date(Long.parseLong(asPart[ofPart]));
     
                // Construct Contact and add to batch
                mapBatch.put(id, new Contact(sFirstName, sLastName, addrHome,
                        addrWork, mapTelNum, dtBirth));
     
                ++cRecord;
                if  (cRecord % 1024 == 0)
                    {
                    // load batch
                    cache.putAll(mapBatch);
                    mapBatch.clear();
                    System.out.print('.');
                    System.out.flush();
                    }
                }
     
            if (!mapBatch.isEmpty())
                {
                // load final batch
                cache.putAll(mapBatch);
                }
     
            System.out.println("Added " + cRecord + " entries to cache");
            }
     
     
        // ----- constants ------------------------------------------------------
     
        /**
        * Default cache name.
        */
        public static final String CACHENAME = "ContactsCache";
     
        /**
        * Default file name.
        */
        public static final String FILENAME = "contacts.csv";
        }
    
  7. Add the cache configuration file (C:\home\oracle\labs) to the project's classpath. See "Adding JARS and Libraries to the Project Classpath" if you need detailed information.

  8. Add the path to the cache configuration to the Java Options field of the Project Properties>Run/Debug/Profile.

    -Dtangosol.coherence.cacheconfig=\home\oracle\labs\contacts-cache-config.xml 
    
  9. Edit the contacts-cache-server.cmd file to add the compiled classes for the Loading project (in this case, C:\home\oracle\labs\Loading\classes). Ensure that the path to the directory where the cache configuration file is stored (in this case, C:\home\oracle\labs) is already listed.

    The contacts-cache-server.cmd file should look similar to Example 4-5:

    Example 4-5 Sample contacts-cache-server.cmd File

    @echo off
    setlocal
     
    if (%COHERENCE_HOME%)==() (
        set COHERENCE_HOME=c:\oracle\product\coherence
    ) 
     
     
    set COH_OPTS=%COH_OPTS% -server -cp %COHERENCE_HOME%\lib\coherence.jar;C:\home\oracle\labs\Contacts\classes;C:\home\oracle\labs\Loading\classes;C:\home\oracle\labs;
    set COH_OPTS=%COH_OPTS% -Dtangosol.coherence.cacheconfig=\home\oracle\labs\contacts-cache-config.xml
     
    java %COH_OPTS% -Xms1g -Xmx1g -Xloggc: com.tangosol.net.DefaultCacheServer %2 %3 %4 %5 %6 %7
     
    :exit
    
  10. Start the cache server with the contacts-cache-server.cmd file.

  11. Run DataGenerator from JDeveloper, then run LoaderExample.

    A stream of employee contact information should appear in the JDeveloper output window. Figure 4-1 illustrates an example.

    Figure 4-1 Output from the Sample Cache Loading Program

    Output from the Sample Cache Loading Program

4.3 Querying and Aggregating Data in the Cache

This exercise introduces the concept of querying and aggregating data in a cache. This exercise demonstrates how to:

After putting complex objects in the named caches, you look at querying and aggregating information within the grid. The com.tangosol.util.QueryMap interface provides methods for managing the values or keys within a cache. You can use filters to restrict your results. You can also define indexes to optimize your queries.

Because Coherence serializes information when storing, you also have the overhead of deserializing when querying. When indexes are added, the values kept in the index itself are deserialized and therefore, offer quicker access time. The objects that are indexed are always kept serialized.

Some of the more useful methods in the QueryMap interface are:

It is important to note that filtering occurs at Cache Entry Owner level. In a partitioned topology, filtering can be done in parallel because it is the primary partitions that do the filtering. The QueryMap interface uses the Filter classes. You can find more information on these classes in the API for the com.tangosol.util.filter package.

All Coherence NamedCaches implement the com.tangosol.util.QueryMap interface. This allows NamedCaches to support the searching for keys or entries in a cache that satisfy some condition. The condition can being represented as an object that implements the com.tangosol.util.Filter interface.

The com.tangosol.util.filter package contains several predefined classes that provide implementations of standard query expressions. Examples of these classes include GreaterFilter, GreaterEquals, LikeFilter, NotEqualsFilter, InFilter, and so on. You can use these filters to construct and compose object-based equivalents of most SQL WHERE clause expressions.

Note:

Coherence does not provide a SQLFilter because it is unlikely that the objects placed in a cache are modeled in a relational manner, that is, using rows and columns (as they are typically represented in a database). Additionally, it is common that objects placed in a cache are not easily modeled using relational models, for example, large blobs.

The Filter classes use standard Java method reflection to represent test conditions. For example, the following filter represents the condition where the value returned from the getHomeAddress.getState method on an object in a cache is for Massachusetts (MA):

(new EqualsFilter("getHomeAddress.getState", "MA");

If the object tested with this filter fails to have a getSymbol method, then the test will fail.

A couple of examples will make things clearer:

In addition to the entrySet and keySet methods defined by the QueryMap interface, Coherence supports the definition of indexes, using the addIndex method, to improve query performance. Unlike relational database systems, where indexes are defined according to well-known and strictly enforced collections of named columns (that is, a schema), Coherence does not have a schema. Though lacking a formal schema for data allows for significant flexibility and polymorphism, within applications, it means that an approach different from that of traditional database systems is required to define indexes and therefore, increase query performance.

To define the values that are to be indexed for each object placed in a cache, Coherence introduces the concept of a ValueExtractor. A com.tangosol.util.ValueExtractor is a simple interface that defines an "extract" method. If given an object parameter, a ValueExtractor returns some value based on the parameter.

A simple example of a ValueExtractor implementation is the com.tangosol.util.extractor.ReflectionExtractor, which uses reflection to return the result of a method call on an object. For example:

new ReflectionExtractor("getCity")

ValueExtractors may be used throughout the Coherence API. Typically, however, they are used to define indexes.

An especially useful type of extractor is the ChainedExtractor. This is a composite ValueExtractor implementation based on an array of extractors. Extractors in the array are applied sequentially left-to-right, so a result of a previous extractor serves as a target object for a next one. For example:

new ChainedExtractor(new ReflectionExtractor("getHomeAddress"),new ReflectionExtractor("getState"))

This example assumes that HomeAddress and State belong to a complex Contact object. ChainedExtractor first uses reflection to call getHomeAddress on each cached Contact object, and then uses reflection to call getState on the set of returned HomeAddress.

To aggregate and query data in the cache:

  1. Create a new Java class called QueryExample to perform your queries. Ensure that the class has a main method. See "Creating a Java Class" if you need detailed information.

    Use the entrySet method to get the employee contact information for:

    • all employees who live in Massachusetts. Hint:

      cache.entrySet(new EqualsFilter("getHomeAddress.getState", "MA"));
      
    • all employees who live in Massachusetts and work elsewhere. Hint:

      cache.entrySet(new AndFilter(
               new EqualsFilter("getHomeAddress.getState", "MA"),
               new NotEqualsFilter("getWorkAddress.getState", "MA")));
      
    • all employees whose city name begins with 'S'. Hint:

      cache.entrySet(new LikeFilter("getHomeAddress.getCity", "S%"));
      
    • all employees with last name beginning with 'S' that live in Massachusetts. Use both key and value in the query. Hint:

      cache.entrySet(new AndFilter(
           new LikeFilter(new KeyExtractor("getLastName"), "S%", 
               (char) 0, false),
           new EqualsFilter("getHomeAddress.getState", "MA")));
      
    • all employees who are older than a specified age. Hint:

      final int nAge = 42;
          setResults = cache.entrySet(new GreaterFilter("getAge", nAge));
      

    Use indexes to improve performance. Hints: Find the addIndex method in the Javadoc for the QueryMap interface.

    Example 4-6 illustrates a possible solution.

    Example 4-6 Sample QueryExample Class

    package com.oracle.coherence.handson;
     
    import com.tangosol.net.CacheFactory;
    import com.tangosol.net.NamedCache;
     
    import com.tangosol.util.extractor.ChainedExtractor;
    import com.tangosol.util.extractor.KeyExtractor;
    import com.tangosol.util.extractor.ReflectionExtractor;
     
    import com.tangosol.util.filter.AlwaysFilter;
    import com.tangosol.util.filter.AndFilter;
    import com.tangosol.util.filter.EqualsFilter;
    import com.tangosol.util.filter.GreaterFilter;
    import com.tangosol.util.filter.LikeFilter;
    import com.tangosol.util.filter.NotEqualsFilter;
     
    import java.util.Iterator;
    import java.util.Set;
     
     
    /**
    * QueryExample runs sample queries for contacts.
    *
    */
    public class QueryExample{
      
        // ----- QueryExample methods ---------------------------------------
     
        public static void main(String[] args) {
          NamedCache cache = CacheFactory.getCache("ContactsCache");
           query(cache);
        }
        /**
        * Perform the example queries
        *
        */
         public static void query(NamedCache cache)
            {
             // Add indices to make queries more efficient
            ReflectionExtractor reflectAddrHome =
                    new ReflectionExtractor("getHomeAddress");
    
            // Add an index for the age
            cache.addIndex(new ReflectionExtractor("getAge"), true, null);
    
            // Add index for state within home address
           cache.addIndex(new ChainedExtractor(reflectAddrHome,
                    new ReflectionExtractor("getState")), true, null);
    
    // Add index for state within work address
            cache.addIndex(new ChainedExtractor(
                    new ReflectionExtractor("getWorkAddress"),
                    new ReflectionExtractor("getState")), true, null);
    
            // Add index for city within home address
            cache.addIndex(new ChainedExtractor(reflectAddrHome,
                    new ReflectionExtractor("getCity")), true, null);
     
            // Find all contacts who live in Massachusetts
            Set setResults = cache.entrySet(new EqualsFilter(
                    "getHomeAddress.getState", "MA"));
            printResults("MA Residents", setResults);
     
            // Find all contacts who live in Massachusetts and work elsewhere
            setResults = cache.entrySet(new AndFilter(
                    new EqualsFilter("getHomeAddress.getState", "MA"),
                    new NotEqualsFilter("getWorkAddress.getState", "MA")));
            printResults("MA Residents, Work Elsewhere", setResults);
     
            // Find all contacts whose city name begins with 'S'
            setResults = cache.entrySet(new LikeFilter("getHomeAddress.getCity",
                    "S%"));
            printResults("City Begins with S", setResults);
     
            final int nAge = 42;
            // Find all contacts who are older than nAge
            setResults = cache.entrySet(new GreaterFilter("getAge", nAge));
            printResults("Age > " + nAge, setResults);
     
            // Find all contacts with last name beginning with 'S' that live
            // in Massachusetts. Uses both key and value in the query.
            setResults = cache.entrySet(new AndFilter(
                    new LikeFilter(new KeyExtractor("getLastName"), "S%",
                                   (char) 0, false),
                    new EqualsFilter("getHomeAddress.getState", "MA")));
            printResults("Last Name Begins with S and State Is MA", setResults);
     
            }
     
        /**
        * Print results of the query
        *
        * @param sTitle      the title that describes the results
        * @param setResults  a set of query results
        */
        private static void printResults(String sTitle, Set setResults)
            {
            System.out.println(sTitle);
            for (Iterator iter = setResults.iterator(); iter.hasNext(); )
                {
                System.out.println(iter.next());
                }
            }
        }
    
  2. Stop all running cache servers. Restart the Contacts cache server with contacts-cache-server.cmd. Run the DataGenerator file, the LoadExample file, then the QueryExample file. After printing all of the contact information in the cache, it displays the results of the queries. The results should look similar to Example 4-1.

    Figure 4-2 Output of the QueryExample Class

    Output of the QueryExample Class
  3. Add code in QueryExample.java to perform aggregations on the data in the cache.

    An EntryAggregator (com.tangosol.util.InvocableMap.EntryAggregator) enables you to perform operations on all or a specific set of objects and return an aggregation. EntryAggregators are essentially agents that execute services in parallel against the data within the cluster.

    Aggregations are performed in parallel and can benefit from the addition of cluster members.

    There are two ways of aggregating: aggregate over a collection of keys or by specifying a filter. Example 4-7 illustrates the methods that perform these aggregations.

    Example 4-7 Methods to Aggregate Over Keys or by Specifying Filters

    Object aggregate(Collection keys, InvocableMap.entryAggregator agg)
    
    Object aggregate(Filter filter, InvocableMap.entryAggregator agg)
    

    The following example uses a filter.

    1. Using aggregations, write code in the QueryExample class to calculate the following:

      —the number of employees that are greater than a specified age. Hint: use the GreaterFilter and the Count class:

      cache.aggregate(new GreaterFilter("getAge", nAge), new Count())
      

      —lowest age of the set of employees. Hint: use the AlwaysFilter and the LongMin class:

      cache.aggregate(AlwaysFilter.INSTANCE, new LongMin("getAge"))
      

      —highest age of the set of employees. Hint: use the AlwaysFilter and the LongMax class:

      cache.aggregate(AlwaysFilter.INSTANCE, new LongMax("getAge"))
      

      —average age of employees. Hint: use the AlwaysFilter and the DoubleAverage class:

      cache.aggregate(AlwaysFilter.INSTANCE, new DoubleAverage("getAge")
      
    2. Import the Count, DoubleAverage, LongMax, and LongMin aggregator classes.

      import com.tangosol.util.aggregator.Count;
      import com.tangosol.util.aggregator.DoubleAverage;
      import com.tangosol.util.aggregator.LongMax;
      import com.tangosol.util.aggregator.LongMin;
      

      The QueryExample.java file should now look similar to this:

      Example 4-8 QueryExample with Aggregation

      package com.oracle.coherence.handson;
      
      import com.tangosol.net.CacheFactory;
      import com.tangosol.net.NamedCache;
      
      import com.tangosol.util.aggregator.Count;
      import com.tangosol.util.aggregator.DoubleAverage;
      import com.tangosol.util.aggregator.LongMax;
      import com.tangosol.util.aggregator.LongMin;
      
      import com.tangosol.util.extractor.ChainedExtractor;
      import com.tangosol.util.extractor.KeyExtractor;
      import com.tangosol.util.extractor.ReflectionExtractor;
      
      import com.tangosol.util.filter.AlwaysFilter;
      import com.tangosol.util.filter.AndFilter;
      import com.tangosol.util.filter.EqualsFilter;
      import com.tangosol.util.filter.GreaterFilter;
      import com.tangosol.util.filter.LikeFilter;
      import com.tangosol.util.filter.NotEqualsFilter;
      
      import java.util.Iterator;
      import java.util.Set;
      
      /**
      * QueryExample runs sample queries for contacts.
      */
      public class QueryExample{
        
          // ----- QueryExample methods ---------------------------------------
      
          public static void main(String[] args) {
            NamedCache cache = CacheFactory.getCache("ContactsCache");
             query(cache);
          }
          /**
          * Perform the example queries
          *
          */
           public static void query(NamedCache cache)
              {
               // Add indices to make queries more efficient
              ReflectionExtractor reflectAddrHome =
                      new ReflectionExtractor("getHomeAddress");
      
              cache.addIndex(new ReflectionExtractor("getAge"), true, null);
              cache.addIndex(new ChainedExtractor(reflectAddrHome,
                      new ReflectionExtractor("getState")), true, null);
              cache.addIndex(new ChainedExtractor(
                      new ReflectionExtractor("getWorkAddress"),
                      new ReflectionExtractor("getState")), true, null);
              cache.addIndex(new ChainedExtractor(reflectAddrHome,
                      new ReflectionExtractor("getCity")), true, null);
      
              // Find all contacts who live in Massachusetts
              Set setResults = cache.entrySet(new EqualsFilter(
                      "getHomeAddress.getState", "MA"));
              printResults("MA Residents", setResults);
      
              // Find all contacts who live in Massachusetts and work elsewhere
              setResults = cache.entrySet(new AndFilter(
                      new EqualsFilter("getHomeAddress.getState", "MA"),
                      new NotEqualsFilter("getWorkAddress.getState", "MA")));
              printResults("MA Residents, Work Elsewhere", setResults);
      
              // Find all contacts whose city name begins with 'S'
              setResults = cache.entrySet(new LikeFilter("getHomeAddress.getCity",
                      "S%"));
              printResults("City Begins with S", setResults);
      
              final int nAge = 42;
              // Find all contacts who are older than nAge
              setResults = cache.entrySet(new GreaterFilter("getAge", nAge));
              printResults("Age > " + nAge, setResults);
      
              // Find all contacts with last name beginning with 'S' that live
              // in Massachusetts. Uses both key and value in the query.
              setResults = cache.entrySet(new AndFilter(
                      new LikeFilter(new KeyExtractor("getLastName"), "S%",
                                     (char) 0, false),
                      new EqualsFilter("getHomeAddress.getState", "MA")));
              printResults("Last Name Begins with S and State Is MA", setResults);
       
             // Count contacts who are older than nAge
              System.out.println("count > " + nAge + ": "+ cache.aggregate(
                      new GreaterFilter("getAge", nAge), new Count()));
        
              // Find minimum age
              System.out.println("min age: " + cache.aggregate(AlwaysFilter.INSTANCE,
                      new LongMin("getAge")));
        
              // Calculate average age
              System.out.println("avg age: " + cache.aggregate(AlwaysFilter.INSTANCE,
                      new DoubleAverage("getAge")));
        
              // Find maximum age
              System.out.println("max age: " + cache.aggregate(AlwaysFilter.INSTANCE,
                      new LongMax("getAge")));
              }
        
          /**
          * Print results of the query
          *
          */
          private static void printResults(String sTitle, Set setResults)
              {
              System.out.println(sTitle);
              for (Iterator iter = setResults.iterator(); iter.hasNext(); )
                  {
                  System.out.println(iter.next());
                  }
              }
          }
      
    3. Stop and start the cache server after making this change.

    4. Start the Contacts cache server with the contacts-cache-server.cmd file. Run the QueryExample application.

      You should see out output similar to Example 4-3 in JDeveloper.

      Figure 4-3 Output from the Aggregators

      Output from the Aggregators