/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2004,2008 Oracle. All rights reserved. * * $Id: EventExample.java,v 1.1 2008/02/07 17:12:24 mark Exp $ */ package persist; import java.io.File; import java.io.FileNotFoundException; import java.io.Serializable; import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.Random; import java.util.Set; import com.sleepycat.bind.EntryBinding; import com.sleepycat.bind.serial.SerialBinding; import com.sleepycat.bind.serial.StoredClassCatalog; import com.sleepycat.bind.tuple.IntegerBinding; import com.sleepycat.bind.tuple.LongBinding; import com.sleepycat.db.Cursor; import com.sleepycat.db.Database; import com.sleepycat.db.DatabaseConfig; import com.sleepycat.db.DatabaseEntry; import com.sleepycat.db.DatabaseException; import com.sleepycat.db.DatabaseType; import com.sleepycat.db.Environment; import com.sleepycat.db.EnvironmentConfig; import com.sleepycat.db.OperationStatus; import com.sleepycat.db.SecondaryConfig; import com.sleepycat.db.SecondaryCursor; import com.sleepycat.db.SecondaryDatabase; import com.sleepycat.db.SecondaryKeyCreator; import com.sleepycat.db.Transaction; /** * EventExample is a trivial example which stores Java objects that represent * an event. Events are primarily indexed by a timestamp, but have other * attributes, such as price, account reps, customer name and quantity. * Some of those other attributes are indexed. *

* The example simply shows the creation of a BDB environment and database, * inserting some events, and retrieving the events. *

* This example is meant to be paired with its twin, EventExampleDPL.java. * EventExample.java and EventExampleDPL.java perform the same functionality, * but use the Base API and the Direct Persistence Layer API, respectively. * This may be a useful way to compare the two APIs. *

* To run the example: *

 * cd jehome/examples
 * javac je/EventExample.java
 * java je.EventExample -h 
 * 
*/ public class EventExample { /* * The Event class embodies our example event and is the application * data. BDB data records are represented at key/data tuples. In this * example, the key portion of the record is the event time, and the data * portion is the Event instance. */ static class Event implements Serializable { /* This example will add secondary indices on price and accountReps. */ private int price; private Set accountReps; private String customerName; private int quantity; Event(int price, String customerName) { this.price = price; this.customerName = customerName; this.accountReps = new HashSet(); } void addRep(String rep) { accountReps.add(rep); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(" price=").append(price); sb.append(" customerName=").append(customerName); sb.append(" reps="); if (accountReps.size() == 0) { sb.append("none"); } else { for (String rep: accountReps) { sb.append(rep).append(" "); } } return sb.toString(); } int getPrice() { return price; } } /* A BDB environment is roughly equivalent to a relational database. */ private Environment env; /* * A BDB table is roughly equivalent to a relational table with a * primary index. */ private Database eventDb; /* A secondary database indexes an additional field of the data record */ private SecondaryDatabase eventByPriceDb; /* * The catalogs and bindings are used to convert Java objects to the byte * array format used by BDB key/data in the base API. The Direct * Persistence Layer API supports Java objects as arguments directly. */ private Database catalogDb; private EntryBinding eventBinding; /* Used for generating example data. */ private Calendar cal; /* * First manually make a directory to house the BDB environment. * Usage: java EventExample -h * All BDB on-disk storage is held within envHome. */ public static void main(String[] args) throws DatabaseException, FileNotFoundException { if (args.length != 2 || !"-h".equals(args[0])) { System.err.println ("Usage: java " + EventExample.class.getName() + " -h "); System.exit(2); } EventExample example = new EventExample(new File(args[1])); example.run(); example.close(); } private EventExample(File envHome) throws DatabaseException, FileNotFoundException { /* Open a transactional Berkeley DB engine environment. */ System.out.println("-> Creating a BDB environment"); EnvironmentConfig envConfig = new EnvironmentConfig(); envConfig.setAllowCreate(true); envConfig.setTransactional(true); envConfig.setInitializeCache(true); envConfig.setInitializeLocking(true); env = new Environment(envHome, envConfig); init(); cal = Calendar.getInstance(); } /** * Create all primary and secondary indices. */ private void init() throws DatabaseException, FileNotFoundException { System.out.println("-> Creating a BDB database"); DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(true); dbConfig.setAllowCreate(true); dbConfig.setType(DatabaseType.BTREE); eventDb = env.openDatabase(null, // use auto-commit txn "eventDb", // file name null, // database name dbConfig); /* * In our example, the database record is composed of a key portion * which represents the event timestamp, and a data portion holds an * instance of the Event class. * * BDB's base API accepts and returns key and data as byte arrays, so * we need some support for marshaling between objects and byte arrays. * We call this binding, and supply a package of helper classes to * support this. It's entirely possible to do all binding on your own. * * A class catalog database is needed for storing class descriptions * for the serial binding used below. This avoids storing class * descriptions redundantly in each record. */ DatabaseConfig catalogConfig = new DatabaseConfig(); catalogConfig.setTransactional(true); catalogConfig.setAllowCreate(true); catalogConfig.setType(DatabaseType.BTREE); catalogDb = env.openDatabase(null, "catalogDb", null, catalogConfig); StoredClassCatalog catalog = new StoredClassCatalog(catalogDb); /* * Create a serial binding for Event data objects. Serial * bindings can be used to store any Serializable object. * We can use some pre-defined binding classes to convert * primitives like the long key value to the a byte array. */ eventBinding = new SerialBinding(catalog, Event.class); /* * Open a secondary database to allow accessing the primary * database a secondary key value. In this case, access events * by price. */ SecondaryConfig secConfig = new SecondaryConfig(); secConfig.setTransactional(true); secConfig.setAllowCreate(true); secConfig.setType(DatabaseType.BTREE); secConfig.setSortedDuplicates(true); secConfig.setKeyCreator(new PriceKeyCreator(eventBinding)); eventByPriceDb = env.openSecondaryDatabase(null, "priceDb", null, eventDb, secConfig); } private void run() throws DatabaseException { Random rand = new Random(); /* DatabaseEntry represents the key and data of each record */ DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); /* * Create a set of events. Each insertion is a separate, auto-commit * transaction. */ System.out.println("-> Inserting 4 events"); LongBinding.longToEntry(makeDate(1), key); eventBinding.objectToEntry(new Event(100, "Company_A"), data); eventDb.put(null, key, data); LongBinding.longToEntry(makeDate(2), key); eventBinding.objectToEntry(new Event(2, "Company_B"), data); eventDb.put(null, key, data); LongBinding.longToEntry(makeDate(3), key); eventBinding.objectToEntry(new Event(20, "Company_C"), data); eventDb.put(null, key, data); LongBinding.longToEntry(makeDate(4), key); eventBinding.objectToEntry(new Event(40, "CompanyD"), data); eventDb.put(null, key, data); /* Load a whole set of events transactionally. */ Transaction txn = env.beginTransaction(null, null); int maxPrice = 50; System.out.println("-> Inserting some randomly generated events"); for (int i = 0; i < 25; i++) { long time = makeDate(rand.nextInt(365)); Event e = new Event(rand.nextInt(maxPrice),"Company_X"); if ((i%2) ==0) { e.addRep("Jane"); e.addRep("Nikunj"); } else { e.addRep("Yongmin"); } LongBinding.longToEntry(time, key); eventBinding.objectToEntry(e, data); eventDb.put(txn, key, data); } txn.commitWriteNoSync(); /* * Windows of events - display the events between June 1 and Aug 31 */ System.out.println("\n-> Display the events between June 1 and Aug 31"); long endDate = makeDate(Calendar.AUGUST, 31); /* Position the cursor and print the first event. */ Cursor eventWindow = eventDb.openCursor(null, null); LongBinding.longToEntry(makeDate(Calendar.JUNE, 1), key); if ((eventWindow.getSearchKeyRange(key, data, null)) != OperationStatus.SUCCESS) { System.out.println("No events found!"); eventWindow.close(); return; } try { printEvents(key, data, eventWindow, endDate); } finally { eventWindow.close(); } /* * Display all events, ordered by a secondary index on price. */ System.out.println("\n-> Display all events, ordered by price"); SecondaryCursor priceCursor = eventByPriceDb.openSecondaryCursor(null, null); try { printEvents(priceCursor); } finally { priceCursor.close(); } } private void close() throws DatabaseException { eventByPriceDb.close(); eventDb.close(); catalogDb.close(); env.close(); } /** * Print all events covered by this cursor up to the end date. We know * that the cursor operates on long keys and Event data items, but there's * no type-safe way of expressing that within the BDB base API. */ private void printEvents(DatabaseEntry firstKey, DatabaseEntry firstData, Cursor cursor, long endDate) throws DatabaseException { System.out.println("time=" + new Date(LongBinding.entryToLong(firstKey)) + eventBinding.entryToObject(firstData)); DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); while (cursor.getNext(key, data, null) == OperationStatus.SUCCESS) { if (LongBinding.entryToLong(key) > endDate) { break; } System.out.println("time=" + new Date(LongBinding.entryToLong(key)) + eventBinding.entryToObject(data)); } } private void printEvents(SecondaryCursor cursor) throws DatabaseException { DatabaseEntry timeKey = new DatabaseEntry(); DatabaseEntry priceKey = new DatabaseEntry(); DatabaseEntry eventData = new DatabaseEntry(); while (cursor.getNext(priceKey, timeKey, eventData, null) == OperationStatus.SUCCESS) { System.out.println("time=" + new Date(LongBinding.entryToLong(timeKey)) + eventBinding.entryToObject(eventData)); } } /** * Little utility for making up java.util.Dates for different days, just * to generate test data. */ private long makeDate(int day) { cal.set((Calendar.DAY_OF_YEAR), day); return cal.getTime().getTime(); } /** * Little utility for making up java.util.Dates for different days, just * to make the test data easier to read. */ private long makeDate(int month, int day) { cal.set((Calendar.MONTH), month); cal.set((Calendar.DAY_OF_MONTH), day); return cal.getTime().getTime(); } /** * A key creator that knows how to extract the secondary key from the data * entry of the primary database. To do so, it uses both the dataBinding * of the primary database and the secKeyBinding. */ private static class PriceKeyCreator implements SecondaryKeyCreator { private EntryBinding dataBinding; PriceKeyCreator(EntryBinding eventBinding) { this.dataBinding = eventBinding; } public boolean createSecondaryKey(SecondaryDatabase secondaryDb, DatabaseEntry keyEntry, DatabaseEntry dataEntry, DatabaseEntry resultEntry) throws DatabaseException { /* * Convert the data entry to an Event object, extract the secondary * key value from it, and then convert it to the resulting * secondary key entry. */ Event e = (Event) dataBinding.entryToObject(dataEntry); int price = e.getPrice(); IntegerBinding.intToEntry(price, resultEntry); return true; } } }