In this exercise, you learn how to populate a Coherence cache with domain objects that are read from text files. It highlights the use of APIs from the Coherence aggregator
, extractor
, and filter
packages.
This chapter contains the following sections:
Until now, you put objects into the cache and retrieved them individually. Each call to the put
method can result in increased network traffic, especially for partitioned and replicated caches. Additionally, each call to a put
method returns the object it just replaced in the cache, which adds more unnecessary overhead. By using the putAll
method, loading the cache can be made much more efficient.
To perform the tasks in this chapter, you must first complete the project described in Chapter 4, "Working with Complex Objects." You must also be 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.
This exercise shows you how to create a console application that populates a Coherence cache with domain objects. The application will use the Coherence com.tangosol.io.pof.PortableObject
implementation to serialize the objects into Portable Object format (POF).
In the exercise, you create a key that helps to get the Contact
object, a generator to provide data for the cache, and a loader to load the cache.
To create a class that contains the key for a domain object:
Create a new Application Client Project called Loading
. Select CoherenceConfig from the Configuration drop-down list. In the Application Client Module page of the New Application Client Project wizard, deselect the Create a default Main class checkbox.
See "Creating and Caching Complex Objects" for information on creating a new project.
Add the classes and files related to the Address
, PhoneNumber
, and Contact
classes that you created in an earlier exercise (Contacts
). These files can be found in c:\home\oracle\workspace\Contacts\appClientModule
and c:\home\oracle\workspace\Contacts\build\classes
directories.
Right click the Loading project in the Project Explorer and select Properties. In the Properties for Loading dialog box, select Java Build Path. In the Projects tab, click Add. Select the Contacts project from the Required Project Selection dialog box, as illustrated in Figure 5-1.
Figure 5-1 Adding Folders to the Project Build Path
In the Order and Export tab, use the Up and Down buttons to move Contacts to the top of the list. The contents of the tab should look similar to Figure 5-2.
Figure 5-2 Contents of the Order and Export Tab for the Loading Project
Create a ContactId
class that provides a key to the employee for whom information is tracked. See "Creating a Java Class" for 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 get the Contact
object.
Because this class uses POF serialization, it must implement the PortableObject
interface, 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 the hashCode
and equals
object methods 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 the equals
method returns true must serialize identically; most built-in Java types meet this requirement.
Example 5-1 illustrates a possible implementation of the ContactId
class.
Example 5-1 Simple Contact ID Class
package com.oracle.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; }
Edit the POF configuration file. Add a <user-type>
entry for the ContactId
class to the contacts-pof-config.xml
file. The file looks similar to Example 5-2.
Example 5-2 POF Configuration File with the ContactId Entry
<?xml version="1.0"?> <pof-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.oracle.com/coherence/coherence-pof-config" xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-pof-config coherence-pof-config.xsd"> <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.handson.Contact</class-name> </user-type> <user-type> <type-id>1002</type-id> <class-name>com.oracle.handson.Address</class-name> </user-type> <user-type> <type-id>1003</type-id> <class-name>com.oracle.handson.PhoneNumber</class-name> </user-type> <user-type> <type-id>1004</type-id> <class-name>com.oracle.handson.ContactId</class-name> </user-type> </user-type-list> <allow-interfaces>true</allow-interfaces> <allow-subclasses>true</allow-subclasses> </pof-config>
Create a Java class named DataGenerator
to generate random employee contact names and addresses. See "Creating a Java Class" for detailed information.
Use the Address
, PhoneNumber
, and Contact
classes that you created in an earlier exercise. Use java.util.Random
to generate some random names, addresses, telephone numbers, and ages.
Example 5-3 illustrates a possible implementation of the data generator. This implementation creates a text file, contacts.cvs
, that contains the employee contact information.
Example 5-3 Sample Data Generation Class
package com.oracle.handson; import com.oracle.handson.Address; import com.oracle.handson.Contact; import com.oracle.handson.PhoneNumber; 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] : 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 PhoneNumber getRandomPhone() { int[] anPhone = getRandomPhoneDigits(); return new PhoneNumber((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" }; /** * Default contacts file name. */ public static final String FILENAME = "contacts.csv"; }
The DataGenerator
class generates, at random, information about an employee. The information includes the employee's name, home address, work address, home telephone number, work telephone number, and the employee's age. The information is stored in a CSV-formatted file named contacts.csv
. This file follows the standard rules for a CSV-formatted file, where there is one record per line, and individual fields within the record are separated by commas.
Example 5-4 illustrates the first few entries of the contacts.csv
file.
Example 5-4 Contents of the contacts.csv File
John,Dvcqbvcp,669 Beacon St.,,Tetuvusz,CA,68457,US,Yoyodyne Propulsion Systems,330 Lectroid Rd.,Grover's Mill,KS,30344,US,home,11,98,183,8707139,work,11,96,425,1175949,63072000000 John,Fxsr,549 Beacon St.,,Jutaswmaby,MI,16315,US,Yoyodyne Propulsion Systems,330 Lectroid Rd.,Grover's Mill,CT,60309,US,home,11,20,40,3662989,work,11,41,187,3148474,-189216000000 John,Gmyrolvfyd,73 Beacon St.,,Lpnztf,AR,82667,US,Yoyodyne Propulsion Systems,330 Lectroid Rd.,Grover's Mill,NY,42297,US,home,11,22,17,8579970,work,11,35,338,9286245,-567648000000 John,Efmpjlbj,85 Beacon St.,,Wyswpb,AK,29590,US,Yoyodyne Propulsion Systems,330 Lectroid Rd.,Grover's Mill,NJ,06219,US,home,11,20,744,4331451,work,11,39,911,9104590,-157680000000 ...
Create a Java class called LoaderExample
. See "Creating a Java Class" for detailed information.
Implement the class to load the cache with employee data generated by the program described in "Create the Data Generator". Use input streams and buffered readers to load the employee information in the contacts.csv
file into a single Coherence cache.
Add code to parse the employee information in the data file. After you have this information, create 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 5-5 illustrates a possible implementation of the LoaderExample
class.
Example 5-5 Sample Cache Loading Program
package com.oracle.handson; import com.tangosol.net.CacheFactory; import com.tangosol.net.NamedCache; import com.oracle.handson.ContactId; import com.oracle.handson.Address; import com.oracle.handson.PhoneNumber; import com.oracle.handson.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 file. * */ public class LoaderExample { // ----- static methods ------------------------------------------------- /** * Load contacts. */ public static void main (String[] asArg) throws IOException { String sFile = asArg.length > 0 ? asArg[0] : DataGenerator.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 PhoneNumber( /*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 Contact con1 = new Contact(sFirstName, sLastName, addrHome, addrWork, mapTelNum, dtBirth); System.out.println(con1); mapBatch.put(id, con1); ++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"; }
To run the cache loading example:
Stop any running cache servers. See "Stopping Cache Servers" for detailed information.
Start a cache server with the ContactsCacheServer
file.
Right click the project and select Run As then Run Configurations to edit the ContactsCacheServer
configuration. In the Main tab, click Browse and select the Loading project.
In the Classpath tab, select User Entries and click Add Projects. In the Project Selection dialog box, select the Loading project. Click OK.
In the Common tab, click Shared file and browse for the Loading project.
Click Apply, then Run.
Create a run configuration for DataGenerator
. Right click DataGenerator
in the Project Explorer and select Run As. In the Run Configurations dialog box select Oracle Coherence and click the New Configuration icon.
In the Name field, enter DataGenerator
.
In the Project field in the Main tab, enter Loading
. In the Main class field, enter com.oracle.handson.DataGenerator
.
In the General tab of the Coherence tab, browse to the c:\home\oracle\workspace\Contacts\appClientModule\contacts-cache-config.xml
file in the Cache configuration descriptor field. Select the Disabled (cache client) button. Enter 3155
in the Cluster port field. Click Apply.
In the Other tab, scroll down to the tangosol.pof.config field. Enter the absolute path to the POF configuration file contacts-pof-config.xml
. Click Apply.
In the Common tab, select Shared file and browse for the Loading directory.
Examine the contents of Loading in the Classpath tab. Contacts should appear as one of the entries under Loading, as in Figure 5-3.
Figure 5-3 Classpath for the DataGenerator Program
Create a run configuration for LoaderExample
.
In the Name field, enter LoaderGenerator
In the Project field in the Main tab, enter Loading
. In the Main class field, enter com.oracle.handson.LoaderExample
.
In the General tab of the Coherence tab, browse to the c:\home\oracle\workspace\Contacts\appClientModule\contacts-cache-config.xml
file in the Cache configuration descriptor field. Select the Disabled (cache client) button. Enter 3155
in the Cluster port field. Click Apply.
In the Other tab, scroll down to the tangosol.pof.config field. Enter the absolute path to the POF configuration file contacts-pof-config.xml
. Click Apply.
Examine the contents of the Classpath tab. Contacts should appear as one of the entries under Loading, as in Figure 5-4. If it does not appear, click User Entries, then add the project with the Add Project button.
Figure 5-4 Classpath for the LoaderExample Program
Run the DataGenerator
configuration from the Run Configurations dialog box. Then run the LoaderExample
configuration.
The DataGenerator
run configuration generates a data file; you should not see any output or response in the Eclipse console window. The LoaderGenerator
run configuration sends a stream of employee contact information, similar to Example 5-6, to the Eclipse console window.
Example 5-6 Output from the Sample Cache Loading Program
...
2012-08-16 15:47:04.413/2.266 Oracle Coherence GE 12.1.2.0 <D5> (thread=Invocation:Management, member=2): Service Management joined the cluster with senior service member 1
2012-08-16 15:47:04.413/2.266 Oracle Coherence GE 12.1.2.0 <Info> (thread=Cluster, member=2): Loaded POF configuration from "file:/C:/home/oracle/workspace/Contacts/appClientModule/contacts-pof-config.xml"
2012-08-16 15:47:04.413/2.266 Oracle Coherence GE 12.1.2.0 <Info> (thread=Cluster, member=2): Loaded included POF configuration from "jar:file:/C:/coherence360/coherence/lib/coherence.jar!/coherence-pof-config.xml"
2012-08-16 15:47:04.413/2.266 Oracle Coherence GE 12.1.2.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=2): Service PartitionedPofCache joined the cluster with senior service member 1
2012-08-16 15:47:04.413/2.266 Oracle Coherence GE 12.1.2.0 <D4> (thread=DistributedCache:PartitionedPofCache, member=2): Asking member 1 for 128 primary partitions
John Hwolru
Addresses
Home: 742 Beacon St.
Icymz, OH 74135
US
Work: Yoyodyne Propulsion Systems
330 Lectroid Rd.
Grover's Mill, WY 20222
US
Telephone Numbers
work: +11 50 928 2637858
home: +11 72 403 7946780
Birth Date: 1981-12-28
...
John Unglg
Addresses
Home: 973 Beacon St.
Cgarhej, MI 10517
US
Work: Yoyodyne Propulsion Systems
330 Lectroid Rd.
Grover's Mill, GA 81835
US
Telephone Numbers
work: +11 8 925 5233805
home: +11 95 108 2947077
Birth Date: 1965-01-01
John Lkkwgw
Addresses
Home: 806 Beacon St.
Zlpft, GU 55786
US
Work: Yoyodyne Propulsion Systems
330 Lectroid Rd.
Grover's Mill, WV 88125
US
Telephone Numbers
work: +11 45 321 6385646
home: +11 87 371 2109053
Birth Date: 1987-12-27
Added 1000 entries to cache
2012-08-16 15:47:04.575/2.625 Oracle Coherence GE 12.1.2.0 <D5> (thread=Invocation:Management, member=n/a): Service Management left the cluster
2012-08-16 15:47:04.591/2.641 Oracle Coherence GE 12.1.2.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=n/a): Service PartitionedPofCache left the cluster
2012-08-16 15:47:04.622/2.672 Oracle Coherence GE 12.1.2.0 <D5> (thread=Cluster, member=n/a): Service Cluster left the cluster
This exercise introduces querying and aggregating data in a cache. This exercise shows you how to:
Query the cache for specific data
Aggregate information within the cache
After putting complex objects in the named caches, you can query and aggregate 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 it, you also have the overhead of deserializing when querying information. When indexes are added, the values kept in the index itself are deserialized and, therefore, are quicker to access. The objects that are indexed are always kept serialized.
Some of the often-used methods in the QueryMap
interface are:
Set
entrySet(Filterfilter)
, which returns a set of entries that are contained in the map that satisfy the filter
addIndex(ValueExtractorextractor,booleanfOrdered, Comparator comparator)
, which adds an index
Set
keySet(Filter filter)
, which is similar to entrySet
, but returns keys, not values
It is important to note that filtering occurs at Cache Entry Owner level. In a partitioned topology, filtering can be performed 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 NamedCache
objects implement the com.tangosol.util.QueryMap
interface. This enables NamedCache
objects to support searching for keys or entries in a cache that satisfy a specified condition. The condition can be 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 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 does not have a get
method, then the test fails.
Here are additional examples of using the Filter
classes:
Return a set of people who live in a city whose name begins with the letter S
:
Set sPeople = cache.entrySet(new LikeFilter("getHomeAddress.getCity","S%");
Return a set containing people over the age of 42:
final int nAge = 42;// Find all contacts who are older than nAge Set sSeniors = cache.entrySet(new GreaterFilter("getAge", nAge));
In addition to the entrySet
and keySet
methods defined by the QueryMap
interface, Coherence lets you define indexes to improve query performance by using the addIndex
method. 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. Because there is no schema, you define indexes differently from a traditional database.
To define the values that are to be indexed for each object placed in a cache, Coherence introduces the concept of a value extractor. The com.tangosol.util.ValueExtractor
interface defines an extract
method. If given an object parameter, a ValueExtractor
implementation returns a value based on the parameter.
A simple example of a ValueExtractor
implementation is the com.tangosol.util.extractor.ReflectionExtractor
interface, which uses reflection to return the result of a method call on an object, for example:
new ReflectionExtractor("getCity")
You can use value extractors 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, from left to right. The result of a previous extractor becomes the target object for a next extractor, for example:
new ChainedExtractor(new ReflectionExtractor("getHomeAddress"), new ReflectionExtractor("getState"))
This example assumes that the HomeAddress
and State
objects belong to a complex Contact
object. ChainedExtractor
first uses reflection to call the getHomeAddress
method on each cached Contact
object, and then uses reflection to call the getState
method on the set of returned HomeAddress
objects.
Create a new Java class called QueryExample
to perform your queries. Ensure that the class has a main
method. See "Creating a Java Class" for detailed information.
Use the entrySet
method to get the employee contact information for:
All employees who live in Massachusetts:
cache.entrySet(new EqualsFilter("getHomeAddress.getState", "MA"));
All employees who live in Massachusetts and work elsewhere:
cache.entrySet(new AndFilter( new EqualsFilter("getHomeAddress.getState", "MA"), new NotEqualsFilter("getWorkAddress.getState", "MA")));
All employees whose city name begins with an S
:
cache.entrySet(new LikeFilter("getHomeAddress.getCity", "S%"));
All employees whose last name begins with an S
and live in Massachusetts. Use both the key and value in the query. Note that the cache entries use ContactId
objects as the keys. You can use the KeyExtractor
to get these values. KeyExtractor
is a specialized value extractor that indicates that a query be run against the key objects, rather than the values:
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. Hint: Find the addIndex
method in the Javadoc for the QueryMap
interface.
Example 5-7 illustrates a possible implementation of the QueryExample
class.
Example 5-7 Sample QueryExample Class
package com.oracle.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 indexes 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()); } } }
To run the query example:
Stop all running cache servers. See "Stopping Cache Servers" for more information.
Create a run configuration for the QueryExample
class.
Right click QueryExample
in the Project Navigator and select Run As then Run Configurations. Select Oracle Coherence and click the New launch configuration icon.
In the Name field, enter QueryExample
.
In the Project field in the Main tab, enter Loading
. In the Main class field, enter com.oracle.handson.QueryExample
.
In the General tab of the Coherence tab, browse to the c:\home\oracle\workspace\Contacts\appClientModule\contacts-cache-config.xml
file in the Cache configuration descriptor field. Select the Disabled (cache client) button. Enter 3155
in the Cluster port field. Click Apply.
In the Other tab, scroll down to the tangosol.pof.config field. Enter the absolute path to the POF configuration file contacts-pof-config.xml
. Click Apply.
Examine the contents of the Classpath tab. The Contacts project should appear under the Loading project in User Entries.
Restart the Contacts
CacheServer
.
Run the DataGenerator
class, the LoaderExample
class, then the QueryExample
class.
After printing all of the contact information in the cache, the QueryExample
file displays the results of the queries. The results should look similar to Example 5-8.
Example 5-8 Results of the QueryExample Program
TcpRing{Connections=[1]} IpMonitor{AddressListSize=0} 2012-08-16 16:15:01.778/2.094 Oracle Coherence GE 12.1.2.0 <D5> (thread=Invocation:Management, member=5): Service Management joined the cluster with senior service member 1 2012-08-16 16:15:01.825/2.141 Oracle Coherence GE 12.1.2.0 <Info> (thread=main, member=5): Loaded Reporter configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/reports/report-group.xml" 2012-08-16 16:15:02.137/2.453 Oracle Coherence GE 12.1.2.0 <Info> (thread=DistributedCache:PartitionedPofCache, member=5): Loaded POF configuration from "file:/C:/home/oracle/workspace/Contacts/build/classes/contacts-pof-config.xml" 2012-08-16 16:15:02.169/2.485 Oracle Coherence GE 12.1.2.0 <Info> (thread=DistributedCache:PartitionedPofCache, member=5): Loaded included POF configuration from "jar:file:/C:/oracle/product/coherence/lib/coherence.jar!/coherence-pof-config.xml" 2012-08-16 16:15:02.231/2.547 Oracle Coherence GE 12.1.2.0 <D5> (thread=DistributedCache:PartitionedPofCache, member=5): Service PartitionedPofCache joined the cluster with senior service member 1 MA Residents ConverterEntry{Key="John Ueccc", Value="John Ueccc Addresses Home: 285 Beacon St. Oxtqwisgti, MA 41063 US Work: Yoyodyne Propulsion Systems 330 Lectroid Rd. Grover's Mill, VA 28107 US Telephone Numbers work: +11 48 835 1876678 home: +11 78 482 1247744 Birth Date: 1972-12-30"} ... MA Residents, Work Elsewhere ConverterEntry{Key="John Ueccc", Value="John Ueccc Addresses Home: 285 Beacon St. Oxtqwisgti, MA 41063 US Work: Yoyodyne Propulsion Systems 330 Lectroid Rd. Grover's Mill, VA 28107 US Telephone Numbers work: +11 48 835 1876678 home: +11 78 482 1247744 Birth Date: 1972-12-30"} ... City Begins with S ConverterEntry{Key="John Frepojf", Value="John Frepojf Addresses Home: 851 Beacon St. Swnsfng, PR 00734 US Work: Yoyodyne Propulsion Systems 330 Lectroid Rd. Grover's Mill, AR 97794 US Telephone Numbers work: +11 10 474 781020 home: +11 29 575 9284939 Birth Date: 1985-12-27"} ... Age > 42 ConverterEntry{Key="John Qghbguyn", Value="John Qghbguyn Addresses Home: 49 Beacon St. Dvftzpq, PR 34220 US Work: Yoyodyne Propulsion Systems 330 Lectroid Rd. Grover's Mill, MD 28247 US Telephone Numbers work: +11 39 563 7113013 home: +11 58 910 4667915 Birth Date: 1961-01-02"} ... Last Name Begins with S and State Is MA ConverterEntry{Key="John Smnfg", Value="John Smnfg Addresses Home: 178 Beacon St. Hbpeak, MA 64081 US Work: Yoyodyne Propulsion Systems 330 Lectroid Rd. Grover's Mill, OK 92191 US Telephone Numbers work: +11 56 322 7307404 home: +11 33 928 3075361 Birth Date: 1978-12-29"} ...
Add code to the QueryExample.java
file to perform aggregations on the cache data. An entry aggregator (com.tangosol.util.InvocableMap.EntryAggregator
) enables you to perform operations on all or a specific set of objects and return an aggregation. EntryAggregator
instances are 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 5-9 illustrates the EntryAggregator
methods that perform these tasks.
Example 5-9 Methods to Aggregate Over Keys or by Specifying Filters
Object aggregate(Collection keys, InvocableMap.entryAggregator agg) Object aggregate(Filter filter, InvocableMap.entryAggregator agg)
To add aggregations to return data for filtering:
Using aggregations, write code in the QueryExample
class to calculate the following:
The number of employees that are older than a specified age. Use the GreaterFilter
and the Count
class:
cache.aggregate(new GreaterFilter("getAge", nAge), new Count())
Lowest age in the set of employees. Use the AlwaysFilter
and the LongMin
class:
cache.aggregate(AlwaysFilter.INSTANCE, new LongMin("getAge"))
Highest age in the set of employees. Use the AlwaysFilter
and the LongMax
class:
cache.aggregate(AlwaysFilter.INSTANCE, new LongMax("getAge"))
Average age of employees. Use the AlwaysFilter
and the DoubleAverage
class:
cache.aggregate(AlwaysFilter.INSTANCE, new DoubleAverage("getAge")
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 looks similar to Example 5-10.
Example 5-10 QueryExample with Aggregation
package com.oracle.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 indexes 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()); } } }
To query the cache and aggregate the results:
Stop the Contacts
cache server, ContactsCacheServer
. See "Stopping Cache Servers" for more information.
Restart ContactsCacheServer
.
Run the DataGenerator
, LoaderExample
, and QueryExample
applications.
The output should look similar to Example 5-11 in the Eclipse Console.
Example 5-11 Output from the Aggregators
... Last Name Begins with S and State Is MA ConverterEntry{Key="John Sqmyas", Value="John Sqmyas Addresses Home: 594 Beacon St. Jxaxt, MA 10081 US Work: Yoyodyne Propulsion Systems 330 Lectroid Rd. Grover's Mill, NJ 95236 US Telephone Numbers work: +11 70 837 4723938 home: +11 2 227 6816592 Birth Date: 1977-12-29"} count > 42: 482 min age: 22 avg age: 41.627 max age: 61