001: package org.columba.core.search;
002:
003: import java.util.Collections;
004: import java.util.Hashtable;
005: import java.util.Iterator;
006: import java.util.List;
007: import java.util.Map;
008: import java.util.Vector;
009: import java.util.logging.Logger;
010:
011: import javax.swing.SwingUtilities;
012: import javax.swing.event.EventListenerList;
013:
014: import org.columba.api.command.IWorkerStatusController;
015: import org.columba.core.command.Command;
016: import org.columba.core.command.CommandProcessor;
017: import org.columba.core.search.api.IResultEvent;
018: import org.columba.core.search.api.IResultListener;
019: import org.columba.core.search.api.ISearchCriteria;
020: import org.columba.core.search.api.ISearchManager;
021: import org.columba.core.search.api.ISearchProvider;
022: import org.columba.core.search.api.ISearchRequest;
023: import org.columba.core.search.api.ISearchResult;
024:
025: public class SearchManager implements ISearchManager {
026:
027: private static final Logger LOG = Logger
028: .getLogger("org.columba.core.search.SearchManager");
029:
030: protected EventListenerList listenerList = new EventListenerList();
031:
032: private Map<String, ISearchProvider> providerMap = new Hashtable<String, ISearchProvider>();
033:
034: /**
035: * command hashtable used for paging to call the same command several times
036: * for a given <code>startIndex</code> and <code>resultCount</code>
037: */
038: private Map<String, Command> commandMap = new Hashtable<String, Command>();
039:
040: public SearchManager() {
041: // ensure map can be used by multiple threads
042: commandMap = Collections.synchronizedMap(commandMap);
043:
044: }
045:
046: /**
047: * @see org.columba.core.search.api.ISearchManager#executeSearch(java.lang.String,
048: * int, int)
049: */
050: public void executeSearch(String searchTerm, boolean searchInside,
051: int startIndex, int resultCount) {
052: if (searchTerm == null)
053: throw new IllegalArgumentException("searchTerm == null");
054: if (startIndex < 0)
055: throw new IllegalArgumentException(
056: "startIndex must be >= 0");
057: if (resultCount <= 0)
058: throw new IllegalArgumentException(
059: "resultCount must be > 0");
060:
061: Command command = new SearchCommand(new SearchCommandReference(
062: searchTerm, startIndex, resultCount, searchInside));
063:
064: // fire up search command
065: CommandProcessor.getInstance().addOp(command);
066: }
067:
068: /**
069: * @see org.columba.core.search.api.ISearchManager#executeSearch(java.lang.String,
070: * java.lang.String, int, int)
071: */
072: public void executeSearch(String searchTerm, String providerName,
073: boolean searchInside, int startIndex, int resultCount) {
074: if (searchTerm == null)
075: throw new IllegalArgumentException("searchTerm == null");
076: if (providerName == null)
077: throw new IllegalArgumentException("providerName == null");
078: if (startIndex < 0)
079: throw new IllegalArgumentException(
080: "startIndex must be >= 0");
081: if (resultCount <= 0)
082: throw new IllegalArgumentException(
083: "resultCount must be > 0");
084:
085: Command command = new SearchCommand(new SearchCommandReference(
086: searchTerm, providerName, startIndex, resultCount));
087: // fire up search command
088: CommandProcessor.getInstance().addOp(command);
089: }
090:
091: public void executeSearch(String searchTerm, String providerName,
092: String criteriaName, boolean searchInside, int startIndex,
093: int resultCount) {
094: if (searchTerm == null)
095: throw new IllegalArgumentException("searchTerm == null");
096: if (providerName == null)
097: throw new IllegalArgumentException("providerName == null");
098: if (criteriaName == null)
099: throw new IllegalArgumentException("criteriaName == null");
100: if (startIndex < 0)
101: throw new IllegalArgumentException(
102: "startIndex must be >= 0");
103: if (resultCount <= 0)
104: throw new IllegalArgumentException(
105: "resultCount must be > 0");
106:
107: Command command = new SearchCommand(new SearchCommandReference(
108: searchTerm, providerName, criteriaName, searchInside,
109: startIndex, resultCount));
110:
111: // fire up search command
112: CommandProcessor.getInstance().addOp(command);
113: }
114:
115: public void executeSearch(List<ISearchRequest> requests,
116: boolean allCriteria, boolean searchInside, int startIndex,
117: int resultCount) {
118: if (requests == null)
119: throw new IllegalArgumentException("requests == null");
120: if (startIndex < 0)
121: throw new IllegalArgumentException(
122: "startIndex must be >= 0");
123: if (resultCount <= 0)
124: throw new IllegalArgumentException(
125: "resultCount must be > 0");
126:
127: Command command = new SearchCommand(new SearchCommandReference(
128: requests, allCriteria, startIndex, resultCount,
129: searchInside));
130:
131: CommandProcessor.getInstance().addOp(command);
132: }
133:
134: /**
135: * @see org.columba.core.search.api.ISearchManager#getAllProviders()
136: */
137: public Iterator<ISearchProvider> getAllProviders() {
138: return providerMap.values().iterator();
139: }
140:
141: /**
142: * @see org.columba.core.search.api.ISearchManager#clearSearch(java.lang.String)
143: */
144: public void clearSearch(String searchTerm) {
145: // we assume user cancelled search
146: // -> remove cached command
147: if (commandMap.containsKey(searchTerm))
148: commandMap.remove(searchTerm);
149:
150: fireClearSearch(searchTerm);
151: }
152:
153: public void reset() {
154: fireReset();
155: }
156:
157: /**
158: * Propagates an event to all registered listeners notifying them of a item
159: * addition.
160: */
161: protected void fireNewResultArrived(String searchTerm,
162: String providerTechnicalName, ISearchCriteria criteria,
163: List<ISearchResult> result, int totalResultCount) {
164:
165: IResultEvent e = new ResultEvent(this , searchTerm,
166: providerTechnicalName, criteria, result,
167: totalResultCount);
168: // Guaranteed to return a non-null array
169: Object[] listeners = listenerList.getListenerList();
170:
171: // Process the listeners last to first, notifying
172: // those that are interested in this event
173: for (int i = listeners.length - 2; i >= 0; i -= 2) {
174: if (listeners[i] == IResultListener.class) {
175: ((IResultListener) listeners[i + 1]).resultArrived(e);
176: }
177: }
178: }
179:
180: protected void fireNewResultArrived(String providerTechnicalName,
181: List<ISearchResult> result, int totalResultCount) {
182:
183: IResultEvent e = new ResultEvent(this , providerTechnicalName,
184: result, totalResultCount);
185: // Guaranteed to return a non-null array
186: Object[] listeners = listenerList.getListenerList();
187:
188: // Process the listeners last to first, notifying
189: // those that are interested in this event
190: for (int i = listeners.length - 2; i >= 0; i -= 2) {
191: if (listeners[i] == IResultListener.class) {
192: ((IResultListener) listeners[i + 1]).resultArrived(e);
193: }
194: }
195: }
196:
197: protected void fireFinished() {
198: IResultEvent e = new ResultEvent(this );
199: // Guaranteed to return a non-null array
200: Object[] listeners = listenerList.getListenerList();
201:
202: // Process the listeners last to first, notifying
203: // those that are interested in this event
204: for (int i = listeners.length - 2; i >= 0; i -= 2) {
205: if (listeners[i] == IResultListener.class) {
206: ((IResultListener) listeners[i + 1]).finished(e);
207: }
208: }
209:
210: }
211:
212: /**
213: * Propagates an event to all registered listeners
214: */
215: protected void fireClearSearch(String searchTerm) {
216:
217: IResultEvent e = new ResultEvent(this , searchTerm);
218: // Guaranteed to return a non-null array
219: Object[] listeners = listenerList.getListenerList();
220:
221: // Process the listeners last to first, notifying
222: // those that are interested in this event
223: for (int i = listeners.length - 2; i >= 0; i -= 2) {
224: if (listeners[i] == IResultListener.class) {
225: ((IResultListener) listeners[i + 1]).clearSearch(e);
226: }
227: }
228: }
229:
230: protected void fireReset() {
231:
232: IResultEvent e = new ResultEvent(this );
233: // Guaranteed to return a non-null array
234: Object[] listeners = listenerList.getListenerList();
235:
236: // Process the listeners last to first, notifying
237: // those that are interested in this event
238: for (int i = listeners.length - 2; i >= 0; i -= 2) {
239: if (listeners[i] == IResultListener.class) {
240: ((IResultListener) listeners[i + 1]).reset(e);
241: }
242: }
243: }
244:
245: /**
246: * @see org.columba.core.search.api.ISearchManager#addResultListener(org.columba.core.search.api.IResultListener)
247: */
248: public void addResultListener(IResultListener l) {
249: listenerList.add(IResultListener.class, l);
250:
251: }
252:
253: /**
254: * @see org.columba.core.search.api.ISearchManager#removeResultListener(org.columba.core.search.api.IResultListener)
255: */
256: public void removeResultListener(IResultListener l) {
257: listenerList.remove(IResultListener.class, l);
258:
259: }
260:
261: /**
262: * Command executes the search.
263: * <p>
264: * In case new result results arrive, it ensures that all interested
265: * listeners are notified from inside the EDT.
266: * <p>
267: * FIXME: fdietz: No locking of folders currently implemented! TODO: fdietz:
268: * create new Command for every provider to introduce real "parallel" search
269: *
270: * @author fdietz
271: */
272: class SearchCommand extends Command {
273:
274: public SearchCommand(SearchCommandReference reference) {
275: super (reference);
276: }
277:
278: @Override
279: public void execute(IWorkerStatusController worker)
280: throws Exception {
281: final SearchCommandReference ref = (SearchCommandReference) getReference();
282:
283: if (ref.getType().equals(
284: SearchCommandReference.TYPE.SIMPLE_ALL)) {
285: Iterator<ISearchProvider> it = getAllProviders();
286: while (it.hasNext()) {
287: final ISearchProvider p = it.next();
288:
289: // query using all criteria
290: Iterator<ISearchCriteria> it2 = p.getAllCriteria(
291: ref.getSearchTerm()).iterator();
292: while (it2.hasNext()) {
293: ISearchCriteria c = it2.next();
294: String searchCriteriaTechnicalName = c
295: .getTechnicalName();
296: // execute search
297: doExecute(ref.getSearchTerm(), p,
298: searchCriteriaTechnicalName, ref
299: .isSearchInside(), ref
300: .getStartIndex(), ref
301: .getResultCount());
302: }
303:
304: }
305: } else if (ref
306: .getType()
307: .equals(
308: SearchCommandReference.TYPE.SIMPLE_SPECIFIC_PROVIDER)) {
309: String providerTechnicalName = ref
310: .getProviderTechnicalName();
311: ISearchProvider p = getProvider(providerTechnicalName);
312: // query using all criteria
313: Iterator<ISearchCriteria> it2 = p.getAllCriteria(
314: ref.getSearchTerm()).iterator();
315: while (it2.hasNext()) {
316: ISearchCriteria c = it2.next();
317: String searchCriteriaTechnicalName = c
318: .getTechnicalName();
319: // execute search
320: doExecute(ref.getSearchTerm(), p,
321: searchCriteriaTechnicalName, ref
322: .isSearchInside(), ref
323: .getStartIndex(), ref
324: .getResultCount());
325: }
326: } else if (ref
327: .getType()
328: .equals(
329: SearchCommandReference.TYPE.SIMPLE_SPECIFIC_CRITERIA)) {
330: String providerTechnicalName = ref
331: .getProviderTechnicalName();
332: ISearchProvider p = getProvider(providerTechnicalName);
333: doExecute(ref.getSearchTerm(), p, ref
334: .getSearchCriteriaTechnicalName(), ref
335: .isSearchInside(), ref.getStartIndex(), ref
336: .getResultCount());
337: } else if (ref.getType().equals(
338: SearchCommandReference.TYPE.COMPLEX)) {
339:
340: // first, create bucket for each provider
341: Map<String, Vector<ISearchRequest>> map = new Hashtable<String, Vector<ISearchRequest>>();
342: Iterator<ISearchRequest> it = ref.getRequest()
343: .iterator();
344: while (it.hasNext()) {
345: ISearchRequest r = it.next();
346: String providerName = r.getProvider();
347:
348: if (map.containsKey(providerName)) {
349: Vector<ISearchRequest> v = map
350: .get(providerName);
351: v.add(r);
352: } else {
353: Vector<ISearchRequest> v = new Vector<ISearchRequest>();
354: v.add(r);
355: map.put(providerName, v);
356: }
357: }
358:
359: // now search through all buckets
360: Iterator<String> it2 = map.keySet().iterator();
361: while (it2.hasNext()) {
362: final String providerName = it2.next();
363: ISearchProvider p = getProvider(providerName);
364: Vector<ISearchRequest> v = map.get(providerName);
365: final List<ISearchResult> resultList = p.query(v,
366: ref.isMatchAll(), ref.isSearchInside(), ref
367: .getStartIndex(), ref
368: .getResultCount());
369:
370: final int totalResultCount = p
371: .getTotalResultCount();
372:
373: // notify all listeners that new search results arrived
374:
375: // ensure this is called in the EDT
376: Runnable run = new Runnable() {
377: public void run() {
378: fireNewResultArrived(providerName,
379: resultList, totalResultCount);
380: }
381: };
382: SwingUtilities.invokeLater(run);
383: }
384:
385: }
386:
387: // notify that search is finished
388: Runnable run = new Runnable() {
389: public void run() {
390: fireFinished();
391: }
392: };
393: SwingUtilities.invokeLater(run);
394:
395: // create list of all registered providers
396: // Iterator<ISearchProvider> it = getAllProviders();
397: // while (it.hasNext()) {
398: // final ISearchProvider p = it.next();
399: //
400: // // if providerName specified
401: // // -> skip if this isn't the matching provider
402: // if (providerTechnicalName != null) {
403: // if (!providerTechnicalName.equals(p.getTechnicalName()))
404: // continue;
405: // }
406: //
407: // // keep search history
408: // // SearchHistoryList.getInstance().add(ref.getSearchTerm(), p,
409: // // c);
410: //
411: // if (ref.getSearchCriteriaTechnicalName() == null) {
412: // // query using all criteria
413: // Iterator<ISearchCriteria> it2 = p.getAllCriteria(
414: // ref.getSearchTerm()).iterator();
415: // while (it2.hasNext()) {
416: // ISearchCriteria c = it2.next();
417: // String searchCriteriaTechnicalName = c
418: // .getTechnicalName();
419: // // execute search
420: // doExecute(ref, p, searchCriteriaTechnicalName, ref
421: // .isSearchInside());
422: // }
423: // } else {
424: // // query only a single criteria
425: //
426: // // execute search
427: // doExecute(ref, p, ref.getSearchCriteriaTechnicalName(), ref
428: // .isSearchInside());
429: // }
430: //
431: // }
432:
433: }
434:
435: private void doExecute(final String searchTerm,
436: final ISearchProvider p,
437: final String searchCriteriaTechnicalName,
438: final boolean searchInside, final int startIndex,
439: final int resultCount) {
440:
441: // query provider
442: final List<ISearchResult> resultList = p.query(searchTerm,
443: searchCriteriaTechnicalName, searchInside,
444: startIndex, resultCount);
445:
446: // retrieve total result count
447: final int totalResultCount = p.getTotalResultCount();
448:
449: // notify all listeners that new search results arrived
450:
451: // ensure this is called in the EDT
452: Runnable run = new Runnable() {
453: public void run() {
454:
455: fireNewResultArrived(searchTerm, p
456: .getTechnicalName(), p.getCriteria(
457: searchCriteriaTechnicalName, searchTerm),
458: resultList, totalResultCount);
459:
460: }
461: };
462: SwingUtilities.invokeLater(run);
463: }
464:
465: }
466:
467: /**
468: * @see org.columba.core.search.api.ISearchManager#getProvider(java.lang.String)
469: */
470: public ISearchProvider getProvider(String technicalName) {
471: return providerMap.get(technicalName);
472: }
473:
474: public void registerProvider(ISearchProvider p) {
475: providerMap.put(p.getTechnicalName(), p);
476: }
477:
478: public void unregisterProvider(ISearchProvider p) {
479: providerMap.remove(p.getTechnicalName());
480: }
481:
482: }
|