1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2004,2008 Oracle.  All rights reserved.
5 *
6 * $Id: EventExample.java,v 1.1 2008/02/07 17:12:24 mark Exp $
7 */
8
9package persist;
10
11import java.io.File;
12import java.io.FileNotFoundException;
13import java.io.Serializable;
14import java.util.Calendar;
15import java.util.Date;
16import java.util.HashSet;
17import java.util.Random;
18import java.util.Set;
19
20import com.sleepycat.bind.EntryBinding;
21import com.sleepycat.bind.serial.SerialBinding;
22import com.sleepycat.bind.serial.StoredClassCatalog;
23import com.sleepycat.bind.tuple.IntegerBinding;
24import com.sleepycat.bind.tuple.LongBinding;
25import com.sleepycat.db.Cursor;
26import com.sleepycat.db.Database;
27import com.sleepycat.db.DatabaseConfig;
28import com.sleepycat.db.DatabaseEntry;
29import com.sleepycat.db.DatabaseException;
30import com.sleepycat.db.DatabaseType;
31import com.sleepycat.db.Environment;
32import com.sleepycat.db.EnvironmentConfig;
33import com.sleepycat.db.OperationStatus;
34import com.sleepycat.db.SecondaryConfig;
35import com.sleepycat.db.SecondaryCursor;
36import com.sleepycat.db.SecondaryDatabase;
37import com.sleepycat.db.SecondaryKeyCreator;
38import 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 */
61public 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
78        Event(int price,
79              String customerName) {
80
81            this.price = price;
82            this.customerName = customerName;
83            this.accountReps = new HashSet<String>();
84        }
85
86        void addRep(String rep) {
87            accountReps.add(rep);
88        }
89
90        @Override
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
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     */
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
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     */
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
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
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     */
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
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     */
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     */
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
403        PriceKeyCreator(EntryBinding eventBinding) {
404            this.dataBinding = eventBinding;
405        }
406
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