001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: EntityJoin.java,v 1.7.2.3 2008/01/07 15:14:18 cwl Exp $
007: */
008:
009: package com.sleepycat.persist;
010:
011: import java.util.ArrayList;
012: import java.util.Iterator;
013: import java.util.List;
014:
015: import com.sleepycat.bind.EntityBinding;
016: import com.sleepycat.bind.EntryBinding;
017: import com.sleepycat.je.Cursor;
018: import com.sleepycat.je.CursorConfig;
019: import com.sleepycat.je.Database;
020: import com.sleepycat.je.DatabaseEntry;
021: import com.sleepycat.je.DatabaseException;
022: import com.sleepycat.je.JoinCursor;
023: import com.sleepycat.je.LockMode;
024: import com.sleepycat.je.OperationStatus;
025: import com.sleepycat.je.Transaction;
026:
027: /**
028: * Performs an equality join on two or more secondary keys.
029: *
030: * <p>{@code EntityJoin} objects are thread-safe. Multiple threads may safely
031: * call the methods of a shared {@code EntityJoin} object.</p>
032: *
033: * <p>An equality join is a match on all entities in a given primary index that
034: * have two or more specific secondary key values. Note that key ranges may
035: * not be matched by an equality join, only exact keys are matched.</p>
036: *
037: * <p>For example:</p>
038: * <pre class="code">
039: * // Index declarations -- see {@link <a href="package-summary.html#example">package summary example</a>}.
040: * //
041: * {@literal PrimaryIndex<String,Person> personBySsn;}
042: * {@literal SecondaryIndex<String,String,Person> personByParentSsn;}
043: * {@literal SecondaryIndex<Long,String,Person> personByEmployerIds;}
044: * Employer employer = ...;
045: *
046: * // Match on all Person objects having parentSsn "111-11-1111" and also
047: * // containing an employerId of employer.id. In other words, match on all
048: * // of Bob's children that work for a given employer.
049: * //
050: * {@literal EntityJoin<String,Person> join = new EntityJoin(personBySsn);}
051: * join.addCondition(personByParentSsn, "111-11-1111");
052: * join.addCondition(personByEmployerIds, employer.id);
053: *
054: * // Perform the join operation by traversing the results with a cursor.
055: * //
056: * {@literal ForwardCursor<Person> results = join.entities();}
057: * try {
058: * for (Person person : results) {
059: * System.out.println(person.ssn + ' ' + person.name);
060: * }
061: * } finally {
062: * results.close();
063: * }</pre>
064: *
065: * @author Mark Hayes
066: */
067: public class EntityJoin<PK, E> {
068:
069: private PrimaryIndex<PK, E> primary;
070: private List<Condition> conditions;
071:
072: /**
073: * Creates a join object for a given primary index.
074: *
075: * @param index the primary index on which the join will operate.
076: */
077: public EntityJoin(PrimaryIndex<PK, E> index) {
078: primary = index;
079: conditions = new ArrayList<Condition>();
080: }
081:
082: /**
083: * Adds a secondary key condition to the equality join. Only entities
084: * having the given key value in the given secondary index will be returned
085: * by the join operation.
086: *
087: * @param index the secondary index containing the given key value.
088: *
089: * @param key the key value to match during the join.
090: */
091: public <SK> void addCondition(SecondaryIndex<SK, PK, E> index,
092: SK key) {
093:
094: /* Make key entry. */
095: DatabaseEntry keyEntry = new DatabaseEntry();
096: index.getKeyBinding().objectToEntry(key, keyEntry);
097:
098: /* Use keys database if available. */
099: Database db = index.getKeysDatabase();
100: if (db == null) {
101: db = index.getDatabase();
102: }
103:
104: /* Add condition. */
105: conditions.add(new Condition(db, keyEntry));
106: }
107:
108: /**
109: * Opens a cursor that returns the entities qualifying for the join. The
110: * join operation is performed as the returned cursor is accessed.
111: *
112: * <p>The operations performed with the cursor will not be transaction
113: * protected, and {@link CursorConfig#DEFAULT} is used implicitly.</p>
114: *
115: * @return the cursor.
116: *
117: * @throws IllegalStateException if less than two conditions were added.
118: */
119: public ForwardCursor<E> entities() throws DatabaseException {
120:
121: return entities(null, null);
122: }
123:
124: /**
125: * Opens a cursor that returns the entities qualifying for the join. The
126: * join operation is performed as the returned cursor is accessed.
127: *
128: * @param txn the transaction used to protect all operations performed with
129: * the cursor, or null if the operations should not be transaction
130: * protected.
131: *
132: * @param config the cursor configuration that determines the default lock
133: * mode used for all cursor operations, or null to implicitly use {@link
134: * CursorConfig#DEFAULT}.
135: *
136: * @return the cursor.
137: *
138: * @throws IllegalStateException if less than two conditions were added.
139: */
140: public ForwardCursor<E> entities(Transaction txn,
141: CursorConfig config) throws DatabaseException {
142:
143: return new JoinForwardCursor<E>(txn, config, false);
144: }
145:
146: /**
147: * Opens a cursor that returns the primary keys of entities qualifying for
148: * the join. The join operation is performed as the returned cursor is
149: * accessed.
150: *
151: * <p>The operations performed with the cursor will not be transaction
152: * protected, and {@link CursorConfig#DEFAULT} is used implicitly.</p>
153: *
154: * @return the cursor.
155: *
156: * @throws IllegalStateException if less than two conditions were added.
157: */
158: public ForwardCursor<PK> keys() throws DatabaseException {
159:
160: return keys(null, null);
161: }
162:
163: /**
164: * Opens a cursor that returns the primary keys of entities qualifying for
165: * the join. The join operation is performed as the returned cursor is
166: * accessed.
167: *
168: * @param txn the transaction used to protect all operations performed with
169: * the cursor, or null if the operations should not be transaction
170: * protected.
171: *
172: * @param config the cursor configuration that determines the default lock
173: * mode used for all cursor operations, or null to implicitly use {@link
174: * CursorConfig#DEFAULT}.
175: *
176: * @return the cursor.
177: *
178: * @throws IllegalStateException if less than two conditions were added.
179: */
180: public ForwardCursor<PK> keys(Transaction txn, CursorConfig config)
181: throws DatabaseException {
182:
183: return new JoinForwardCursor<PK>(txn, config, true);
184: }
185:
186: private static class Condition {
187:
188: private Database db;
189: private DatabaseEntry key;
190:
191: Condition(Database db, DatabaseEntry key) {
192: this .db = db;
193: this .key = key;
194: }
195:
196: Cursor openCursor(Transaction txn, CursorConfig config)
197: throws DatabaseException {
198:
199: OperationStatus status;
200: Cursor cursor = db.openCursor(txn, config);
201: try {
202: DatabaseEntry data = BasicIndex.NO_RETURN_ENTRY;
203: status = cursor.getSearchKey(key, data, null);
204: } catch (DatabaseException e) {
205: try {
206: cursor.close();
207: } catch (DatabaseException ignored) {
208: }
209: throw e;
210: }
211: if (status == OperationStatus.SUCCESS) {
212: return cursor;
213: } else {
214: cursor.close();
215: return null;
216: }
217: }
218: }
219:
220: private class JoinForwardCursor<V> implements ForwardCursor<V> {
221:
222: private Cursor[] cursors;
223: private JoinCursor joinCursor;
224: private boolean doKeys;
225:
226: JoinForwardCursor(Transaction txn, CursorConfig config,
227: boolean doKeys) throws DatabaseException {
228:
229: this .doKeys = doKeys;
230: try {
231: cursors = new Cursor[conditions.size()];
232: for (int i = 0; i < cursors.length; i += 1) {
233: Condition cond = conditions.get(i);
234: Cursor cursor = cond.openCursor(txn, config);
235: if (cursor == null) {
236: /* Leave joinCursor null. */
237: doClose(null);
238: return;
239: }
240: cursors[i] = cursor;
241: }
242: joinCursor = primary.getDatabase().join(cursors, null);
243: } catch (DatabaseException e) {
244: /* doClose will throw e. */
245: doClose(e);
246: }
247: }
248:
249: public V next() throws DatabaseException {
250:
251: return next(null);
252: }
253:
254: public V next(LockMode lockMode) throws DatabaseException {
255:
256: if (joinCursor == null) {
257: return null;
258: }
259: if (doKeys) {
260: DatabaseEntry key = new DatabaseEntry();
261: OperationStatus status = joinCursor.getNext(key,
262: lockMode);
263: if (status == OperationStatus.SUCCESS) {
264: EntryBinding binding = primary.getKeyBinding();
265: return (V) binding.entryToObject(key);
266: }
267: } else {
268: DatabaseEntry key = new DatabaseEntry();
269: DatabaseEntry data = new DatabaseEntry();
270: OperationStatus status = joinCursor.getNext(key, data,
271: lockMode);
272: if (status == OperationStatus.SUCCESS) {
273: EntityBinding binding = primary.getEntityBinding();
274: return (V) binding.entryToObject(key, data);
275: }
276: }
277: return null;
278: }
279:
280: public Iterator<V> iterator() {
281: return iterator(null);
282: }
283:
284: public Iterator<V> iterator(LockMode lockMode) {
285: return new BasicIterator<V>(this , lockMode);
286: }
287:
288: public void close() throws DatabaseException {
289:
290: doClose(null);
291: }
292:
293: private void doClose(DatabaseException firstException)
294: throws DatabaseException {
295:
296: if (joinCursor != null) {
297: try {
298: joinCursor.close();
299: joinCursor = null;
300: } catch (DatabaseException e) {
301: if (firstException == null) {
302: firstException = e;
303: }
304: }
305: }
306: for (int i = 0; i < cursors.length; i += 1) {
307: Cursor cursor = cursors[i];
308: if (cursor != null) {
309: try {
310: cursor.close();
311: cursors[i] = null;
312: } catch (DatabaseException e) {
313: if (firstException == null) {
314: firstException = e;
315: }
316: }
317: }
318: }
319: if (firstException != null) {
320: throw firstException;
321: }
322: }
323: }
324: }
|