Author: tommaso
Date: Thu Feb 12 15:08:57 2015
New Revision: 1659285

URL: http://svn.apache.org/r1659285
Log:
OAK-2508 - added ACL filtering for spellchecks too

Modified:
    
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/LucenePropertyIndex.java
    
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/AdvancedSolrQueryIndex.java
    
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
    
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
    
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml

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=1659285&r1=1659284&r2=1659285&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
 Thu Feb 12 15:08:57 2015
@@ -81,6 +81,7 @@ import org.apache.lucene.analysis.Analyz
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
+import org.apache.lucene.document.Document;
 import org.apache.lucene.index.FieldInfo;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.MultiFields;
@@ -106,6 +107,7 @@ import org.apache.lucene.search.Wildcard
 import org.apache.lucene.search.spell.SuggestWord;
 import org.apache.lucene.search.suggest.Lookup;
 import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.Version;
 import org.apache.lucene.util.automaton.Automaton;
 import org.apache.lucene.util.automaton.CompiledAutomaton;
 import org.slf4j.Logger;
@@ -366,19 +368,47 @@ public class LuceneIndex implements Adva
                     } else if (luceneRequestFacade.getLuceneRequest() 
instanceof SpellcheckHelper.SpellcheckQuery) {
                         SpellcheckHelper.SpellcheckQuery spellcheckQuery = 
(SpellcheckHelper.SpellcheckQuery) luceneRequestFacade.getLuceneRequest();
                         SuggestWord[] suggestWords = 
SpellcheckHelper.getSpellcheck(spellcheckQuery);
+
+                        // ACL filter spellchecks
                         Collection<String> suggestedWords = new 
ArrayList<String>(suggestWords.length);
-                        for (SuggestWord suggestWord : suggestWords) {
-                            suggestedWords.add(suggestWord.string);
+                        QueryParser qp = new QueryParser(Version.LUCENE_47, 
FieldNames.FULLTEXT, indexNode.getDefinition().getAnalyzer());
+                        for (SuggestWord suggestion : suggestWords) {
+                            Query query = 
qp.createPhraseQuery(FieldNames.FULLTEXT, suggestion.string);
+                            TopDocs topDocs = searcher.search(query, 100);
+                            if (topDocs.totalHits > 0) {
+                                for (ScoreDoc doc : topDocs.scoreDocs) {
+                                    Document retrievedDoc = 
searcher.doc(doc.doc);
+                                    if 
(filter.isAccessible(retrievedDoc.get(FieldNames.PATH))) {
+                                        suggestedWords.add(suggestion.string);
+                                        break;
+                                    }
+                                }
+                            }
                         }
+
                         queue.add(new LuceneResultRow(suggestedWords));
                         noDocs = true;
                     } else if (luceneRequestFacade.getLuceneRequest() 
instanceof SuggestHelper.SuggestQuery) {
                         SuggestHelper.SuggestQuery suggestQuery = 
(SuggestHelper.SuggestQuery) luceneRequestFacade.getLuceneRequest();
                         List<Lookup.LookupResult> lookupResults = 
SuggestHelper.getSuggestions(suggestQuery);
+
+                        // ACL filter suggestions
                         Collection<String> suggestedWords = new 
ArrayList<String>(lookupResults.size());
-                        for (Lookup.LookupResult suggestWord : lookupResults) {
-                            suggestedWords.add("{term=" + suggestWord.key + 
",weight=" + suggestWord.value + "}");
+                        QueryParser qp = new QueryParser(Version.LUCENE_47, 
FieldNames.FULLTEXT, indexNode.getDefinition().getAnalyzer());
+                        for (Lookup.LookupResult suggestion : lookupResults) {
+                            Query query = 
qp.createPhraseQuery(FieldNames.FULLTEXT, suggestion.key.toString());
+                            TopDocs topDocs = searcher.search(query, 100);
+                            if (topDocs.totalHits > 0) {
+                                for (ScoreDoc doc : topDocs.scoreDocs) {
+                                    Document retrievedDoc = 
searcher.doc(doc.doc);
+                                    if 
(filter.isAccessible(retrievedDoc.get(FieldNames.PATH))) {
+                                        suggestedWords.add("{term=" + 
suggestion.key + ",weight=" + suggestion.value + "}");
+                                        break;
+                                    }
+                                }
+                            }
                         }
+
                         queue.add(new LuceneResultRow(suggestedWords));
                         noDocs = true;
                     }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java?rev=1659285&r1=1659284&r2=1659285&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 Thu Feb 12 15:08:57 2015
@@ -340,10 +340,24 @@ public class LucenePropertyIndex impleme
                     } else if (luceneRequestFacade.getLuceneRequest() 
instanceof SpellcheckHelper.SpellcheckQuery) {
                         SpellcheckHelper.SpellcheckQuery spellcheckQuery = 
(SpellcheckHelper.SpellcheckQuery) luceneRequestFacade.getLuceneRequest();
                         SuggestWord[] suggestWords = 
SpellcheckHelper.getSpellcheck(spellcheckQuery);
+
+                        // ACL filter spellchecks
                         Collection<String> suggestedWords = new 
ArrayList<String>(suggestWords.length);
-                        for (SuggestWord suggestWord : suggestWords) {
-                            suggestedWords.add(suggestWord.string);
+                        QueryParser qp = new QueryParser(Version.LUCENE_47, 
FieldNames.FULLTEXT, indexNode.getDefinition().getAnalyzer());
+                        for (SuggestWord suggestion : suggestWords) {
+                            Query query = 
qp.createPhraseQuery(FieldNames.FULLTEXT, suggestion.string);
+                            TopDocs topDocs = searcher.search(query, 100);
+                            if (topDocs.totalHits > 0) {
+                                for (ScoreDoc doc : topDocs.scoreDocs) {
+                                    Document retrievedDoc = 
searcher.doc(doc.doc);
+                                    if 
(filter.isAccessible(retrievedDoc.get(FieldNames.PATH))) {
+                                        suggestedWords.add(suggestion.string);
+                                        break;
+                                    }
+                                }
+                            }
                         }
+
                         queue.add(new LuceneResultRow(suggestedWords));
                         noDocs = true;
                     } else if (luceneRequestFacade.getLuceneRequest() 
instanceof SuggestHelper.SuggestQuery) {

Modified: 
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/AdvancedSolrQueryIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/AdvancedSolrQueryIndex.java?rev=1659285&r1=1659284&r2=1659285&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/AdvancedSolrQueryIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/AdvancedSolrQueryIndex.java
 Thu Feb 12 15:08:57 2015
@@ -40,17 +40,13 @@ public class AdvancedSolrQueryIndex exte
 
     private static final Map<String, Long> cache = new WeakHashMap<String, 
Long>();
 
-    private final OakSolrConfiguration configuration;
     private final SolrServer solrServer;
-    private final NodeAggregator aggregator;
     private final String name;
 
     public AdvancedSolrQueryIndex(String name, SolrServer solrServer, 
OakSolrConfiguration configuration, NodeAggregator aggregator) {
         super(name, solrServer, configuration, aggregator);
         this.name = name;
-        this.configuration = configuration;
         this.solrServer = solrServer;
-        this.aggregator = aggregator;
     }
 
     @Override
@@ -117,11 +113,6 @@ public class AdvancedSolrQueryIndex exte
     }
 
     @Override
-    public NodeAggregator getNodeAggregator() {
-        return aggregator;
-    }
-
-    @Override
     public double getCost(Filter filter, NodeState rootState) {
         return super.getCost(filter, rootState);
     }

Modified: 
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java?rev=1659285&r1=1659284&r2=1659285&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java
 Thu Feb 12 15:08:57 2015
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
@@ -31,7 +32,6 @@ import com.google.common.collect.Iterabl
 import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
 import org.apache.jackrabbit.oak.api.PropertyValue;
-import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
 import 
org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfiguration;
 import org.apache.jackrabbit.oak.query.QueryEngineSettings;
@@ -196,139 +196,170 @@ public class SolrQueryIndex implements F
 
             final int parentDepth = getDepth(parent);
 
-            cursor = new SolrRowCursor(new AbstractIterator<SolrResultRow>() {
-                private final Set<String> seenPaths = Sets.newHashSet();
-                private final Deque<SolrResultRow> queue = 
Queues.newArrayDeque();
-                private SolrDocument lastDoc;
-                private int offset = 0;
-                private boolean noDocs = false;
-
-                @Override
-                protected SolrResultRow computeNext() {
-                    if (!queue.isEmpty() || loadDocs()) {
-                        return queue.remove();
-                    }
-                    return endOfData();
+            cursor = new SolrRowCursor(getIterator(filter, parent, 
parentDepth), filter.getQueryEngineSettings());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return cursor;
+    }
+
+    private AbstractIterator<SolrResultRow> getIterator(final Filter filter, 
final String parent, final int parentDepth) {
+        return new AbstractIterator<SolrResultRow>() {
+            private final Set<String> seenPaths = Sets.newHashSet();
+            private final Deque<SolrResultRow> queue = Queues.newArrayDeque();
+            private SolrDocument lastDoc;
+            private int offset = 0;
+            private boolean noDocs = false;
+
+            @Override
+            protected SolrResultRow computeNext() {
+                if (!queue.isEmpty() || loadDocs()) {
+                    return queue.remove();
                 }
+                return endOfData();
+            }
 
-                private SolrResultRow convertToRow(SolrDocument doc) {
-                    String path = 
String.valueOf(doc.getFieldValue(configuration.getPathField()));
-                    if (path != null) {
-                        if ("".equals(path)) {
-                            path = "/";
-                        }
-                        if (!parent.isEmpty()) {
-                            path = getAncestorPath(path, parentDepth);
-                            // avoid duplicate entries
-                            if (seenPaths.contains(path)) {
-                                return null;
-                            }
-                            seenPaths.add(path);
+            private SolrResultRow convertToRow(SolrDocument doc) {
+                String path = 
String.valueOf(doc.getFieldValue(configuration.getPathField()));
+                if (path != null) {
+                    if ("".equals(path)) {
+                        path = "/";
+                    }
+                    if (!parent.isEmpty()) {
+                        path = getAncestorPath(path, parentDepth);
+                        // avoid duplicate entries
+                        if (seenPaths.contains(path)) {
+                            return null;
                         }
+                        seenPaths.add(path);
+                    }
 
-                        float score = 0f;
-                        Object scoreObj = doc.get("score");
-                        if (scoreObj != null) {
-                            score = (Float) scoreObj;
-                        }
-                        return new SolrResultRow(path, score, doc);
+                    float score = 0f;
+                    Object scoreObj = doc.get("score");
+                    if (scoreObj != null) {
+                        score = (Float) scoreObj;
                     }
-                    return null;
+                    return new SolrResultRow(path, score, doc);
                 }
+                return null;
+            }
 
-                /**
-                 * Loads the Solr documents in batches
-                 * @return true if any document is loaded
-                 */
-                private boolean loadDocs() {
+            /**
+             * Loads the Solr documents in batches
+             * @return true if any document is loaded
+             */
+            private boolean loadDocs() {
 
-                    if (noDocs) {
-                        return false;
-                    }
+                if (noDocs) {
+                    return false;
+                }
 
-                    SolrDocument lastDocToRecord = null;
+                SolrDocument lastDocToRecord = null;
 
-                    try {
-                        if (log.isDebugEnabled()) {
-                            log.debug("converting filter {}", filter);
-                        }
-                        SolrQuery query = FilterQueryParser.getQuery(filter, 
configuration);
-                        if (lastDoc != null) {
-                            offset++;
-                            int newOffset = offset * configuration.getRows();
-                            query.setParam("start", String.valueOf(newOffset));
-                        }
-                        if (log.isDebugEnabled()) {
-                            log.debug("sending query {}", query);
-                        }
-                        QueryResponse queryResponse = solrServer.query(query);
+                try {
+                    if (log.isDebugEnabled()) {
+                        log.debug("converting filter {}", filter);
+                    }
+                    SolrQuery query = FilterQueryParser.getQuery(filter, 
configuration);
+                    if (lastDoc != null) {
+                        offset++;
+                        int newOffset = offset * configuration.getRows();
+                        query.setParam("start", String.valueOf(newOffset));
+                    }
+                    if (log.isDebugEnabled()) {
+                        log.debug("sending query {}", query);
+                    }
+                    QueryResponse queryResponse = solrServer.query(query);
 
-                        SolrDocumentList docs = queryResponse.getResults();
+                    if (log.isDebugEnabled()) {
+                        log.debug("getting response {}", 
queryResponse.getHeader());
+                    }
 
-                        if (docs != null) {
-                            onRetrievedDocs(filter, docs);
+                    SolrDocumentList docs = queryResponse.getResults();
 
-                            if (log.isDebugEnabled()) {
-                                log.debug("getting docs {}", docs);
-                            }
+                    if (docs != null) {
+                        onRetrievedDocs(filter, docs);
 
-                            for (SolrDocument doc : docs) {
-                                SolrResultRow row = convertToRow(doc);
-                                if (row != null) {
-                                    queue.add(row);
-                                }
-                                lastDocToRecord = doc;
+                        for (SolrDocument doc : docs) {
+                            SolrResultRow row = convertToRow(doc);
+                            if (row != null) {
+                                queue.add(row);
                             }
+                            lastDocToRecord = doc;
                         }
+                    }
 
-                        // handle spellcheck
-                        SpellCheckResponse spellCheckResponse = 
queryResponse.getSpellCheckResponse();
-                        if (spellCheckResponse != null && 
spellCheckResponse.getSuggestions() != null &&
-                                spellCheckResponse.getSuggestions().size() > 
0) {
-                            SolrDocument fakeDoc = new SolrDocument();
-                            for (SpellCheckResponse.Suggestion suggestion : 
spellCheckResponse.getSuggestions()) {
-                                fakeDoc.addField(QueryImpl.REP_SPELLCHECK, 
suggestion.getAlternatives());
-                            }
+                    // handle spellcheck
+                    SpellCheckResponse spellCheckResponse = 
queryResponse.getSpellCheckResponse();
+                    if (spellCheckResponse != null && 
spellCheckResponse.getSuggestions() != null &&
+                            spellCheckResponse.getSuggestions().size() > 0) {
+                        SolrDocument fakeDoc = 
getSpellChecks(spellCheckResponse, filter);
+                        queue.add(new SolrResultRow("/", 1.0, fakeDoc));
+                        noDocs = true;
+                    }
+
+                    // handle suggest
+                    NamedList<Object> response = queryResponse.getResponse();
+                    Map suggest = (Map) response.get("suggest");
+                    if (suggest != null) {
+                        Set<Map.Entry<String, Object>> suggestEntries = 
suggest.entrySet();
+                        if (!suggestEntries.isEmpty()) {
+                            SolrDocument fakeDoc = 
getSuggestions(suggestEntries, filter);
                             queue.add(new SolrResultRow("/", 1.0, fakeDoc));
                             noDocs = true;
                         }
-
-                        // handle suggest
-                        NamedList<Object> response = 
queryResponse.getResponse();
-                        Map suggest = (Map) response.get("suggest");
-                        if (suggest != null) {
-                            Set<Map.Entry<String, Object>> suggestEntries = 
suggest.entrySet();
-                            if (!suggestEntries.isEmpty()) {
-                                SolrDocument fakeDoc = 
getSuggestions(suggestEntries, filter);
-                                queue.add(new SolrResultRow("/", 1.0, 
fakeDoc));
-                                noDocs = true;
-                            }
-                        }
-
-                    } catch (Exception e) {
-                        if (log.isWarnEnabled()) {
-                            log.warn("query via {} failed.", solrServer, e);
-                        }
-                    }
-                    if (lastDocToRecord != null) {
-                        this.lastDoc = lastDocToRecord;
                     }
 
-                    return !queue.isEmpty();
+                } catch (Exception e) {
+                    if (log.isWarnEnabled()) {
+                        log.warn("query via {} failed.", solrServer, e);
+                    }
+                }
+                if (lastDocToRecord != null) {
+                    this.lastDoc = lastDocToRecord;
                 }
 
-            }, filter.getQueryEngineSettings());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
+                return !queue.isEmpty();
+            }
+
+        };
+    }
+
+    private SolrDocument getSpellChecks(SpellCheckResponse spellCheckResponse, 
Filter filter) throws SolrServerException {
+        SolrDocument fakeDoc = new SolrDocument();
+        List<SpellCheckResponse.Suggestion> suggestions = 
spellCheckResponse.getSuggestions();
+        Collection<String> alternatives = new 
ArrayList<String>(suggestions.size());
+        for (SpellCheckResponse.Suggestion suggestion : suggestions) {
+            alternatives.addAll(suggestion.getAlternatives());
         }
-        return cursor;
+
+        // ACL filter spellcheck results
+        for (String alternative : alternatives) {
+            SolrQuery solrQuery = new SolrQuery();
+            solrQuery.setParam("q", alternative);
+            solrQuery.setParam("df", configuration.getCatchAllField());
+            solrQuery.setParam("q.op", "AND");
+            solrQuery.setParam("rows", "100");
+            QueryResponse suggestQueryResponse = solrServer.query(solrQuery);
+            SolrDocumentList results = suggestQueryResponse.getResults();
+            if (results != null && results.getNumFound() > 0) {
+                for (SolrDocument doc : results) {
+                    if 
(filter.isAccessible(String.valueOf(doc.getFieldValue(configuration.getPathField()))))
 {
+                        fakeDoc.addField(QueryImpl.REP_SPELLCHECK, 
alternative);
+                        break;
+                    }
+                }
+            }
+        }
+
+        return fakeDoc;
     }
 
     private SolrDocument getSuggestions(Set<Map.Entry<String, Object>> 
suggestEntries, Filter filter) throws SolrServerException {
         Collection<SimpleOrderedMap<Object>> retrievedSuggestions = new 
HashSet<SimpleOrderedMap<Object>>();
         SolrDocument fakeDoc = new SolrDocument();
-        for (Map.Entry<String, Object> suggestor : suggestEntries) {
-            SimpleOrderedMap<Object> suggestionResponses = ((SimpleOrderedMap) 
suggestor.getValue());
+        for (Map.Entry<String, Object> suggester : suggestEntries) {
+            SimpleOrderedMap<Object> suggestionResponses = ((SimpleOrderedMap) 
suggester.getValue());
             for (Map.Entry<String, Object> suggestionResponse : 
suggestionResponses) {
                 SimpleOrderedMap<Object> suggestionResults = 
((SimpleOrderedMap) suggestionResponse.getValue());
                 for (Map.Entry<String, Object> suggestionResult : 
suggestionResults) {

Modified: 
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml?rev=1659285&r1=1659284&r2=1659285&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
 (original)
+++ 
jackrabbit/oak/trunk/oak-solr-core/src/main/resources/solr/oak/conf/solrconfig.xml
 Thu Feb 12 15:08:57 2015
@@ -852,9 +852,6 @@
            </arr>
           -->
         <arr name="last-components">
-            <str>mlt</str>
-            <str>spellcheck</str>
-            <str>suggest</str>
         </arr>
     </requestHandler>
 

Modified: 
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml?rev=1659285&r1=1659284&r2=1659285&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml
 (original)
+++ 
jackrabbit/oak/trunk/oak-solr-core/src/test/resources/solr/oak/conf/solrconfig.xml
 Thu Feb 12 15:08:57 2015
@@ -808,9 +808,6 @@ Lucene will flush based on whichever lim
          </arr>
         -->
         <arr name="last-components">
-            <str>mlt</str>
-            <str>spellcheck</str>
-            <str>suggest</str>
         </arr>
     </requestHandler>
 


Reply via email to