001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2004,2008 Oracle. All rights reserved.
005: *
006: * $Id: ToManyExample.java,v 1.5.2.2 2008/01/07 15:14:03 cwl Exp $
007: */
008:
009: package je;
010:
011: import java.io.File;
012: import java.io.Serializable;
013: import java.util.HashSet;
014: import java.util.Iterator;
015: import java.util.Set;
016:
017: import com.sleepycat.bind.EntryBinding;
018: import com.sleepycat.bind.serial.SerialBinding;
019: import com.sleepycat.bind.serial.StoredClassCatalog;
020: import com.sleepycat.bind.tuple.StringBinding;
021: import com.sleepycat.je.Database;
022: import com.sleepycat.je.DatabaseConfig;
023: import com.sleepycat.je.DatabaseEntry;
024: import com.sleepycat.je.DatabaseException;
025: import com.sleepycat.je.Environment;
026: import com.sleepycat.je.EnvironmentConfig;
027: import com.sleepycat.je.ForeignKeyDeleteAction;
028: import com.sleepycat.je.ForeignMultiKeyNullifier;
029: import com.sleepycat.je.OperationStatus;
030: import com.sleepycat.je.SecondaryConfig;
031: import com.sleepycat.je.SecondaryCursor;
032: import com.sleepycat.je.SecondaryDatabase;
033: import com.sleepycat.je.SecondaryMultiKeyCreator;
034: import com.sleepycat.je.Transaction;
035:
036: /**
037: * An example of using many-to-many and one-to-many secondary indices.
038: */
039: public class ToManyExample {
040:
041: private Environment env;
042: private Database catalogDb;
043: private Database animalDb;
044: private Database personDb;
045: private SecondaryDatabase personByEmail;
046: private SecondaryDatabase personByAnimal;
047: private EntryBinding keyBinding;
048: private EntryBinding dataBinding;
049:
050: /**
051: * Runs the example program, given a single "-h HOME" argument.
052: */
053: public static void main(String[] args) {
054:
055: if (args.length != 2 || !"-h".equals(args[0])) {
056: System.out.println("Usage: java "
057: + ToManyExample.class.getName() + " -h ENV_HOME");
058: System.exit(1);
059: }
060: String homeDir = args[1];
061:
062: try {
063: ToManyExample example = new ToManyExample(homeDir);
064: example.exec();
065: example.close();
066: } catch (DatabaseException e) {
067: e.printStackTrace();
068: }
069: }
070:
071: /**
072: * Opens the environment and all databases.
073: */
074: private ToManyExample(String homeDir) throws DatabaseException {
075:
076: /* Open the environment. */
077: EnvironmentConfig envConfig = new EnvironmentConfig();
078: envConfig.setAllowCreate(true);
079: envConfig.setTransactional(true);
080: env = new Environment(new File(homeDir), envConfig);
081:
082: /* Open/create all databases in a transaction. */
083: Transaction txn = env.beginTransaction(null, null);
084: try {
085: /* A standard (no duplicates) database config. */
086: DatabaseConfig dbConfig = new DatabaseConfig();
087: dbConfig.setAllowCreate(true);
088: dbConfig.setTransactional(true);
089:
090: /* The catalog is used for the serial binding. */
091: catalogDb = env.openDatabase(txn, "catalog", dbConfig);
092: StoredClassCatalog catalog = new StoredClassCatalog(
093: catalogDb);
094: dataBinding = new SerialBinding(catalog, null);
095: keyBinding = new StringBinding();
096:
097: /* Open the person and animal primary DBs. */
098: animalDb = env.openDatabase(txn, "animal", dbConfig);
099: personDb = env.openDatabase(txn, "person", dbConfig);
100:
101: /*
102: * A standard secondary config; duplicates, key creators and key
103: * nullifiers are specified below.
104: */
105: SecondaryConfig secConfig = new SecondaryConfig();
106: secConfig.setAllowCreate(true);
107: secConfig.setTransactional(true);
108:
109: /*
110: * Open the secondary database for personByEmail. This is a
111: * one-to-many index because duplicates are not configured.
112: */
113: secConfig.setSortedDuplicates(false);
114: secConfig.setMultiKeyCreator(new EmailKeyCreator());
115: personByEmail = env.openSecondaryDatabase(txn,
116: "personByEmail", personDb, secConfig);
117:
118: /*
119: * Open the secondary database for personByAnimal. This is a
120: * many-to-many index because duplicates are configured. Foreign
121: * key constraints are specified to ensure that all animal keys
122: * exist in the animal database.
123: */
124: secConfig.setSortedDuplicates(true);
125: secConfig.setMultiKeyCreator(new AnimalKeyCreator());
126: secConfig
127: .setForeignMultiKeyNullifier(new AnimalKeyNullifier());
128: secConfig.setForeignKeyDatabase(animalDb);
129: secConfig
130: .setForeignKeyDeleteAction(ForeignKeyDeleteAction.NULLIFY);
131: personByAnimal = env.openSecondaryDatabase(txn,
132: "personByAnimal", personDb, secConfig);
133:
134: txn.commit();
135: } catch (DatabaseException e) {
136: txn.abort();
137: throw e;
138: } catch (RuntimeException e) {
139: txn.abort();
140: throw e;
141: }
142: }
143:
144: /**
145: * Closes all databases and the environment.
146: */
147: private void close() throws DatabaseException {
148:
149: if (personByEmail != null) {
150: personByEmail.close();
151: }
152: if (personByAnimal != null) {
153: personByAnimal.close();
154: }
155: if (catalogDb != null) {
156: catalogDb.close();
157: }
158: if (personDb != null) {
159: personDb.close();
160: }
161: if (animalDb != null) {
162: animalDb.close();
163: }
164: if (env != null) {
165: env.close();
166: }
167: }
168:
169: /**
170: * Adds, updates, prints and deletes Person records with many-to-many and
171: * one-to-many secondary indices.
172: */
173: private void exec() throws DatabaseException {
174:
175: System.out.println("\nInsert some animals.");
176: Animal dogs = insertAndPrintAnimal("dogs", true);
177: Animal fish = insertAndPrintAnimal("fish", false);
178: Animal horses = insertAndPrintAnimal("horses", true);
179: Animal donkeys = insertAndPrintAnimal("donkeys", true);
180:
181: System.out.println("\nInsert a new empty person.");
182: Person kathy = new Person();
183: kathy.name = "Kathy";
184: putPerson(kathy);
185: printPerson("Kathy");
186:
187: System.out
188: .println("\nAdd favorites/addresses and update the record.");
189: kathy.favoriteAnimals.add(horses.name);
190: kathy.favoriteAnimals.add(dogs.name);
191: kathy.favoriteAnimals.add(fish.name);
192: kathy.emailAddresses.add("kathy@kathy.com");
193: kathy.emailAddresses.add("kathy@yahoo.com");
194: putPerson(kathy);
195: printPerson("Kathy");
196:
197: System.out
198: .println("\nChange favorites and addresses and update the person record.");
199: kathy.favoriteAnimals.remove(fish.name);
200: kathy.favoriteAnimals.add(donkeys.name);
201: kathy.emailAddresses.add("kathy@gmail.com");
202: kathy.emailAddresses.remove("kathy@yahoo.com");
203: putPerson(kathy);
204: printPerson("Kathy");
205:
206: System.out
207: .println("\nInsert another person with some of the same favorites.");
208: Person mark = new Person();
209: mark.favoriteAnimals.add(dogs.name);
210: mark.favoriteAnimals.add(horses.name);
211: mark.name = "Mark";
212: putPerson(mark);
213: printPerson("Mark");
214:
215: System.out.println("\nPrint by favorite animal index.");
216: printByIndex(personByAnimal);
217:
218: System.out.println("\nPrint by email address index.");
219: printByIndex(personByEmail);
220:
221: System.out
222: .println("\nDelete 'dogs' and print again by favorite animal index.");
223: deleteAnimal(dogs.name);
224: printPerson("Kathy");
225: printPerson("Mark");
226: printByIndex(personByAnimal);
227:
228: System.out
229: .println("\nDelete both records and print again (should print nothing).");
230: deletePerson("Kathy");
231: deletePerson("Mark");
232: printPerson("Kathy");
233: printPerson("Mark");
234: printByIndex(personByAnimal);
235: printByIndex(personByEmail);
236: }
237:
238: /**
239: * Inserts an animal record and prints it. Uses auto-commit.
240: */
241: private Animal insertAndPrintAnimal(String name, boolean furry)
242: throws DatabaseException {
243:
244: Animal animal = new Animal();
245: animal.name = name;
246: animal.furry = furry;
247:
248: DatabaseEntry key = new DatabaseEntry();
249: keyBinding.objectToEntry(name, key);
250:
251: DatabaseEntry data = new DatabaseEntry();
252: dataBinding.objectToEntry(animal, data);
253:
254: OperationStatus status = animalDb.putNoOverwrite(null, key,
255: data);
256: if (status == OperationStatus.SUCCESS) {
257: System.out.println(animal);
258: } else {
259: System.out.println("Animal was not inserted: " + name
260: + " (" + status + ')');
261: }
262:
263: return animal;
264: }
265:
266: /**
267: * Deletes an animal. Uses auto-commit.
268: */
269: private boolean deleteAnimal(String name) throws DatabaseException {
270:
271: DatabaseEntry key = new DatabaseEntry();
272: keyBinding.objectToEntry(name, key);
273:
274: OperationStatus status = animalDb.delete(null, key);
275: return status == OperationStatus.SUCCESS;
276: }
277:
278: /**
279: * Gets a person by name and prints it.
280: */
281: private void printPerson(String name) throws DatabaseException {
282:
283: DatabaseEntry key = new DatabaseEntry();
284: keyBinding.objectToEntry(name, key);
285:
286: DatabaseEntry data = new DatabaseEntry();
287:
288: OperationStatus status = personDb.get(null, key, data, null);
289: if (status == OperationStatus.SUCCESS) {
290: Person person = (Person) dataBinding.entryToObject(data);
291: person.name = (String) keyBinding.entryToObject(key);
292: System.out.println(person);
293: } else {
294: System.out.println("Person not found: " + name);
295: }
296: }
297:
298: /**
299: * Prints all person records by a given secondary index.
300: */
301: private void printByIndex(SecondaryDatabase secDb)
302: throws DatabaseException {
303:
304: DatabaseEntry secKey = new DatabaseEntry();
305: DatabaseEntry priKey = new DatabaseEntry();
306: DatabaseEntry priData = new DatabaseEntry();
307:
308: SecondaryCursor cursor = secDb.openSecondaryCursor(null, null);
309: try {
310: while (cursor.getNext(secKey, priKey, priData, null) == OperationStatus.SUCCESS) {
311: Person person = (Person) dataBinding
312: .entryToObject(priData);
313: person.name = (String) keyBinding.entryToObject(priKey);
314: System.out
315: .println("Index key ["
316: + keyBinding.entryToObject(secKey)
317: + "] maps to primary key ["
318: + person.name + ']');
319: }
320: } finally {
321: cursor.close();
322: }
323: }
324:
325: /**
326: * Inserts or updates a person. Uses auto-commit.
327: */
328: private void putPerson(Person person) throws DatabaseException {
329:
330: DatabaseEntry key = new DatabaseEntry();
331: keyBinding.objectToEntry(person.name, key);
332:
333: DatabaseEntry data = new DatabaseEntry();
334: dataBinding.objectToEntry(person, data);
335:
336: personDb.put(null, key, data);
337: }
338:
339: /**
340: * Deletes a person. Uses auto-commit.
341: */
342: private boolean deletePerson(String name) throws DatabaseException {
343:
344: DatabaseEntry key = new DatabaseEntry();
345: keyBinding.objectToEntry(name, key);
346:
347: OperationStatus status = personDb.delete(null, key);
348: return status == OperationStatus.SUCCESS;
349: }
350:
351: /**
352: * A person object.
353: */
354: private static class Person implements Serializable {
355:
356: /** The primary key. */
357: private transient String name;
358:
359: /** A many-to-many set of keys. */
360: private Set favoriteAnimals = new HashSet();
361:
362: /** A one-to-many set of keys. */
363: private Set emailAddresses = new HashSet();
364:
365: public String toString() {
366: return "Person {" + "\n Name: " + name
367: + "\n FavoriteAnimals: " + favoriteAnimals
368: + "\n EmailAddresses: " + emailAddresses + "\n}";
369: }
370: }
371:
372: /**
373: * An animal object.
374: */
375: private static class Animal implements Serializable {
376:
377: /** The primary key. */
378: private transient String name;
379:
380: /** A non-indexed property. */
381: private boolean furry;
382:
383: public String toString() {
384: return "Animal {" + "\n Name: " + name + "\n Furry: "
385: + furry + "\n}";
386: }
387: }
388:
389: /**
390: * Returns the set of email addresses for a person. This is an example
391: * of a multi-key creator for a to-many index.
392: */
393: private class EmailKeyCreator implements SecondaryMultiKeyCreator {
394:
395: public void createSecondaryKeys(SecondaryDatabase secondary,
396: DatabaseEntry primaryKey, DatabaseEntry primaryData,
397: Set results) throws DatabaseException {
398:
399: Person person = (Person) dataBinding
400: .entryToObject(primaryData);
401: copyKeysToEntries(person.emailAddresses, results);
402: }
403: }
404:
405: /**
406: * Returns the set of favorite animals for a person. This is an example
407: * of a multi-key creator for a to-many index.
408: */
409: private class AnimalKeyCreator implements SecondaryMultiKeyCreator {
410:
411: public void createSecondaryKeys(SecondaryDatabase secondary,
412: DatabaseEntry primaryKey, DatabaseEntry primaryData,
413: Set results) throws DatabaseException {
414:
415: Person person = (Person) dataBinding
416: .entryToObject(primaryData);
417: copyKeysToEntries(person.favoriteAnimals, results);
418: }
419: }
420:
421: /**
422: * A utility method to copy a set of keys (Strings) into a set of
423: * DatabaseEntry objects.
424: */
425: private void copyKeysToEntries(Set keys, Set entries) {
426:
427: for (Iterator i = keys.iterator(); i.hasNext();) {
428: DatabaseEntry entry = new DatabaseEntry();
429: keyBinding.objectToEntry(i.next(), entry);
430: entries.add(entry);
431: }
432: }
433:
434: /**
435: * Removes a given key from the set of favorite animals for a person. This
436: * is an example of a nullifier for a to-many index. The nullifier is
437: * called when an animal record is deleted because we configured this
438: * secondary with ForeignKeyDeleteAction.NULLIFY.
439: */
440: private class AnimalKeyNullifier implements
441: ForeignMultiKeyNullifier {
442:
443: public boolean nullifyForeignKey(SecondaryDatabase secondary,
444: DatabaseEntry primaryKey, DatabaseEntry primaryData,
445: DatabaseEntry secKey) throws DatabaseException {
446:
447: Person person = (Person) dataBinding
448: .entryToObject(primaryData);
449: String key = (String) keyBinding.entryToObject(secKey);
450: if (person.favoriteAnimals.remove(key)) {
451: dataBinding.objectToEntry(person, primaryData);
452: return true;
453: } else {
454: return false;
455: }
456: }
457: }
458: }
|