Author: norman
Date: Tue May 31 19:15:53 2011
New Revision: 1129859

URL: http://svn.apache.org/viewvc?rev=1129859&view=rev
Log:
More work on Lucene search index. Part of MAILBOX-10    


Modified:
    james/mailbox/trunk/pom.xml
    james/mailbox/trunk/store/pom.xml
    
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/ImapSearchAnalyzer.java
    
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndex.java
    
james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndexTest.java

Modified: james/mailbox/trunk/pom.xml
URL: 
http://svn.apache.org/viewvc/james/mailbox/trunk/pom.xml?rev=1129859&r1=1129858&r2=1129859&view=diff
==============================================================================
--- james/mailbox/trunk/pom.xml (original)
+++ james/mailbox/trunk/pom.xml Tue May 31 19:15:53 2011
@@ -475,14 +475,18 @@
       <dependency>
         <groupId>org.apache.lucene</groupId>
         <artifactId>lucene-core</artifactId>
-        <version>3.0.3</version>
+        <version>3.1.0</version>
       </dependency>
       <dependency>
         <groupId>org.apache.lucene</groupId>
         <artifactId>lucene-analyzers</artifactId>
-        <version>3.0.3</version>
+        <version>3.1.0</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.lucene</groupId>
+        <artifactId>lucene-smartcn</artifactId>
+        <version>3.1.0</version>
       </dependency>
-
 
       <!--
         START Mail

Modified: james/mailbox/trunk/store/pom.xml
URL: 
http://svn.apache.org/viewvc/james/mailbox/trunk/store/pom.xml?rev=1129859&r1=1129858&r2=1129859&view=diff
==============================================================================
--- james/mailbox/trunk/store/pom.xml (original)
+++ james/mailbox/trunk/store/pom.xml Tue May 31 19:15:53 2011
@@ -57,6 +57,10 @@
       <artifactId>lucene-analyzers</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.apache.lucene</groupId>
+      <artifactId>lucene-smartcn</artifactId>
+    </dependency>
+    <dependency>
       <groupId>commons-lang</groupId>
       <artifactId>commons-lang</artifactId>
     </dependency>

Modified: 
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/ImapSearchAnalyzer.java
URL: 
http://svn.apache.org/viewvc/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/ImapSearchAnalyzer.java?rev=1129859&r1=1129858&r2=1129859&view=diff
==============================================================================
--- 
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/ImapSearchAnalyzer.java
 (original)
+++ 
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/ImapSearchAnalyzer.java
 Tue May 31 19:15:53 2011
@@ -21,9 +21,11 @@ package org.apache.james.mailbox.store.l
 import java.io.Reader;
 
 import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.LowerCaseFilter;
 import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.WhitespaceTokenizer;
+import org.apache.lucene.analysis.cn.smart.SentenceTokenizer;
 import org.apache.lucene.analysis.ngram.NGramTokenFilter;
+import org.apache.lucene.util.Version;
 
 /**
 *
@@ -38,12 +40,22 @@ import org.apache.lucene.analysis.ngram.
 */
 public final class ImapSearchAnalyzer extends Analyzer {
 
+    private final int minTokenLength;
+    private final int maxTokenLength;
+    
+    public ImapSearchAnalyzer() {
+        this(3, 40);
+    }
+    public ImapSearchAnalyzer(int minTokenLength, int maxTokenLength) {
+        this.minTokenLength = minTokenLength;
+        this.maxTokenLength = maxTokenLength;
+    }
    /*
     * (non-Javadoc)
     * @see org.apache.lucene.analysis.Analyzer#tokenStream(java.lang.String, 
java.io.Reader)
     */
    public TokenStream tokenStream(String fieldName, Reader reader) {
-       return new NGramTokenFilter(new WhitespaceTokenizer(reader),2 , 4);
+       return new NGramTokenFilter(new LowerCaseFilter(Version.LUCENE_31, new 
SentenceTokenizer(reader)), minTokenLength, maxTokenLength);
    }
    
 }
\ No newline at end of file

Modified: 
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndex.java
URL: 
http://svn.apache.org/viewvc/james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndex.java?rev=1129859&r1=1129858&r2=1129859&view=diff
==============================================================================
--- 
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndex.java
 (original)
+++ 
james/mailbox/trunk/store/src/main/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndex.java
 Tue May 31 19:15:53 2011
@@ -46,24 +46,32 @@ import org.apache.james.mailbox.SearchQu
 import org.apache.james.mailbox.SearchQuery.HeaderOperator;
 import org.apache.james.mailbox.SearchQuery.NumericOperator;
 import org.apache.james.mailbox.SearchQuery.NumericRange;
+import org.apache.james.mailbox.SearchQuery.UidCriterion;
 import org.apache.james.mailbox.UnsupportedSearchException;
 import org.apache.james.mailbox.store.MessageSearchIndex;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
 import org.apache.james.mailbox.store.mail.model.Message;
 import org.apache.james.mime4j.MimeException;
 import org.apache.james.mime4j.descriptor.BodyDescriptor;
+import org.apache.james.mime4j.field.AddressListField;
+import org.apache.james.mime4j.field.address.Address;
+import org.apache.james.mime4j.field.address.AddressList;
+import org.apache.james.mime4j.field.address.Group;
+import org.apache.james.mime4j.field.address.MailboxList;
 import org.apache.james.mime4j.message.Header;
 import org.apache.james.mime4j.message.SimpleContentHandler;
 import org.apache.james.mime4j.parser.MimeStreamParser;
 import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.PerFieldAnalyzerWrapper;
+import org.apache.lucene.analysis.SimpleAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Field.Index;
 import org.apache.lucene.document.Field.Store;
 import org.apache.lucene.document.NumericField;
 import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
@@ -78,6 +86,7 @@ import org.apache.lucene.search.TermQuer
 import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.LockObtainFailedException;
+import org.apache.lucene.util.Version;
 
 /**
  * Lucene based {@link MessageSearchIndex} which offers message searching.
@@ -97,6 +106,7 @@ public class LuceneMessageSearchIndex<Id
      */
     public final static String ID_FIELD ="id";
     
+    
     /**
      * {@link Field} which will contain uid of the {@link MailboxMembership}
      */
@@ -124,7 +134,15 @@ public class LuceneMessageSearchIndex<Id
      * {@link Field} which will contain the whole message header of the {@link 
MailboxMembership}
      */
     public final static String HEADERS_FIELD ="headers";
+
+    public final static String MODSEQ_FIELD = "modSeq";
+
     
+    public final static String TO_FIELD ="to";
+    public final static String CC_FIELD ="cc";
+    public final static String BCC_FIELD ="bcc";
+    public final static String FROM_FIELD ="from";
+
 
     public final static String INTERNAL_DATE_FIELD_YEAR_RESOLUTION 
="internaldateYearResolution";
  
@@ -150,11 +168,11 @@ public class LuceneMessageSearchIndex<Id
     
     private final static Sort UID_SORT = new Sort(new SortField(UID_FIELD, 
SortField.LONG));
     
-    public LuceneMessageSearchIndex(Directory directory) throws 
CorruptIndexException, LockObtainFailedException, IOException {
-        this(new IndexWriter(directory, createAnalyzer(), true, 
IndexWriter.MaxFieldLength.UNLIMITED));
+    public LuceneMessageSearchIndex(Directory directory, boolean breakIMAPRFC) 
throws CorruptIndexException, LockObtainFailedException, IOException {
+        this(new IndexWriter(directory,  new 
IndexWriterConfig(Version.LUCENE_31, createAnalyzer(breakIMAPRFC))));
     }
     
-   
+    
     public LuceneMessageSearchIndex(IndexWriter writer) {
         this.writer = writer;
     }
@@ -170,13 +188,20 @@ public class LuceneMessageSearchIndex<Id
     /**
      * Create a {@link Analyzer} which is used to index the {@link 
MailboxMembership}'s
      * 
+     * @param breakIMAPRFC 
+     * 
      * @return analyzer
      */
-    public static Analyzer createAnalyzer() {
-        PerFieldAnalyzerWrapper wrapper = new PerFieldAnalyzerWrapper(new 
ImapSearchAnalyzer());
-        return wrapper;
+    private static Analyzer createAnalyzer(boolean breakIMAPRFC) {
+        if (breakIMAPRFC) {
+            return new SimpleAnalyzer(Version.LUCENE_31);
+        } else {
+            return new ImapSearchAnalyzer();
+        }
+
     }
     
+    
     /*
      * (non-Javadoc)
      * @see 
org.apache.james.mailbox.store.MessageSearchIndex#search(org.apache.james.mailbox.MailboxSession,
 org.apache.james.mailbox.store.mail.model.Mailbox, 
org.apache.james.mailbox.SearchQuery)
@@ -186,10 +211,10 @@ public class LuceneMessageSearchIndex<Id
         IndexSearcher searcher = null;
 
         try {
-            searcher = new IndexSearcher(writer.getReader());
+            searcher = new IndexSearcher(IndexReader.open(writer, true));
             BooleanQuery query = new BooleanQuery();
             query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, 
mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST);
-            query.add(createQuery(searchQuery), BooleanClause.Occur.MUST);
+            query.add(createQuery(searchQuery, mailbox), 
BooleanClause.Occur.MUST);
             
             // query for all the documents sorted by uid
             TopDocs docs = searcher.search(query, null, maxQueryResults, 
UID_SORT);
@@ -213,24 +238,23 @@ public class LuceneMessageSearchIndex<Id
     }
 
     /**
-     * Create a new {@link Document} for the given {@link MailboxMembership}
+     * Create a new {@link Document} for the given {@link MailboxMembership}. 
This Document does not contain any flags data. The {@link Flags} are stored in 
a seperate Document. 
+     * 
+     * See {@link #createFlagsDocument(Message)}
      * 
      * @param membership
      * @return document
      */
-    public static Document createDocument(Message<?> membership) throws 
MailboxException{
+    private Document createMessageDocument(Message<?> membership) throws 
MailboxException{
         final Document doc = new Document();
         // TODO: Better handling
-        doc.add(new Field(MAILBOX_ID_FIELD, 
membership.getMailboxId().toString(), Store.NO, Index.NOT_ANALYZED));
-        
-        
+        doc.add(new Field(MAILBOX_ID_FIELD, 
membership.getMailboxId().toString().toLowerCase(Locale.US), Store.YES, 
Index.NOT_ANALYZED));
         doc.add(new NumericField(UID_FIELD,Store.YES, 
true).setLongValue(membership.getUid()));
         
         // create an unqiue key for the document which can be used later on 
updates to find the document
-        doc.add(new Field(ID_FIELD, membership.getMailboxId().toString() +"-" 
+ Long.toString(membership.getUid()), Store.NO, Index.NOT_ANALYZED));
+        doc.add(new Field(ID_FIELD, 
membership.getMailboxId().toString().toLowerCase(Locale.US) +"-" + 
Long.toString(membership.getUid()), Store.YES, Index.NOT_ANALYZED));
         
-        // add flags
-        indexFlags(membership.createFlags(), doc);
+      
 
         doc.add(new NumericField(INTERNAL_DATE_FIELD_YEAR_RESOLUTION,Store.NO, 
true).setLongValue(DateUtils.truncate(membership.getInternalDate(),Calendar.YEAR).getTime()));
         doc.add(new 
NumericField(INTERNAL_DATE_FIELD_MONTH_RESOLUTION,Store.NO, 
true).setLongValue(DateUtils.truncate(membership.getInternalDate(),Calendar.MONTH).getTime()));
@@ -253,9 +277,47 @@ public class LuceneMessageSearchIndex<Id
                 
                 Iterator<org.apache.james.mime4j.parser.Field> fields = 
header.iterator();
                 while(fields.hasNext()) {
-                   org.apache.james.mime4j.parser.Field f = fields.next();
-                    doc.add(new Field(HEADERS_FIELD, f.toString() ,Store.NO, 
Index.ANALYZED));
-                    doc.add(new Field(PREFIX_HEADER_FIELD + f.getName(), 
f.getBody() ,Store.NO, Index.ANALYZED));
+                    org.apache.james.mime4j.parser.Field f = fields.next();
+                    String headerName = f.getName().toLowerCase(Locale.US);
+                    String fullValue =  f.toString().toLowerCase(Locale.US);
+                    doc.add(new Field(HEADERS_FIELD, fullValue, Store.NO, 
Index.ANALYZED));
+                    doc.add(new Field(PREFIX_HEADER_FIELD + headerName, 
f.getBody().toLowerCase(Locale.US) ,Store.NO, Index.ANALYZED));
+                    
+                    if (f instanceof AddressListField) {
+                        AddressListField addressField = (AddressListField) f;
+                        String field = null;;
+                        if ("To".equalsIgnoreCase(headerName)) {
+                            field = TO_FIELD;
+                        } else if ("From".equalsIgnoreCase(headerName)) {
+                            field = FROM_FIELD;
+                        } else if ("Bcc".equalsIgnoreCase(headerName)) {
+                            field = BCC_FIELD;
+                        } else if ("Cc".equalsIgnoreCase(headerName)) {
+                            field = CC_FIELD;
+                        }
+                        
+                        // Check if we can index the the addressfield in the 
right manner
+                        if (field != null) {
+                            AddressList aList = addressField.getAddressList();
+
+                            if (aList != null) {
+                                for (int i = 0; i < aList.size(); i++) {
+                                    Address address = aList.get(i);
+                                    if (address instanceof 
org.apache.james.mime4j.field.address.Mailbox) {
+                                        String value = 
((org.apache.james.mime4j.field.address.Mailbox) 
address).getEncodedString().toLowerCase(Locale.US);
+                                        doc.add(new Field(field, value, 
Store.NO, Index.ANALYZED));
+                                        
+                                    } else if (address instanceof Group) {
+                                        MailboxList mList = ((Group) 
address).getMailboxes();
+                                        for (int a = 0; a < mList.size(); a++) 
{
+                                            String value = 
mList.get(i).getEncodedString().toLowerCase(Locale.US);
+                                            doc.add(new Field(field, value, 
Store.NO, Index.ANALYZED));
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
                 }
            
             }
@@ -276,7 +338,7 @@ public class LuceneMessageSearchIndex<Id
                         out.write(b);
                     }
                     out.flush();
-                    doc.add(new Field(BODY_FIELD,  
out.toString(charset),Store.NO, Index.ANALYZED));
+                    doc.add(new Field(BODY_FIELD,  
out.toString(charset).toLowerCase(Locale.US),Store.NO, Index.ANALYZED));
                     out.close();
                     
                 }
@@ -307,19 +369,19 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createQuery(SearchQuery searchQuery) throws 
UnsupportedSearchException {
+    private Query createQuery(SearchQuery searchQuery, Mailbox<?> mailbox) 
throws UnsupportedSearchException, MailboxException {
         List<Criterion> crits = searchQuery.getCriterias();
         BooleanQuery booleanQuery = new BooleanQuery();
 
         for (int i = 0; i < crits.size(); i++) {
-            booleanQuery.add(createQuery(crits.get(i)), 
BooleanClause.Occur.MUST);
+            booleanQuery.add(createQuery(crits.get(i), mailbox), 
BooleanClause.Occur.MUST);
         }
         return booleanQuery;
 
     }
 
 
-    private static String toInteralDateField(DateResolution res) {
+    private String toInteralDateField(DateResolution res) {
         String field;
         switch (res) {
         case Year:
@@ -354,7 +416,7 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query 
createInternalDateQuery(SearchQuery.InternalDateCriterion crit) throws 
UnsupportedSearchException {
+    private Query createInternalDateQuery(SearchQuery.InternalDateCriterion 
crit) throws UnsupportedSearchException {
         DateOperator op = crit.getOperator();
         DateResolution res = op.getDateResultion();
         Date date = op.getDate();
@@ -380,7 +442,7 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createSizeQuery(SearchQuery.SizeCriterion crit) throws 
UnsupportedSearchException {
+    private Query createSizeQuery(SearchQuery.SizeCriterion crit) throws 
UnsupportedSearchException {
         NumericOperator op = crit.getOperator();
         switch (op.getType()) {
         case EQUALS:
@@ -401,14 +463,16 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createHeaderQuery(SearchQuery.HeaderCriterion crit) 
throws UnsupportedSearchException {
+    private Query createHeaderQuery(SearchQuery.HeaderCriterion crit) throws 
UnsupportedSearchException {
         HeaderOperator op = crit.getOperator();
-        String fieldName = PREFIX_HEADER_FIELD + crit.getHeaderName();
+        String fieldName = PREFIX_HEADER_FIELD + 
crit.getHeaderName().toLowerCase(Locale.US);
         if (op instanceof SearchQuery.ContainsOperator) {
             ContainsOperator cop = (ContainsOperator) op;
-            return new TermQuery(new Term(fieldName, cop.getValue()));
+            return new PrefixQuery(new Term(fieldName, 
cop.getValue().toLowerCase(Locale.US)));
         } else if (op instanceof SearchQuery.ExistsOperator){
             return new PrefixQuery(new Term(fieldName, ""));
+        } else if (op instanceof SearchQuery.AddressOperator) {
+            return new PrefixQuery(new Term(fieldName.toLowerCase(), 
((SearchQuery.AddressOperator) op).getAddress().toLowerCase(Locale.US)));
         } else {
             // Operator not supported
             throw new UnsupportedSearchException();
@@ -422,35 +486,120 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createUidQuery(SearchQuery.UidCriterion crit) throws 
UnsupportedSearchException {
+    private Query createUidQuery(SearchQuery.UidCriterion crit) throws 
UnsupportedSearchException {
         NumericRange[] ranges = crit.getOperator().getRange();
         BooleanQuery rangesQuery = new BooleanQuery();
         for (int i = 0; i < ranges.length; i++) {
             NumericRange range = ranges[i];
             rangesQuery.add(NumericRangeQuery.newLongRange(UID_FIELD, 
range.getLowValue(), range.getHighValue(), true, true), 
BooleanClause.Occur.SHOULD);
-        }
+        }        
+        rangesQuery.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), 
BooleanClause.Occur.MUST_NOT);
         return rangesQuery;
     }
     
+    
+    /**
+     * Return a {@link Query} which is build based on the given {@link 
SearchQuery.UidCriterion}
+     * 
+     * @param crit
+     * @return query
+     * @throws UnsupportedSearchException
+     */
+    private Query createModSeqQuery(SearchQuery.ModSeqCriterion crit) throws 
UnsupportedSearchException {
+        NumericOperator op = crit.getOperator();
+        switch (op.getType()) {
+        case EQUALS:
+            return NumericRangeQuery.newLongRange(MODSEQ_FIELD, op.getValue(), 
op.getValue(), true, true);
+        case GREATER_THAN:
+            return NumericRangeQuery.newLongRange(MODSEQ_FIELD, op.getValue(), 
Long.MAX_VALUE, false, true);
+        case LESS_THAN:
+            return NumericRangeQuery.newLongRange(MODSEQ_FIELD, 
Long.MIN_VALUE, op.getValue(), true, false);
+        default:
+            throw new UnsupportedSearchException();
+        }
+    }
+    
     /**
-     * Return a {@link Query} which is build based on the given {@link 
SearchQuery.FlagCriterion}
+     * Return a {@link Query} which is build based on the given {@link 
SearchQuery.FlagCriterion}. This is kind of a hack
+     * as it will do a search for the flags in this method and 
      * 
      * @param crit
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createFlagQuery(SearchQuery.FlagCriterion crit) throws 
UnsupportedSearchException {
+    private Query createFlagQuery(SearchQuery.FlagCriterion crit, Mailbox<?> 
mailbox) throws MailboxException, UnsupportedSearchException {
         Flag flag = crit.getFlag();
-        String value = flag.toString();
-        TermQuery query = new TermQuery(new Term(FLAGS_FIELD, value));
+        String value = toString(flag);
+        BooleanQuery query = new BooleanQuery();
+        
         if (crit.getOperator().isSet()) {   
-            return query;
+            query.add(new TermQuery(new Term(FLAGS_FIELD, value)), 
BooleanClause.Occur.MUST);
         } else {
             // lucene does not support simple NOT queries so we do some nasty 
hack here
             BooleanQuery bQuery = new BooleanQuery();
             bQuery.add(new PrefixQuery(new Term(UID_FIELD, "")), 
BooleanClause.Occur.MUST);
-            bQuery.add(query, BooleanClause.Occur.MUST_NOT);
-            return bQuery;
+            bQuery.add(new TermQuery(new Term(FLAGS_FIELD, 
value)),BooleanClause.Occur.MUST_NOT);
+            
+            query.add(bQuery, BooleanClause.Occur.MUST);
+        }
+        query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, 
mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST);
+        
+        
+        IndexSearcher searcher = null;
+
+        try {
+            List<Long> uids = new ArrayList<Long>();
+            searcher = new IndexSearcher(IndexReader.open(writer, true));
+            
+            // query for all the documents sorted by uid
+            TopDocs docs = searcher.search(query, null, maxQueryResults, 
UID_SORT);
+            ScoreDoc[] sDocs = docs.scoreDocs;
+            for (int i = 0; i < sDocs.length; i++) {
+                long uid = 
Long.valueOf(searcher.doc(sDocs[i].doc).get(UID_FIELD));
+                uids.add(uid);
+            }
+            
+            List<MessageRange> ranges = MessageRange.toRanges(uids);
+            NumericRange[] nRanges = new NumericRange[ranges.size()];
+            for (int i = 0; i < ranges.size(); i++) {
+                MessageRange range = ranges.get(i);
+                nRanges[i] = new NumericRange(range.getUidFrom(), 
range.getUidTo());
+            }
+            return createUidQuery((UidCriterion) SearchQuery.uid(nRanges));
+        } catch (IOException e) {
+            throw new MailboxException("Unable to search mailbox " + mailbox, 
e);
+        } finally {
+            if (searcher != null) {
+                try {
+                    searcher.close();
+                } catch (IOException e) {
+                    // ignore on close
+                }
+            }
+        }
+    }
+    
+    /**
+     * Convert the given {@link Flag} to a String
+     * 
+     * @param flag
+     * @return flagString
+     */
+    private String toString(Flag flag) {
+        if (Flag.ANSWERED.equals(flag)) {
+            return "\\ANSWERED";
+        } else if (Flag.DELETED.equals(flag)) {
+            return "\\DELETED";
+        } else if (Flag.DRAFT.equals(flag)) {
+            return "\\DRAFT";
+        } else if (Flag.FLAGGED.equals(flag)) {
+            return "\\FLAGGED";
+        } else if (Flag.RECENT.equals(flag)) {
+            return "\\RECENT";
+        } else if (Flag.SEEN.equals(flag)) {
+            return "\\FLAG";
+        } else {
+            return flag.toString();
         }
     }
     
@@ -461,14 +610,14 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createTextQuery(SearchQuery.TextCriterion crit) throws 
UnsupportedSearchException {
+    private Query createTextQuery(SearchQuery.TextCriterion crit) throws 
UnsupportedSearchException {
         switch(crit.getType()) {
         case BODY:
-            return new TermQuery(new Term(BODY_FIELD, 
crit.getOperator().getValue().toLowerCase(Locale.US)));
+            return new PrefixQuery(new Term(BODY_FIELD, 
crit.getOperator().getValue().toLowerCase(Locale.US)));
         case FULL: 
             BooleanQuery query = new BooleanQuery();
-            query.add(new TermQuery(new Term(BODY_FIELD, 
crit.getOperator().getValue().toLowerCase(Locale.US))), 
BooleanClause.Occur.SHOULD);
-            query.add(new TermQuery(new Term(HEADERS_FIELD, 
crit.getOperator().getValue().toLowerCase(Locale.US))), 
BooleanClause.Occur.SHOULD);
+            query.add(new PrefixQuery(new Term(BODY_FIELD, 
crit.getOperator().getValue().toLowerCase(Locale.US))), 
BooleanClause.Occur.SHOULD);
+            query.add(new PrefixQuery(new Term(HEADERS_FIELD, 
crit.getOperator().getValue().toLowerCase(Locale.US))), 
BooleanClause.Occur.SHOULD);
             return query;
         default:
             throw new UnsupportedSearchException();
@@ -482,8 +631,13 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createAllQuery(SearchQuery.AllCriterion crit) throws 
UnsupportedSearchException{
-        return NumericRangeQuery.newLongRange(UID_FIELD, Long.MIN_VALUE, 
Long.MAX_VALUE, true, true);
+    private Query createAllQuery(SearchQuery.AllCriterion crit) throws 
UnsupportedSearchException{
+        BooleanQuery query = new BooleanQuery();
+        
+        query.add(NumericRangeQuery.newLongRange(UID_FIELD, Long.MIN_VALUE, 
Long.MAX_VALUE, true, true), BooleanClause.Occur.MUST);
+        query.add(new PrefixQuery(new Term(FLAGS_FIELD, "")), 
BooleanClause.Occur.MUST_NOT);
+        
+        return query;
     }
     
     /**
@@ -493,7 +647,7 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query 
createConjunctionQuery(SearchQuery.ConjunctionCriterion crit) throws 
UnsupportedSearchException {
+    private Query createConjunctionQuery(SearchQuery.ConjunctionCriterion 
crit, Mailbox<?> mailbox) throws UnsupportedSearchException, MailboxException {
         BooleanClause.Occur occur;
         switch (crit.getType()) {
         case AND:
@@ -511,7 +665,7 @@ public class LuceneMessageSearchIndex<Id
         List<Criterion> crits = crit.getCriteria();
         BooleanQuery conQuery = new BooleanQuery();
         for (int i = 0; i < crits.size(); i++) {
-            conQuery.add(createQuery(crits.get(i)), occur);
+            conQuery.add(createQuery(crits.get(i), mailbox), occur);
         }
         return conQuery;
     }
@@ -523,7 +677,7 @@ public class LuceneMessageSearchIndex<Id
      * @return query
      * @throws UnsupportedSearchException
      */
-    public static Query createQuery(Criterion criterion) throws 
UnsupportedSearchException {
+    private Query createQuery(Criterion criterion, Mailbox<?> mailbox) throws 
UnsupportedSearchException, MailboxException {
         if (criterion instanceof SearchQuery.InternalDateCriterion) {
             SearchQuery.InternalDateCriterion crit = 
(SearchQuery.InternalDateCriterion) criterion;
             return createInternalDateQuery(crit);
@@ -538,7 +692,7 @@ public class LuceneMessageSearchIndex<Id
             return createUidQuery(crit);
         } else if (criterion instanceof SearchQuery.FlagCriterion) {
             FlagCriterion crit = (FlagCriterion) criterion;
-            return createFlagQuery(crit);
+            return createFlagQuery(crit, mailbox);
         } else if (criterion instanceof SearchQuery.TextCriterion) {
             SearchQuery.TextCriterion crit = (SearchQuery.TextCriterion) 
criterion;
             return createTextQuery(crit);
@@ -546,20 +700,27 @@ public class LuceneMessageSearchIndex<Id
             return createAllQuery((AllCriterion) criterion);
         } else if (criterion instanceof SearchQuery.ConjunctionCriterion) {
             SearchQuery.ConjunctionCriterion crit = 
(SearchQuery.ConjunctionCriterion) criterion;
-            return createConjunctionQuery(crit);
+            return createConjunctionQuery(crit, mailbox);
+        } else if (criterion instanceof SearchQuery.ModSeqCriterion) {
+            return createModSeqQuery((SearchQuery.ModSeqCriterion) criterion);
         }
         throw new UnsupportedSearchException();
 
     }
 
+    
+
     /*
      * (non-Javadoc)
      * @see 
org.apache.james.mailbox.store.MessageSearchIndex#add(org.apache.james.mailbox.MailboxSession,
 org.apache.james.mailbox.store.mail.model.Mailbox, 
org.apache.james.mailbox.store.mail.model.MailboxMembership)
      */
     public void add(MailboxSession session, Mailbox<Id> mailbox, Message<Id> 
membership) throws MailboxException {
-        Document doc = createDocument(membership);
+        Document doc = createMessageDocument(membership);
+        Document flagsDoc = createFlagsDocument(membership);
+
         try {
             writer.addDocument(doc);
+            writer.addDocument(flagsDoc);
         } catch (CorruptIndexException e) {
             throw new MailboxException("Unable to add message to index", e);
         } catch (IOException e) {
@@ -573,16 +734,18 @@ public class LuceneMessageSearchIndex<Id
      */
     public void update(MailboxSession session, Mailbox<Id> mailbox, 
MessageRange range, Flags f) throws MailboxException {
         try {
-            IndexSearcher searcher = new IndexSearcher(writer.getReader());
+            IndexSearcher searcher = new 
IndexSearcher(IndexReader.open(writer, true));
             BooleanQuery query = new BooleanQuery();
             query.add(new TermQuery(new Term(MAILBOX_ID_FIELD, 
mailbox.getMailboxId().toString())), BooleanClause.Occur.MUST);
             query.add(NumericRangeQuery.newLongRange(UID_FIELD, 
range.getUidFrom(), range.getUidTo(), true, true), BooleanClause.Occur.MUST);
+            query.add( new PrefixQuery(new Term(FLAGS_FIELD, "")), 
BooleanClause.Occur.MUST);
+
             TopDocs docs = searcher.search(query, 100000);
             ScoreDoc[] sDocs = docs.scoreDocs;
             for (int i = 0; i < sDocs.length; i++) {
                 Document doc = searcher.doc(sDocs[i].doc);
                 doc.removeFields(FLAGS_FIELD);
-                indexFlags(f, doc);
+                indexFlags(doc, f);
                 writer.updateDocument(new Term(ID_FIELD, doc.get(ID_FIELD)), 
doc);
             }
         } catch (IOException e) {
@@ -598,10 +761,26 @@ public class LuceneMessageSearchIndex<Id
      * @param f
      * @param doc
      */
-    private static void indexFlags(Flags f, Document doc) {
+    private Document createFlagsDocument(Message<?> message) {
+        Document doc = new Document();
+        doc.add(new Field(ID_FIELD, "flags-" + 
message.getMailboxId().toString() +"-" + Long.toString(message.getUid()), 
Store.YES, Index.NOT_ANALYZED));
+        doc.add(new Field(MAILBOX_ID_FIELD, message.getMailboxId().toString(), 
Store.YES, Index.NOT_ANALYZED));
+        doc.add(new NumericField(UID_FIELD,Store.YES, 
true).setLongValue(message.getUid()));
+        
+        indexFlags(doc, message.createFlags());
+        return doc;
+    }
+    
+    /**
+     * Add the given {@link Flags} to the {@link Document}
+     * 
+     * @param doc
+     * @param f
+     */
+    private void indexFlags(Document doc, Flags f) {
         Flag[] flags = f.getSystemFlags();
         for (int a = 0; a < flags.length; a++) {
-            doc.add(new Field(FLAGS_FIELD, flags[a].toString(),Store.NO, 
Index.NOT_ANALYZED));
+            doc.add(new Field(FLAGS_FIELD, toString(flags[a]),Store.NO, 
Index.NOT_ANALYZED));
         }
         
         String[] userFlags = f.getUserFlags();

Modified: 
james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndexTest.java
URL: 
http://svn.apache.org/viewvc/james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndexTest.java?rev=1129859&r1=1129858&r2=1129859&view=diff
==============================================================================
--- 
james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndexTest.java
 (original)
+++ 
james/mailbox/trunk/store/src/test/java/org/apache/james/mailbox/store/lucene/LuceneMessageSearchIndexTest.java
 Tue May 31 19:15:53 2011
@@ -22,21 +22,25 @@ import static org.junit.Assert.assertEqu
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 
 import javax.mail.Flags;
 import javax.mail.Flags.Flag;
 
 import org.apache.james.mailbox.SearchQuery;
 import org.apache.james.mailbox.SearchQuery.DateResolution;
+import org.apache.james.mailbox.store.MessageBuilder;
 import org.apache.james.mailbox.store.MessageSearchIndex;
 import org.apache.james.mailbox.store.SimpleHeader;
 import org.apache.james.mailbox.store.SimpleMailboxMembership;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
+import org.apache.james.mailbox.store.mail.model.Message;
 import org.apache.lucene.store.RAMDirectory;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,11 +51,26 @@ public class LuceneMessageSearchIndexTes
 
     private SimpleMailbox mailbox = new SimpleMailbox(0);
     private SimpleMailbox mailbox2 = new SimpleMailbox(1);
+    private SimpleMailbox mailbox3 = new SimpleMailbox(2);
 
 
+    private static final String FROM_ADDRESS = "Harry <[email protected]";
+
+    private static final String SUBJECT_PART = "Mixed";
+
+    private static final String CUSTARD = "CUSTARD";
+
+    private static final String RHUBARD = "Rhubard";
+
+    private static final String BODY = "This is a simple email\r\n "
+            + "It has " + RHUBARD + ".\r\n" + "It has " + CUSTARD + ".\r\n"
+            + "It needs naught else.\r\n";
+
+    Message<Long> row;
+
     @Before
     public void setUp() throws Exception {
-        index = new LuceneMessageSearchIndex<Long>(new RAMDirectory());
+        index = new LuceneMessageSearchIndex<Long>(new RAMDirectory(), false);
         List<org.apache.james.mailbox.store.SimpleHeader> headersSubject = new 
ArrayList<org.apache.james.mailbox.store.SimpleHeader>();
         headersSubject.add(new SimpleHeader("Subject", 1, "test"));
        
@@ -62,12 +81,14 @@ public class LuceneMessageSearchIndexTes
         headersTestSubject.add(new SimpleHeader("Test", 1, "test"));
         headersTestSubject.add(new SimpleHeader("Subject", 2, "test2"));
 
-        SimpleMailboxMembership m = new 
SimpleMailboxMembership(mailbox.getMailboxId(),1, 0, new Date(), 200, new 
Flags(Flag.ANSWERED), "My Body".getBytes(), headersSubject);
-        index.add(null, mailbox, m);
+
         
         SimpleMailboxMembership m2 = new 
SimpleMailboxMembership(mailbox2.getMailboxId(),1, 0, new Date(), 20, new 
Flags(Flag.ANSWERED), "My Body".getBytes(), headersSubject);
         index.add(null, mailbox2, m2);
 
+        SimpleMailboxMembership m = new 
SimpleMailboxMembership(mailbox.getMailboxId(),1, 0, new Date(), 200, new 
Flags(Flag.ANSWERED), "My Body".getBytes(), headersSubject);
+        index.add(null, mailbox, m);
+        
         Calendar cal = Calendar.getInstance();
         cal.set(1980, 2, 10);
         SimpleMailboxMembership m3 = new 
SimpleMailboxMembership(mailbox.getMailboxId(),2, 0, cal.getTime(), 20, new 
Flags(Flag.DELETED), "My Otherbody".getBytes(), headersTest);
@@ -77,6 +98,100 @@ public class LuceneMessageSearchIndexTes
         cal2.set(8000, 2, 10);
         SimpleMailboxMembership m4 = new 
SimpleMailboxMembership(mailbox.getMailboxId(),3, 0, cal2.getTime(), 20, new 
Flags(Flag.DELETED), "My Otherbody2".getBytes(), headersTestSubject);
         index.add(null, mailbox, m4);
+        
+        MessageBuilder builder = new MessageBuilder();
+        builder.header("From", "Alex <[email protected]");
+        builder.header("To", FROM_ADDRESS);
+        builder.header("Subject", "A " + SUBJECT_PART + " Multipart Mail");
+        builder.header("Date", "Thu, 14 Feb 2008 12:00:00 +0000 (GMT)");
+        builder.body = Charset.forName("us-ascii").encode(BODY).array();
+        builder.uid = 10;
+        builder.mailboxId = mailbox3.getMailboxId();
+        
+        index.add(null, mailbox3, builder.build());
+                
+    }
+    
+
+
+    @Test
+    public void testBodyShouldMatchPhraseInBody() throws Exception {
+        SearchQuery query = new SearchQuery();
+        query.andCriteria(SearchQuery.bodyContains(CUSTARD));
+        Iterator<Long> result = index.search(null, mailbox3, query);
+        assertEquals(10, result.next(), 1);
+        assertFalse(result.hasNext());
+        
+        
+        query = new SearchQuery();
+        query.andCriteria(SearchQuery.bodyContains(CUSTARD + CUSTARD));
+        result = index.search(null, mailbox3, query);
+        assertFalse(result.hasNext());
+    }
+
+    @Test
+    public void testBodyMatchShouldBeCaseInsensitive() throws Exception {
+        SearchQuery query = new SearchQuery();
+        query.andCriteria(SearchQuery.bodyContains(RHUBARD));
+        Iterator<Long> result = index.search(null, mailbox3, query);
+        assertEquals(10, result.next(), 1);
+        assertFalse(result.hasNext());
+    }
+
+    @Test
+    public void testBodyShouldNotMatchPhraseOnlyInHeader() throws Exception {
+        SearchQuery query = new SearchQuery();
+        query.andCriteria(SearchQuery.bodyContains(FROM_ADDRESS));
+        Iterator<Long> result = index.search(null, mailbox3, query);
+        assertFalse(result.hasNext());
+        
+        query = new SearchQuery();
+        query.andCriteria(SearchQuery.bodyContains(SUBJECT_PART));
+        result = index.search(null, mailbox3, query);
+        assertFalse(result.hasNext());
+    }
+
+    @Test
+    public void testTextShouldMatchPhraseInBody() throws Exception {
+        SearchQuery query = new SearchQuery();
+        query.andCriteria(SearchQuery.mailContains(CUSTARD));
+        Iterator<Long> result = index.search(null, mailbox3, query);
+        assertEquals(10, result.next(), 1);
+        assertFalse(result.hasNext());
+        
+        query = new SearchQuery();
+        query.andCriteria(SearchQuery.mailContains(CUSTARD + CUSTARD));
+        result = index.search(null, mailbox3, query);
+        assertFalse(result.hasNext());
+    }
+
+    @Test
+    public void testTextMatchShouldBeCaseInsensitive() throws Exception {
+        SearchQuery query = new SearchQuery();
+        query.andCriteria(SearchQuery.mailContains(RHUBARD));
+        Iterator<Long> result = index.search(null, mailbox3, query);
+        assertEquals(10, result.next(), 1);
+        assertFalse(result.hasNext());
+        
+        
query.andCriteria(SearchQuery.mailContains(RHUBARD.toLowerCase(Locale.US)));
+        result = index.search(null, mailbox3, query);
+        assertEquals(10, result.next(), 1);
+        assertFalse(result.hasNext());
+    }
+
+    @Test
+    public void testBodyShouldMatchPhraseOnlyInHeader() throws Exception {
+        
+        SearchQuery query = new SearchQuery();
+        query.andCriteria(SearchQuery.mailContains(FROM_ADDRESS));
+        Iterator<Long> result = index.search(null, mailbox3, query);
+        assertEquals(10, result.next(), 1);
+        assertFalse(result.hasNext());
+        
+        query.andCriteria(SearchQuery.mailContains(SUBJECT_PART));
+        result = index.search(null, mailbox3, query);
+        assertEquals(10, result.next(), 1);
+        assertFalse(result.hasNext());
     }
     
     @Test
@@ -107,6 +222,8 @@ public class LuceneMessageSearchIndexTes
         Iterator<Long> it4 = index.search(null, mailbox, q2);
         assertEquals(1, it4.next().longValue(), 1);
         assertEquals(2, it4.next().longValue(), 1);
+        assertEquals(3, it4.next().longValue(), 1);
+
         assertFalse(it4.hasNext());
     }
     
@@ -117,6 +234,7 @@ public class LuceneMessageSearchIndexTes
         Iterator<Long> it4 = index.search(null, mailbox, q2);
         assertEquals(1, it4.next().longValue(), 1);
         assertEquals(2, it4.next().longValue(), 1);
+        assertEquals(3, it4.next().longValue(), 1);
 
         assertFalse(it4.hasNext());
     }



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to