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