001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2004,2008 Oracle. All rights reserved.
005: *
006: * $Id: EventExample.java,v 1.1.2.2 2008/01/07 15:14:04 cwl Exp $
007: */
008:
009: package persist;
010:
011: import java.io.File;
012: import java.io.Serializable;
013: import java.util.Calendar;
014: import java.util.Date;
015: import java.util.HashSet;
016: import java.util.Random;
017: import java.util.Set;
018:
019: import com.sleepycat.bind.EntryBinding;
020: import com.sleepycat.bind.serial.SerialBinding;
021: import com.sleepycat.bind.serial.StoredClassCatalog;
022: import com.sleepycat.bind.tuple.IntegerBinding;
023: import com.sleepycat.bind.tuple.LongBinding;
024: import com.sleepycat.je.Cursor;
025: import com.sleepycat.je.Database;
026: import com.sleepycat.je.DatabaseConfig;
027: import com.sleepycat.je.DatabaseEntry;
028: import com.sleepycat.je.DatabaseException;
029: import com.sleepycat.je.Environment;
030: import com.sleepycat.je.EnvironmentConfig;
031: import com.sleepycat.je.OperationStatus;
032: import com.sleepycat.je.SecondaryConfig;
033: import com.sleepycat.je.SecondaryCursor;
034: import com.sleepycat.je.SecondaryDatabase;
035: import com.sleepycat.je.SecondaryKeyCreator;
036: import com.sleepycat.je.Transaction;
037:
038: /**
039: * EventExample is a trivial example which stores Java objects that represent
040: * an event. Events are primarily indexed by a timestamp, but have other
041: * attributes, such as price, account reps, customer name and quantity.
042: * Some of those other attributes are indexed.
043: * <p>
044: * The example simply shows the creation of a JE environment and database,
045: * inserting some events, and retrieving the events.
046: * <p>
047: * This example is meant to be paired with its twin, EventExampleDPL.java.
048: * EventExample.java and EventExampleDPL.java perform the same functionality,
049: * but use the Base API and the Direct Persistence Layer api, respectively.
050: * This may be a useful way to compare the two apis.
051: * <p>
052: * To run the example:
053: * <pre>
054: * cd jehome/examples
055: * javac je/EventExample.java
056: * java -cp "../lib/je.jar;." je.EventExample -h <environmentDirectory>
057: * </pre>
058: */
059: public class EventExample {
060:
061: /*
062: * The Event class embodies our example event and is the application
063: * data. JE data records are represented at key/data tuples. In this
064: * example, the key portion of the record is the event time, and the data
065: * portion is the Event instance.
066: */
067: static class Event implements Serializable {
068:
069: /* This example will add secondary indices on price and accountReps. */
070: private int price;
071: private Set<String> accountReps;
072:
073: private String customerName;
074: private int quantity;
075:
076: Event(int price, String customerName) {
077:
078: this .price = price;
079: this .customerName = customerName;
080: this .accountReps = new HashSet<String>();
081: }
082:
083: void addRep(String rep) {
084: accountReps.add(rep);
085: }
086:
087: @Override
088: public String toString() {
089: StringBuilder sb = new StringBuilder();
090: sb.append(" price=").append(price);
091: sb.append(" customerName=").append(customerName);
092: sb.append(" reps=");
093: if (accountReps.size() == 0) {
094: sb.append("none");
095: } else {
096: for (String rep : accountReps) {
097: sb.append(rep).append(" ");
098: }
099: }
100: return sb.toString();
101: }
102:
103: int getPrice() {
104: return price;
105: }
106: }
107:
108: /* A JE environment is roughly equivalent to a relational database. */
109: private Environment env;
110:
111: /*
112: * A JE table is roughly equivalent to a relational table with a
113: * primary index.
114: */
115: private Database eventDb;
116:
117: /* A secondary database indexes an additional field of the data record */
118: private SecondaryDatabase eventByPriceDb;
119:
120: /*
121: * The catalogs and bindings are used to convert Java objects to the byte
122: * array format used by JE key/data in the base api. The Direct Persistence
123: * Layer api supports Java objects as arguments directly.
124: */
125: private Database catalogDb;
126: private EntryBinding eventBinding;
127:
128: /* Used for generating example data. */
129: private Calendar cal;
130:
131: /*
132: * First manually make a directory to house the JE environment.
133: * Usage: java -cp je.jar EventExample -h <envHome>
134: * All JE on-disk storage is held within envHome.
135: */
136: public static void main(String[] args) throws DatabaseException {
137:
138: if (args.length != 2 || !"-h".equals(args[0])) {
139: System.err.println("Usage: java "
140: + EventExample.class.getName() + " -h <envHome>");
141: System.exit(2);
142: }
143: EventExample example = new EventExample(new File(args[1]));
144: example.run();
145: example.close();
146: }
147:
148: private EventExample(File envHome) throws DatabaseException {
149:
150: /* Open a transactional Berkeley DB engine environment. */
151: System.out.println("-> Creating a JE environment");
152: EnvironmentConfig envConfig = new EnvironmentConfig();
153: envConfig.setAllowCreate(true);
154: envConfig.setTransactional(true);
155: env = new Environment(envHome, envConfig);
156:
157: init();
158: cal = Calendar.getInstance();
159: }
160:
161: /**
162: * Create all primary and secondary indices.
163: */
164: private void init() throws DatabaseException {
165:
166: System.out.println("-> Creating a JE database");
167: DatabaseConfig dbConfig = new DatabaseConfig();
168: dbConfig.setTransactional(true);
169: dbConfig.setAllowCreate(true);
170: eventDb = env.openDatabase(null, // use auto-commit txn
171: "eventDb", // database name
172: dbConfig);
173:
174: /*
175: * In our example, the database record is composed of a key portion
176: * which represents the event timestamp, and a data portion holds an
177: * instance of the Event class.
178: *
179: * JE's base api accepts and returns key and data as byte arrays, so we
180: * need some support for marshaling between objects and byte arrays. We
181: * call this binding, and supply a package of helper classes to support
182: * this. It's entirely possible to do all binding on your own.
183: *
184: * A class catalog database is needed for storing class descriptions
185: * for the serial binding used below. This avoids storing class
186: * descriptions redundantly in each record.
187: */
188: DatabaseConfig catalogConfig = new DatabaseConfig();
189: catalogConfig.setTransactional(true);
190: catalogConfig.setAllowCreate(true);
191: catalogDb = env.openDatabase(null, "catalogDb", catalogConfig);
192: StoredClassCatalog catalog = new StoredClassCatalog(catalogDb);
193:
194: /*
195: * Create a serial binding for Event data objects. Serial
196: * bindings can be used to store any Serializable object.
197: * We can use some pre-defined binding classes to convert
198: * primitives like the long key value to the a byte array.
199: */
200: eventBinding = new SerialBinding(catalog, Event.class);
201:
202: /*
203: * Open a secondary database to allow accessing the primary
204: * database a secondary key value. In this case, access events
205: * by price.
206: */
207: SecondaryConfig secConfig = new SecondaryConfig();
208: secConfig.setTransactional(true);
209: secConfig.setAllowCreate(true);
210: secConfig.setSortedDuplicates(true);
211: secConfig.setKeyCreator(new PriceKeyCreator(eventBinding));
212: eventByPriceDb = env.openSecondaryDatabase(null, "priceDb",
213: eventDb, secConfig);
214:
215: }
216:
217: private void run() throws DatabaseException {
218:
219: Random rand = new Random();
220:
221: /* DatabaseEntry represents the key and data of each record */
222: DatabaseEntry key = new DatabaseEntry();
223: DatabaseEntry data = new DatabaseEntry();
224:
225: /*
226: * Create a set of events. Each insertion is a separate, auto-commit
227: * transaction.
228: */
229: System.out.println("-> Inserting 4 events");
230: LongBinding.longToEntry(makeDate(1), key);
231: eventBinding.objectToEntry(new Event(100, "Company_A"), data);
232: eventDb.put(null, key, data);
233:
234: LongBinding.longToEntry(makeDate(2), key);
235: eventBinding.objectToEntry(new Event(2, "Company_B"), data);
236: eventDb.put(null, key, data);
237:
238: LongBinding.longToEntry(makeDate(3), key);
239: eventBinding.objectToEntry(new Event(20, "Company_C"), data);
240: eventDb.put(null, key, data);
241:
242: LongBinding.longToEntry(makeDate(4), key);
243: eventBinding.objectToEntry(new Event(40, "CompanyD"), data);
244: eventDb.put(null, key, data);
245:
246: /* Load a whole set of events transactionally. */
247: Transaction txn = env.beginTransaction(null, null);
248: int maxPrice = 50;
249: System.out
250: .println("-> Inserting some randomly generated events");
251: for (int i = 0; i < 25; i++) {
252: long time = makeDate(rand.nextInt(365));
253: Event e = new Event(rand.nextInt(maxPrice), "Company_X");
254: if ((i % 2) == 0) {
255: e.addRep("Jane");
256: e.addRep("Nikunj");
257: } else {
258: e.addRep("Yongmin");
259: }
260: LongBinding.longToEntry(time, key);
261: eventBinding.objectToEntry(e, data);
262: eventDb.put(txn, key, data);
263: }
264: txn.commitWriteNoSync();
265:
266: /*
267: * Windows of events - display the events between June 1 and Aug 31
268: */
269: System.out
270: .println("\n-> Display the events between June 1 and Aug 31");
271: long startDate = makeDate(Calendar.JUNE, 31);
272: long endDate = makeDate(Calendar.AUGUST, 31);
273:
274: /* Position the cursor and print the first event. */
275: Cursor eventWindow = eventDb.openCursor(null, null);
276: LongBinding.longToEntry(makeDate(Calendar.JUNE, 1), key);
277:
278: if ((eventWindow.getSearchKeyRange(key, data, null)) != OperationStatus.SUCCESS) {
279: System.out.println("No events found!");
280: eventWindow.close();
281: return;
282: }
283: try {
284: printEvents(key, data, eventWindow, endDate);
285: } finally {
286: eventWindow.close();
287: }
288:
289: /*
290: * Display all events, ordered by a secondary index on price.
291: */
292: System.out.println("\n-> Display all events, ordered by price");
293: SecondaryCursor priceCursor = eventByPriceDb
294: .openSecondaryCursor(null, null);
295: try {
296: printEvents(priceCursor);
297: } finally {
298: priceCursor.close();
299: }
300: }
301:
302: private void close() throws DatabaseException {
303:
304: eventByPriceDb.close();
305: eventDb.close();
306: catalogDb.close();
307: env.close();
308: }
309:
310: /**
311: * Print all events covered by this cursor up to the end date. We know
312: * that the cursor operates on long keys and Event data items, but there's
313: * no type-safe way of expressing that within the JE base api.
314: */
315: private void printEvents(DatabaseEntry firstKey,
316: DatabaseEntry firstData, Cursor cursor, long endDate)
317: throws DatabaseException {
318:
319: System.out.println("time="
320: + new Date(LongBinding.entryToLong(firstKey))
321: + eventBinding.entryToObject(firstData));
322: DatabaseEntry key = new DatabaseEntry();
323: DatabaseEntry data = new DatabaseEntry();
324:
325: while (cursor.getNext(key, data, null) == OperationStatus.SUCCESS) {
326: if (LongBinding.entryToLong(key) > endDate) {
327: break;
328: }
329: System.out.println("time="
330: + new Date(LongBinding.entryToLong(key))
331: + eventBinding.entryToObject(data));
332: }
333: }
334:
335: private void printEvents(SecondaryCursor cursor)
336: throws DatabaseException {
337: DatabaseEntry timeKey = new DatabaseEntry();
338: DatabaseEntry priceKey = new DatabaseEntry();
339: DatabaseEntry eventData = new DatabaseEntry();
340:
341: while (cursor.getNext(priceKey, timeKey, eventData, null) == OperationStatus.SUCCESS) {
342: System.out.println("time="
343: + new Date(LongBinding.entryToLong(timeKey))
344: + eventBinding.entryToObject(eventData));
345: }
346: }
347:
348: /**
349: * Little utility for making up java.util.Dates for different days, just
350: * to generate test data.
351: */
352: private long makeDate(int day) {
353:
354: cal.set((Calendar.DAY_OF_YEAR), day);
355: return cal.getTime().getTime();
356: }
357:
358: /**
359: * Little utility for making up java.util.Dates for different days, just
360: * to make the test data easier to read.
361: */
362: private long makeDate(int month, int day) {
363:
364: cal.set((Calendar.MONTH), month);
365: cal.set((Calendar.DAY_OF_MONTH), day);
366: return cal.getTime().getTime();
367: }
368:
369: /**
370: * A key creator that knows how to extract the secondary key from the data
371: * entry of the primary database. To do so, it uses both the dataBinding
372: * of the primary database and the secKeyBinding.
373: */
374: private static class PriceKeyCreator implements SecondaryKeyCreator {
375:
376: private EntryBinding dataBinding;
377:
378: PriceKeyCreator(EntryBinding eventBinding) {
379: this .dataBinding = eventBinding;
380: }
381:
382: public boolean createSecondaryKey(
383: SecondaryDatabase secondaryDb, DatabaseEntry keyEntry,
384: DatabaseEntry dataEntry, DatabaseEntry resultEntry)
385: throws DatabaseException {
386:
387: /*
388: * Convert the data entry to an Event object, extract the secondary
389: * key value from it, and then convert it to the resulting
390: * secondary key entry.
391: */
392: Event e = (Event) dataBinding.entryToObject(dataEntry);
393: int price = e.getPrice();
394: IntegerBinding.intToEntry(price, resultEntry);
395: return true;
396: }
397: }
398: }
|