You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by al...@apache.org on 2013/05/14 22:30:52 UTC

svn commit: r1482567 - in /jackrabbit/oak/trunk: oak-core/src/test/java/org/apache/jackrabbit/oak/query/ oak-lucene/ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/ind...

Author: alexparvulescu
Date: Tue May 14 20:30:51 2013
New Revision: 1482567

URL: http://svn.apache.org/r1482567
Log:
OAK-819 Lucene fulltext on node not working properly - WIP

Modified:
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
    jackrabbit/oak/trunk/oak-lucene/pom.xml
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexUpdate.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/TermFactory.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneInitializerHelper.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java Tue May 14 20:30:51 2013
@@ -205,12 +205,16 @@ public abstract class AbstractQueryTest 
     }
 
     protected List<String> executeQuery(String query, String language) {
+        return executeQuery(query, language, false);
+    }
+
+    protected List<String> executeQuery(String query, String language, boolean pathsOnly) {
         long time = System.currentTimeMillis();
         List<String> lines = new ArrayList<String>();
         try {
             Result result = executeQuery(query, language, null);
             for (ResultRow row : result.getRows()) {
-                lines.add(readRow(row));
+                lines.add(readRow(row, pathsOnly));
             }
             if (!query.contains("order by")) {
                 Collections.sort(lines);
@@ -228,8 +232,14 @@ public abstract class AbstractQueryTest 
     }
 
     protected List<String> assertQuery(String sql, List<String> expected) {
-        List<String> paths = executeQuery(sql, SQL2);
-        assertEquals(expected.size(), paths.size());
+        return assertQuery(sql, SQL2, expected);
+    }
+
+    protected List<String> assertQuery(String sql, String language,
+            List<String> expected) {
+        List<String> paths = executeQuery(sql, language, true);
+        assertEquals("Result set size is different", expected.size(),
+                paths.size());
         for (String p : expected) {
             assertTrue(paths.contains(p));
         }
@@ -240,7 +250,10 @@ public abstract class AbstractQueryTest 
         ((QueryEngineImpl) qe).setTravesalFallback(traversal);
     }
 
-    protected static String readRow(ResultRow row) {
+    protected static String readRow(ResultRow row, boolean pathOnly) {
+        if (pathOnly) {
+            return row.getValue(Query.JCR_PATH).getValue(Type.STRING);
+        }
         StringBuilder buff = new StringBuilder();
         PropertyValue[] values = row.getValues();
         for (int i = 0; i < values.length; i++) {

Modified: jackrabbit/oak/trunk/oak-lucene/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/pom.xml?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-lucene/pom.xml Tue May 14 20:30:51 2013
@@ -39,7 +39,6 @@
       org.apache.jackrabbit.core.query.FulltextQueryTest <!-- OAK-819 -->
       org.apache.jackrabbit.core.query.JoinTest#testJoinWithOR4 <!-- OAK-819 -->
       org.apache.jackrabbit.core.query.JoinTest#testJoinWithOR5 <!-- OAK-819 -->
-      org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexQueryTest <!-- OAK-819 -->
     </known.issues>
   </properties>
 

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java Tue May 14 20:30:51 2013
@@ -18,8 +18,10 @@ package org.apache.jackrabbit.oak.plugin
 
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
 
 import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldNames.PATH;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldNames.FULLTEXT;
 import static org.apache.lucene.document.Field.Store.NO;
 import static org.apache.lucene.document.Field.Store.YES;
 
@@ -44,4 +46,9 @@ public final class FieldFactory {
         // return new TextField(name, value, NO);
         return new StringField(name, value, NO);
     }
+
+    public static Field newFulltextField(String value) {
+        return new TextField(FULLTEXT, value, NO);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java Tue May 14 20:30:51 2013
@@ -38,6 +38,11 @@ public final class FieldNames {
     public static final String PATH = ":path";
 
     /**
+     * Name of the field that contains the fulltext index.
+     */
+    public static final String FULLTEXT = ":fulltext";
+
+    /**
      * Used to select only the PATH field from the lucene documents
      */
     public static final Set<String> PATH_SELECTOR = new HashSet<String>(

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java Tue May 14 20:30:51 2013
@@ -25,8 +25,10 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldNames.PATH_SELECTOR;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INDEX_DATA_CHILD_NAME;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newFulltextTerm;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newPathTerm;
 import static org.apache.jackrabbit.oak.query.Query.JCR_PATH;
+import static org.apache.lucene.search.BooleanClause.Occur.MUST;
 import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
 
 import java.io.IOException;
@@ -50,10 +52,10 @@ import org.apache.lucene.index.Directory
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.MultiFields;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.search.BooleanClause.Occur;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.MultiPhraseQuery;
 import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
@@ -69,17 +71,20 @@ import org.slf4j.LoggerFactory;
  * Provides a QueryIndex that does lookups against a Lucene-based index
  * 
  * <p>
- * To define a lucene index on a subtree you have to add an <code>oak:index<code> node.
+ * To define a lucene index on a subtree you have to add an
+ * <code>oak:index<code> node.
  * 
  * Under it follows the index definition node that:
  * <ul>
  * <li>must be of type <code>oak:queryIndexDefinition</code></li>
- * <li>must have the <code>type</code> property set to <b><code>lucene</code></b></li>
+ * <li>must have the <code>type</code> property set to <b><code>lucene</code>
+ * </b></li>
  * </ul>
  * </p>
  * 
  * <p>
- * Note: <code>reindex<code> is a property that when set to <code>true</code>, triggers a full content reindex.
+ * Note: <code>reindex<code> is a property that when set to <code>true</code>,
+ * triggers a full content reindex.
  * </p>
  * 
  * <pre>
@@ -191,7 +196,8 @@ public class LuceneIndex implements Full
         }
     }
 
-    private static Query getQuery(Filter filter, NodeState root, IndexReader reader) {
+    private static Query getQuery(Filter filter, NodeState root,
+            IndexReader reader) {
         List<Query> qs = new ArrayList<Query>();
 
         if (!filter.matchesAllTypes()) {
@@ -293,9 +299,10 @@ public class LuceneIndex implements Full
 
             qs.add(TermRangeQuery.newStringRange(name, first, last,
                     pr.firstIncluding, pr.lastIncluding));
-
         }
 
+        addFulltextConstraints(qs, filter);
+
         if (qs.size() == 0) {
             return new MatchAllDocsQuery();
         }
@@ -304,7 +311,7 @@ public class LuceneIndex implements Full
         }
         BooleanQuery bq = new BooleanQuery();
         for (Query q : qs) {
-            bq.add(q, Occur.MUST);
+            bq.add(q, MUST);
         }
         return bq;
     }
@@ -321,7 +328,7 @@ public class LuceneIndex implements Full
         BooleanQuery bq = new BooleanQuery();
         Collection<String> fields = MultiFields.getIndexedFields(reader);
         for (String f : fields) {
-            bq.add(new TermQuery(new Term(f, uuid)), Occur.SHOULD);
+            bq.add(new TermQuery(new Term(f, uuid)), SHOULD);
         }
         qs.add(bq);
     }
@@ -337,4 +344,94 @@ public class LuceneIndex implements Full
         qs.add(bq);
     }
 
+    private static void addFulltextConstraints(List<Query> qs, Filter filter) {
+        if (filter.getFulltextConditions() == null
+                || filter.getFulltextConditions().isEmpty()) {
+            return;
+        }
+        List<String> tokens = tokenize(filter.getFulltextConditions()
+                .iterator().next().toLowerCase());
+        if (tokens.size() == 1) {
+            String token = tokens.get(0);
+            Query q = null;
+            if (token.contains(" ")) {
+                // q = new WildcardQuery(newFulltextTerm(token));
+                // PhraseQuery pq = new PhraseQuery();
+                // pq.add(newFulltextTerm(token));
+                // q = pq;
+            } else {
+                q = new WildcardQuery(newFulltextTerm(token
+                        + WildcardQuery.WILDCARD_STRING));
+            }
+            if (q != null) {
+                qs.add(q);
+            }
+            return;
+        }
+
+        BooleanQuery q = new BooleanQuery();
+        for (String token : tokens) {
+            q.add(new TermQuery(newFulltextTerm(token)), MUST);
+            // if (token.contains(" ")) {
+            // // q = new WildcardQuery(newFulltextTerm(token));
+            // // PhraseQuery pq = new PhraseQuery();
+            // // pq.add(newFulltextTerm(token));
+            // // q = pq;
+            // } else {
+            // q = new WildcardQuery(newFulltextTerm(token
+            // + WildcardQuery.WILDCARD_STRING));
+            // }
+        }
+        qs.add(q);
+    }
+
+    /**
+     * 
+     * inspired from lucene's WildcardQuery#toAutomaton
+     */
+    private static List<String> tokenize(String in) {
+        List<String> out = new ArrayList<String>();
+        StringBuilder token = new StringBuilder();
+        boolean quote = false;
+        for (int i = 0; i < in.length();) {
+            final int c = in.codePointAt(i);
+            int length = Character.charCount(c);
+            switch (c) {
+            case ' ':
+                if (quote) {
+                    token.append(' ');
+                } else if (token.length() > 0) {
+                    out.add(token.toString());
+                    token = new StringBuilder();
+                }
+                break;
+            case '"':
+                if (quote) {
+                    quote = false;
+                    if (token.length() > 0) {
+                        out.add(token.toString());
+                        token = new StringBuilder();
+                    }
+                } else {
+                    quote = true;
+                }
+                break;
+            case '\\':
+                if (i + length < in.length()) {
+                    final int nextChar = in.codePointAt(i + length);
+                    length += Character.charCount(nextChar);
+                    token.append(new String(Character.toChars(nextChar)));
+                    break;
+                }
+            default:
+                token.append(new String(Character.toChars(c)));
+            }
+            i += length;
+        }
+        if (token.length() > 0) {
+            out.add(token.toString());
+        }
+        return out;
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java Tue May 14 20:30:51 2013
@@ -26,7 +26,7 @@ public interface LuceneIndexConstants {
 
     String INDEX_DATA_CHILD_NAME = ":data";
 
-    Version VERSION = Version.LUCENE_41;
+    Version VERSION = Version.LUCENE_42;
 
     Analyzer ANALYZER = new StandardAnalyzer(VERSION);
 

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexUpdate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexUpdate.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexUpdate.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexUpdate.java Tue May 14 20:30:51 2013
@@ -20,6 +20,11 @@ import static com.google.common.base.Pre
 import static org.apache.jackrabbit.oak.plugins.index.IndexUtils.getString;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldFactory.newPathField;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldFactory.newPropertyField;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldFactory.newFulltextField;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.ANALYZER;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INCLUDE_PROPERTY_TYPES;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INDEX_DATA_CHILD_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newPathTerm;
 
 import java.io.Closeable;
@@ -49,7 +54,7 @@ import org.apache.tika.sax.WriteOutConte
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class LuceneIndexUpdate implements Closeable, LuceneIndexConstants {
+class LuceneIndexUpdate implements Closeable {
 
     private static final Logger log = LoggerFactory
             .getLogger(LuceneIndexUpdate.class);
@@ -187,14 +192,15 @@ class LuceneIndexUpdate implements Close
         Document document = new Document();
         document.add(newPathField(path));
         for (PropertyState property : state.getProperties()) {
-            if (propertyTypes.isEmpty()
+            String pname = property.getName();
+            if (isVisible(pname) && propertyTypes.isEmpty()
                     || propertyTypes.contains(property.getType().tag())) {
                 if (Type.BINARY.tag() == property.getType().tag()) {
                     addBinaryValue(document, property, state);
                 } else {
-                    String pname = property.getName();
                     for (String v : property.getValue(Type.STRINGS)) {
                         document.add(newPropertyField(pname, v));
+                        document.add(newFulltextField(v));
                     }
                 }
             }
@@ -202,6 +208,10 @@ class LuceneIndexUpdate implements Close
         return document;
     }
 
+    private static boolean isVisible(String name) {
+        return name.charAt(0) != ':';
+    }
+
     private void addBinaryValue(Document doc, PropertyState property,
             NodeState state) {
         String type = getString(state, JcrConstants.JCR_MIMETYPE);
@@ -216,9 +226,8 @@ class LuceneIndexUpdate implements Close
             metadata.set(Metadata.CONTENT_ENCODING, encoding);
         }
 
-        String name = property.getName();
         for (Blob v : property.getValue(Type.BINARIES)) {
-            doc.add(newPropertyField(name, parseStringValue(v, metadata)));
+            doc.add(newFulltextField(parseStringValue(v, metadata)));
         }
     }
 

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/TermFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/TermFactory.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/TermFactory.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/TermFactory.java Tue May 14 20:30:51 2013
@@ -45,4 +45,8 @@ public final class TermFactory {
         return new Term(FieldNames.PATH, path);
     }
 
+    public static Term newFulltextTerm(String ft) {
+        return new Term(FieldNames.FULLTEXT, ft);
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneInitializerHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneInitializerHelper.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneInitializerHelper.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/LuceneInitializerHelper.java Tue May 14 20:30:51 2013
@@ -31,6 +31,10 @@ public class LuceneInitializerHelper imp
 
     private final Set<String> propertyTypes;
 
+    public LuceneInitializerHelper(String name) {
+        this(name, LuceneIndexHelper.JR_PROPERTY_INCLUDES);
+    }
+
     public LuceneInitializerHelper(String name, Set<String> propertyTypes) {
         this.name = name;
         this.propertyTypes = propertyTypes;

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java?rev=1482567&r1=1482566&r2=1482567&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java Tue May 14 20:30:51 2013
@@ -21,7 +21,6 @@ import static org.junit.Assert.assertFal
 import static org.junit.Assert.assertTrue;
 
 import java.util.Iterator;
-import java.util.List;
 
 import org.apache.jackrabbit.oak.Oak;
 import org.apache.jackrabbit.oak.api.ContentRepository;
@@ -32,6 +31,8 @@ import org.apache.jackrabbit.oak.query.J
 import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableList;
+
 /**
  * Tests the query engine using the default index implementation: the
  * {@link LuceneIndexProvider}
@@ -113,19 +114,29 @@ public class LuceneIndexQueryTest extend
         assertEquals("/, /parents", result.next());
         assertFalse(result.hasNext());
     }
-    
+
     @Test
     public void contains() throws Exception {
+        String h = "Hello" + System.currentTimeMillis();
+        String w = "World" + System.currentTimeMillis();
+
+        JsopUtil.apply(root, "/ + \"test\": { \"a\": { \"name\": [\"" + h
+                + "\", \"" + w + "\" ] }, \"b\": { \"name\" : \"" + h + "\" }}");
+        root.commit();
+
+        // query 'hello'
         StringBuffer stmt = new StringBuffer();
-        stmt.append("/jcr:root").append("/test").append("/*");
-        stmt.append("[jcr:contains(., '").append("token");
+        stmt.append("/jcr:root//*[jcr:contains(., '").append(h);
+        stmt.append("')]");
+        assertQuery(stmt.toString(), "xpath",
+                ImmutableList.of("/test/a", "/test/b"));
+
+        // query 'world'
+        stmt = new StringBuffer();
+        stmt.append("/jcr:root//*[jcr:contains(., '").append(w);
         stmt.append("')]");
-        System.out.println(stmt.toString());
+        assertQuery(stmt.toString(), "xpath", ImmutableList.of("/test/a"));
 
-        List<String> result = executeQuery(stmt.toString(), "xpath");
-        System.out.println(result);
-        for (String h : result) {
-            System.out.println(h);
-        }
     }
+
 }