1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2004, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  * $Id$
7  */
8 
9 package persist;
10 
11 import java.io.File;
12 import java.io.FileNotFoundException;
13 import java.io.Serializable;
14 import java.util.Calendar;
15 import java.util.Date;
16 import java.util.HashSet;
17 import java.util.Random;
18 import java.util.Set;
19 
20 import com.sleepycat.bind.EntryBinding;
21 import com.sleepycat.bind.serial.SerialBinding;
22 import com.sleepycat.bind.serial.StoredClassCatalog;
23 import com.sleepycat.bind.tuple.IntegerBinding;
24 import com.sleepycat.bind.tuple.LongBinding;
25 import com.sleepycat.db.Cursor;
26 import com.sleepycat.db.Database;
27 import com.sleepycat.db.DatabaseConfig;
28 import com.sleepycat.db.DatabaseEntry;
29 import com.sleepycat.db.DatabaseException;
30 import com.sleepycat.db.DatabaseType;
31 import com.sleepycat.db.Environment;
32 import com.sleepycat.db.EnvironmentConfig;
33 import com.sleepycat.db.OperationStatus;
34 import com.sleepycat.db.SecondaryConfig;
35 import com.sleepycat.db.SecondaryCursor;
36 import com.sleepycat.db.SecondaryDatabase;
37 import com.sleepycat.db.SecondaryKeyCreator;
38 import com.sleepycat.db.Transaction;
39 
40 /**
41  * EventExample is a trivial example which stores Java objects that represent
42  * an event. Events are primarily indexed by a timestamp, but have other
43  * attributes, such as price, account reps, customer name and quantity.
44  * Some of those other attributes are indexed.
45  * <p>
46  * The example simply shows the creation of a BDB environment and database,
47  * inserting some events, and retrieving the events.
48  * <p>
49  * This example is meant to be paired with its twin, EventExampleDPL.java.
50  * EventExample.java and EventExampleDPL.java perform the same functionality,
51  * but use the Base API and the Direct Persistence Layer API, respectively.
52  * This may be a useful way to compare the two APIs.
53  * <p>
54  * To run the example:
55  * <pre>
56  * cd jehome/examples
57  * javac je/EventExample.java
58  * java je.EventExample -h <environmentDirectory>
59  * </pre>
60  */
61 public class EventExample {
62 
63     /*
64      * The Event class embodies our example event and is the application
65      * data. BDB data records are represented at key/data tuples. In this
66      * example, the key portion of the record is the event time, and the data
67      * portion is the Event instance.
68      */
69     static class Event implements Serializable {
70 
71         /* This example will add secondary indices on price and accountReps. */
72         private int price;
73         private Set<String> accountReps;
74 
75         private String customerName;
76         private int quantity;
77 
Event(int price, String customerName)78         Event(int price,
79               String customerName) {
80 
81             this.price = price;
82             this.customerName = customerName;
83             this.accountReps = new HashSet<String>();
84         }
85 
addRep(String rep)86         void addRep(String rep) {
87             accountReps.add(rep);
88         }
89 
90         @Override
toString()91         public String toString() {
92             StringBuilder sb = new StringBuilder();
93             sb.append(" price=").append(price);
94             sb.append(" customerName=").append(customerName);
95             sb.append(" reps=");
96             if (accountReps.size() == 0) {
97                 sb.append("none");
98             } else {
99                 for (String rep: accountReps) {
100                     sb.append(rep).append(" ");
101                 }
102             }
103             return sb.toString();
104         }
105 
getPrice()106         int getPrice() {
107             return price;
108         }
109     }
110 
111     /* A BDB environment is roughly equivalent to a relational database. */
112     private Environment env;
113 
114     /*
115      * A BDB table is roughly equivalent to a relational table with a
116      * primary index.
117      */
118     private Database eventDb;
119 
120     /* A secondary database indexes an additional field of the data record */
121     private SecondaryDatabase eventByPriceDb;
122 
123     /*
124      * The catalogs and bindings are used to convert Java objects to the byte
125      * array format used by BDB key/data in the base API. The Direct
126      * Persistence Layer API supports Java objects as arguments directly.
127      */
128     private Database catalogDb;
129     private EntryBinding eventBinding;
130 
131     /* Used for generating example data. */
132     private Calendar cal;
133 
134 
135     /*
136      * First manually make a directory to house the BDB environment.
137      * Usage: java EventExample -h <envHome>
138      * All BDB on-disk storage is held within envHome.
139      */
main(String[] args)140     public static void main(String[] args)
141         throws DatabaseException, FileNotFoundException {
142 
143         if (args.length != 2 || !"-h".equals(args[0])) {
144             System.err.println
145                 ("Usage: java " + EventExample.class.getName() +
146                  " -h <envHome>");
147             System.exit(2);
148         }
149         EventExample example = new EventExample(new File(args[1]));
150         example.run();
151         example.close();
152     }
153 
EventExample(File envHome)154     private EventExample(File envHome)
155         throws DatabaseException, FileNotFoundException {
156 
157         /* Open a transactional Berkeley DB engine environment. */
158         System.out.println("-> Creating a BDB environment");
159         EnvironmentConfig envConfig = new EnvironmentConfig();
160         envConfig.setAllowCreate(true);
161         envConfig.setTransactional(true);
162         envConfig.setInitializeCache(true);
163         envConfig.setInitializeLocking(true);
164         env = new Environment(envHome, envConfig);
165 
166         init();
167         cal = Calendar.getInstance();
168     }
169 
170     /**
171      * Create all primary and secondary indices.
172      */
init()173     private void init()
174         throws DatabaseException, FileNotFoundException {
175 
176         System.out.println("-> Creating a BDB database");
177         DatabaseConfig dbConfig = new DatabaseConfig();
178         dbConfig.setTransactional(true);
179         dbConfig.setAllowCreate(true);
180         dbConfig.setType(DatabaseType.BTREE);
181         eventDb = env.openDatabase(null,      // use auto-commit txn
182                                    "eventDb", // file name
183                                    null,      // database name
184                                    dbConfig);
185 
186 
187         /*
188          * In our example, the database record is composed of a key portion
189          * which represents the event timestamp, and a data portion holds an
190          * instance of the Event class.
191          *
192          * BDB's base API accepts and returns key and data as byte arrays, so
193          * we need some support for marshaling between objects and byte arrays.
194          * We call this binding, and supply a package of helper classes to
195          * support this. It's entirely possible to do all binding on your own.
196          *
197          * A class catalog database is needed for storing class descriptions
198          * for the serial binding used below. This avoids storing class
199          * descriptions redundantly in each record.
200          */
201         DatabaseConfig catalogConfig = new DatabaseConfig();
202         catalogConfig.setTransactional(true);
203         catalogConfig.setAllowCreate(true);
204         catalogConfig.setType(DatabaseType.BTREE);
205         catalogDb = env.openDatabase(null, "catalogDb", null, catalogConfig);
206         StoredClassCatalog catalog = new StoredClassCatalog(catalogDb);
207 
208         /*
209          * Create a serial binding for Event data objects.  Serial
210          * bindings can be used to store any Serializable object.
211          * We can use some pre-defined binding classes to convert
212          * primitives like the long key value to the a byte array.
213          */
214         eventBinding = new SerialBinding(catalog, Event.class);
215 
216         /*
217          * Open a secondary database to allow accessing the primary
218          * database a secondary key value. In this case, access events
219          * by price.
220          */
221         SecondaryConfig secConfig = new SecondaryConfig();
222         secConfig.setTransactional(true);
223         secConfig.setAllowCreate(true);
224         secConfig.setType(DatabaseType.BTREE);
225         secConfig.setSortedDuplicates(true);
226         secConfig.setKeyCreator(new PriceKeyCreator(eventBinding));
227         eventByPriceDb = env.openSecondaryDatabase(null,
228                                                    "priceDb",
229                                                    null,
230                                                    eventDb,
231                                                    secConfig);
232 
233     }
234 
run()235     private void run()
236         throws DatabaseException {
237 
238         Random rand = new Random();
239 
240         /* DatabaseEntry represents the key and data of each record */
241         DatabaseEntry key = new DatabaseEntry();
242         DatabaseEntry data = new DatabaseEntry();
243 
244         /*
245          * Create a set of events. Each insertion is a separate, auto-commit
246          * transaction.
247          */
248         System.out.println("-> Inserting 4 events");
249         LongBinding.longToEntry(makeDate(1), key);
250         eventBinding.objectToEntry(new Event(100, "Company_A"),
251                                    data);
252         eventDb.put(null, key, data);
253 
254         LongBinding.longToEntry(makeDate(2), key);
255         eventBinding.objectToEntry(new Event(2, "Company_B"),
256                                    data);
257         eventDb.put(null, key, data);
258 
259         LongBinding.longToEntry(makeDate(3), key);
260         eventBinding.objectToEntry(new Event(20, "Company_C"),
261                                    data);
262         eventDb.put(null, key, data);
263 
264         LongBinding.longToEntry(makeDate(4), key);
265         eventBinding.objectToEntry(new Event(40, "CompanyD"),
266                                    data);
267         eventDb.put(null, key, data);
268 
269         /* Load a whole set of events transactionally. */
270         Transaction txn = env.beginTransaction(null, null);
271         int maxPrice = 50;
272         System.out.println("-> Inserting some randomly generated events");
273         for (int i = 0; i < 25; i++) {
274             long time = makeDate(rand.nextInt(365));
275             Event e = new Event(rand.nextInt(maxPrice),"Company_X");
276             if ((i%2) ==0) {
277                 e.addRep("Jane");
278                 e.addRep("Nikunj");
279             } else {
280                 e.addRep("Yongmin");
281             }
282             LongBinding.longToEntry(time, key);
283             eventBinding.objectToEntry(e, data);
284             eventDb.put(txn, key, data);
285         }
286         txn.commitWriteNoSync();
287 
288         /*
289          * Windows of events - display the events between June 1 and Aug 31
290          */
291         System.out.println("\n-> Display the events between June 1 and Aug 31");
292         long endDate = makeDate(Calendar.AUGUST, 31);
293 
294         /* Position the cursor and print the first event. */
295         Cursor eventWindow = eventDb.openCursor(null, null);
296         LongBinding.longToEntry(makeDate(Calendar.JUNE, 1), key);
297 
298         if ((eventWindow.getSearchKeyRange(key,  data, null)) !=
299             OperationStatus.SUCCESS) {
300             System.out.println("No events found!");
301             eventWindow.close();
302             return;
303         }
304         try {
305             printEvents(key, data, eventWindow, endDate);
306         } finally {
307             eventWindow.close();
308         }
309 
310         /*
311          * Display all events, ordered by a secondary index on price.
312          */
313         System.out.println("\n-> Display all events, ordered by price");
314         SecondaryCursor priceCursor =
315             eventByPriceDb.openSecondaryCursor(null, null);
316         try {
317             printEvents(priceCursor);
318         } finally {
319             priceCursor.close();
320         }
321     }
322 
close()323     private void close()
324         throws DatabaseException {
325 
326         eventByPriceDb.close();
327         eventDb.close();
328         catalogDb.close();
329         env.close();
330     }
331 
332     /**
333      * Print all events covered by this cursor up to the end date.  We know
334      * that the cursor operates on long keys and Event data items, but there's
335      * no type-safe way of expressing that within the BDB base API.
336      */
printEvents(DatabaseEntry firstKey, DatabaseEntry firstData, Cursor cursor, long endDate)337     private void printEvents(DatabaseEntry firstKey,
338                              DatabaseEntry firstData,
339                              Cursor cursor,
340                              long endDate)
341         throws DatabaseException {
342 
343         System.out.println("time=" +
344                            new Date(LongBinding.entryToLong(firstKey)) +
345                            eventBinding.entryToObject(firstData));
346         DatabaseEntry key = new DatabaseEntry();
347         DatabaseEntry data = new DatabaseEntry();
348 
349         while (cursor.getNext(key, data, null) ==
350                OperationStatus.SUCCESS) {
351             if (LongBinding.entryToLong(key) > endDate) {
352                 break;
353             }
354             System.out.println("time=" +
355                                new Date(LongBinding.entryToLong(key)) +
356                                eventBinding.entryToObject(data));
357         }
358     }
359 
printEvents(SecondaryCursor cursor)360     private void printEvents(SecondaryCursor cursor)
361         throws DatabaseException {
362         DatabaseEntry timeKey = new DatabaseEntry();
363         DatabaseEntry priceKey = new DatabaseEntry();
364         DatabaseEntry eventData = new DatabaseEntry();
365 
366         while (cursor.getNext(priceKey, timeKey, eventData, null) ==
367                OperationStatus.SUCCESS) {
368             System.out.println("time=" +
369                                new Date(LongBinding.entryToLong(timeKey)) +
370                                eventBinding.entryToObject(eventData));
371         }
372     }
373 
374     /**
375      * Little utility for making up java.util.Dates for different days, just
376      * to generate test data.
377      */
makeDate(int day)378     private long makeDate(int day) {
379 
380         cal.set((Calendar.DAY_OF_YEAR), day);
381         return cal.getTime().getTime();
382     }
383     /**
384      * Little utility for making up java.util.Dates for different days, just
385      * to make the test data easier to read.
386      */
makeDate(int month, int day)387     private long makeDate(int month, int day) {
388 
389         cal.set((Calendar.MONTH), month);
390         cal.set((Calendar.DAY_OF_MONTH), day);
391         return cal.getTime().getTime();
392     }
393 
394     /**
395      * A key creator that knows how to extract the secondary key from the data
396      * entry of the primary database.  To do so, it uses both the dataBinding
397      * of the primary database and the secKeyBinding.
398      */
399     private static class PriceKeyCreator implements SecondaryKeyCreator {
400 
401         private EntryBinding dataBinding;
402 
PriceKeyCreator(EntryBinding eventBinding)403         PriceKeyCreator(EntryBinding eventBinding) {
404             this.dataBinding = eventBinding;
405         }
406 
createSecondaryKey(SecondaryDatabase secondaryDb, DatabaseEntry keyEntry, DatabaseEntry dataEntry, DatabaseEntry resultEntry)407         public boolean createSecondaryKey(SecondaryDatabase secondaryDb,
408                                           DatabaseEntry keyEntry,
409                                           DatabaseEntry dataEntry,
410                                           DatabaseEntry resultEntry)
411             throws DatabaseException {
412 
413             /*
414              * Convert the data entry to an Event object, extract the secondary
415              * key value from it, and then convert it to the resulting
416              * secondary key entry.
417              */
418             Event e  = (Event) dataBinding.entryToObject(dataEntry);
419             int price = e.getPrice();
420             IntegerBinding.intToEntry(price, resultEntry);
421             return true;
422         }
423     }
424 }
425