001: //The contents of this file are subject to the Mozilla Public License Version 1.1
002: //(the "License"); you may not use this file except in compliance with the
003: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
004: //
005: //Software distributed under the License is distributed on an "AS IS" basis,
006: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
007: //for the specific language governing rights and
008: //limitations under the License.
009: //
010: //The Original Code is "The Columba Project"
011: //
012: //The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
013: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
014: //
015: //All Rights Reserved.
016:
017: package org.columba.mail.folder.search;
018:
019: import java.util.Arrays;
020: import java.util.Hashtable;
021: import java.util.LinkedList;
022: import java.util.List;
023: import java.util.ListIterator;
024: import java.util.logging.Logger;
025:
026: import javax.swing.JOptionPane;
027:
028: import org.columba.api.command.IStatusObservable;
029: import org.columba.api.plugin.IExtension;
030: import org.columba.api.plugin.IExtensionHandler;
031: import org.columba.core.base.ListTools;
032: import org.columba.core.filter.AbstractFilter;
033: import org.columba.core.filter.Filter;
034: import org.columba.core.filter.FilterCriteria;
035: import org.columba.core.filter.FilterRule;
036: import org.columba.core.filter.IFilter;
037: import org.columba.core.filter.IFilterCriteria;
038: import org.columba.core.filter.IFilterRule;
039: import org.columba.core.plugin.PluginManager;
040: import org.columba.mail.folder.AbstractMessageFolder;
041: import org.columba.mail.folder.event.FolderListener;
042: import org.columba.mail.folder.event.IFolderEvent;
043: import org.columba.mail.plugin.IExtensionHandlerKeys;
044: import org.columba.mail.util.MailResourceLoader;
045:
046: /**
047: * Divides search requests and passes them along to the optimized
048: * {@link QueryEngine} for execution.
049: * <p>
050: * Search requests which can't be performed by the {@link QueryEngine}, are
051: * executed by DefaultSearchEngine using the plugin mechanism.
052: *
053: * @author tstich, fdietz
054: */
055: public class DefaultSearchEngine {
056: /** JDK 1.4+ logging framework logger, used for logging. */
057: private static final Logger LOG = Logger
058: .getLogger("org.columba.mail.folder.search");
059:
060: /**
061: * Filter plugins are cached and reused, instead of re-instanciated all the
062: * time
063: */
064: private static Hashtable filterCache;
065:
066: /**
067: * AbstractMessageFolder on which the search is applied
068: */
069: private AbstractMessageFolder folder;
070:
071: /**
072: * The default query engine used by the search-engine
073: */
074: private QueryEngine nonDefaultEngine;
075:
076: /**
077: * Constructor
078: *
079: * @param folder
080: * folder on which the search is applied
081: */
082: public DefaultSearchEngine(AbstractMessageFolder folder) {
083: this .folder = folder;
084: filterCache = new Hashtable();
085: nonDefaultEngine = new DummyQueryEngine();
086: folder.addFolderListener(new FolderListener() {
087: public void messageAdded(IFolderEvent e) {
088: try {
089: getNonDefaultEngine().messageAdded(e.getChanges());
090: } catch (Exception ex) {
091: }
092: }
093:
094: public void messageRemoved(IFolderEvent e) {
095: try {
096: getNonDefaultEngine()
097: .messageRemoved(e.getChanges());
098: } catch (Exception ex) {
099: }
100: }
101:
102: public void folderPropertyChanged(IFolderEvent e) {
103: }
104:
105: public void folderAdded(IFolderEvent e) {
106: }
107:
108: public void folderRemoved(IFolderEvent e) {
109: }
110:
111: public void messageFlagChanged(IFolderEvent e) {
112: // not needed
113:
114: }
115: });
116: }
117:
118: public IStatusObservable getObservable() {
119: return folder.getObservable();
120: }
121:
122: protected synchronized AbstractFilter getFilter(
123: IFilterCriteria filterCriteria, String type) {
124: // try to re-use already instanciated class
125: if (filterCache.containsKey(type) == true) {
126: AbstractFilter f = (AbstractFilter) filterCache.get(type);
127:
128: // setup filter configuration
129: f.setUp(filterCriteria);
130:
131: return f;
132: }
133:
134: AbstractFilter instance = null;
135:
136: try {
137: IExtensionHandler handler = PluginManager
138: .getInstance()
139: .getExtensionHandler(
140: IExtensionHandlerKeys.ORG_COLUMBA_MAIL_FILTER);
141: IExtension extension = handler.getExtension(type);
142:
143: instance = (AbstractFilter) extension
144: .instanciateExtension(null);
145: } catch (Exception ex) {
146: JOptionPane
147: .showMessageDialog(null,
148: "Error while trying to load filter plugin ="
149: + type);
150: ex.printStackTrace();
151: }
152:
153: // setup filter configuration
154: instance.setUp(filterCriteria);
155:
156: if (instance != null) {
157: filterCache.put(type, instance);
158: }
159:
160: return instance;
161: }
162:
163: protected boolean processRule(Object uid, IFilterCriteria criteria,
164: String type) throws Exception {
165: if (type == null) {
166: JOptionPane.showMessageDialog(null,
167: "Filter type couldn't been found", "Error occured",
168: JOptionPane.ERROR_MESSAGE);
169:
170: return false;
171: }
172:
173: AbstractFilter instance = getFilter(criteria, type);
174:
175: if (instance == null) {
176: return false;
177: }
178:
179: return instance.process(folder, uid);
180: }
181:
182: protected List processCriteria(IFilterRule rule, List uids)
183: throws Exception {
184: LinkedList result = new LinkedList();
185: boolean b;
186:
187: int match = rule.getConditionInt();
188:
189: ListIterator it = uids.listIterator();
190:
191: Object uid;
192:
193: // MATCH_ALL
194: if (match == FilterRule.MATCH_ALL) {
195: while (it.hasNext()) {
196: b = true;
197: uid = it.next();
198:
199: for (int i = 0; (i < rule.count()) && b; i++) {
200: IFilterCriteria criteria = rule.get(i);
201:
202: String type = criteria.getTypeString();
203:
204: b &= processRule(uid, criteria, type);
205: }
206:
207: if (b) {
208: result.add(uid);
209: }
210: }
211: } else { // MATCH ANY
212:
213: while (it.hasNext()) {
214: b = false;
215: uid = it.next();
216:
217: for (int i = 0; (i < rule.count()) && !b; i++) {
218: IFilterCriteria criteria = rule.get(i);
219:
220: String type = criteria.getTypeString();
221:
222: b = processRule(uid, criteria, type);
223: }
224:
225: if (b) {
226: result.add(uid);
227: }
228: }
229: }
230:
231: // result = mergeFilterResult(v, uids, match);
232: // only for debugging purpose
233: // printList( result );
234: return result;
235: }
236:
237: protected void divideFilterRule(IFilterRule filterRule,
238: IFilterRule notDefaultEngine, IFilterRule defaultEngine) {
239: IFilterCriteria actCriteria;
240:
241: String[] caps = getNonDefaultEngine().getCaps();
242:
243: List capList = Arrays.asList(caps);
244:
245: notDefaultEngine.setCondition(filterRule.getCondition());
246: defaultEngine.setCondition(filterRule.getCondition());
247:
248: for (int i = 0; i < filterRule.count(); i++) {
249: actCriteria = filterRule.get(i);
250:
251: if (capList.contains(actCriteria.getTypeString())) {
252: // search request isn't covered by query engine
253: // -> fall back to default search engine
254: defaultEngine.add(actCriteria);
255: } else {
256: // this search request is covered by the query engine
257: notDefaultEngine.add(actCriteria);
258: }
259: }
260: }
261:
262: /**
263: * @see org.columba.mail.folder.SearchEngineInterface#searchMessages(org.columba.mail.filter.Filter,
264: * java.lang.Object, org.columba.api.command.IWorkerStatusController)
265: */
266: public Object[] searchMessages(IFilter filter, Object[] uids)
267: throws Exception {
268: if (!filter.getEnabled()) {
269: // filter is disabled
270: return new Object[] {};
271: }
272:
273: List notDefaultEngineResult = null;
274: List defaultEngineResult = new LinkedList();
275:
276: IFilterRule filterRule = filter.getFilterRule();
277:
278: IFilterRule notDefaultEngine = new FilterRule();
279: IFilterRule defaultEngine = new FilterRule();
280:
281: divideFilterRule(filterRule, notDefaultEngine, defaultEngine);
282:
283: if (defaultEngine.count() > 0) {
284: try {
285: if (uids != null) {
286: defaultEngineResult = getNonDefaultEngine()
287: .queryEngine(defaultEngine, uids);
288: } else {
289: defaultEngineResult = getNonDefaultEngine()
290: .queryEngine(defaultEngine);
291: }
292: } catch (Exception e) {
293: e.printStackTrace();
294: LOG
295: .warning("NonDefaultSearch engine "
296: + nonDefaultEngine.toString()
297: + "reported an error: falling back to default search:\n"
298: + e.getMessage());
299: defaultEngine = new FilterRule();
300: notDefaultEngine = filter.getFilterRule();
301: }
302: }
303:
304: if (notDefaultEngine.count() == 0) {
305: notDefaultEngineResult = defaultEngineResult;
306: } else {
307: // MATCH_ALL
308: if (filterRule.getConditionInt() == FilterRule.MATCH_ALL) {
309: if (defaultEngine.count() > 0) {
310: notDefaultEngineResult = processCriteria(
311: notDefaultEngine, defaultEngineResult);
312: } else {
313: if (uids != null) {
314: notDefaultEngineResult = processCriteria(
315: notDefaultEngine, Arrays.asList(uids));
316: } else {
317: notDefaultEngineResult = processCriteria(
318: notDefaultEngine, Arrays.asList(folder
319: .getUids()));
320: }
321: }
322: }
323: // MATCH_ANY
324: else {
325: if (uids != null) {
326: List uidList = new LinkedList(Arrays.asList(uids));
327: ListTools.substract(uidList, defaultEngineResult);
328:
329: notDefaultEngineResult = processCriteria(
330: notDefaultEngine, uidList);
331:
332: notDefaultEngineResult.addAll(defaultEngineResult);
333: } else {
334: notDefaultEngineResult = processCriteria(
335: notDefaultEngine, Arrays.asList(folder
336: .getUids()));
337: }
338: }
339: }
340:
341: /*
342: * worker.setDisplayText( "Search Result: " +
343: * notDefaultEngineResult.size() + " messages found in " +
344: * (System.currentTimeMillis() - startTime) + " ms");
345: */
346: return notDefaultEngineResult.toArray();
347: }
348:
349: /**
350: * @see org.columba.mail.folder.SearchEngineInterface#searchMessages(org.columba.mail.filter.Filter,
351: * org.columba.api.command.IWorkerStatusController)
352: */
353: public Object[] searchMessages(IFilter filter) throws Exception {
354: if (getObservable() != null) {
355: getObservable().setMessage(
356: MailResourceLoader.getString("statusbar",
357: "message", "search"));
358: }
359:
360: // return searchMessages(filter, null);
361: Object[] result = searchMessages(filter, null);
362:
363: if (getObservable() != null) {
364: // clear status bar message now we are done (with a delay)
365: getObservable().clearMessageWithDelay();
366: }
367:
368: return result;
369: }
370:
371: /**
372: * @see org.columba.mail.folder.DefaultSearchEngine#queryEngine(org.columba.mail.filter.FilterRule,
373: * java.lang.Object, org.columba.api.command.IWorkerStatusController)
374: */
375: protected List queryEngine(IFilterRule filter, Object[] uids)
376: throws Exception {
377: return processCriteria(filter, Arrays.asList(uids));
378: }
379:
380: /**
381: * @see org.columba.mail.folder.DefaultSearchEngine#queryEngine(org.columba.mail.filter.FilterRule,
382: * org.columba.api.command.IWorkerStatusController)
383: */
384: protected List queryEngine(IFilterRule filter) throws Exception {
385: Object[] uids = folder.getUids();
386:
387: return processCriteria(filter, Arrays.asList(uids));
388: }
389:
390: /**
391: * @return
392: */
393: public QueryEngine getNonDefaultEngine() {
394: return nonDefaultEngine;
395: }
396:
397: /**
398: * @param engine
399: */
400: public void setNonDefaultEngine(QueryEngine engine) {
401: nonDefaultEngine = engine;
402: }
403:
404: public void sync() throws Exception {
405: getNonDefaultEngine().sync();
406: }
407:
408: public void save() {
409: getNonDefaultEngine().save();
410: }
411: }
|