diff --git a/search-services/alfresco-search/src/main/java/org/alfresco/solr/query/AlfrescoDefaultTextFields.java b/search-services/alfresco-search/src/main/java/org/alfresco/solr/query/AlfrescoDefaultTextFields.java new file mode 100644 index 000000000..5cb176bf7 --- /dev/null +++ b/search-services/alfresco-search/src/main/java/org/alfresco/solr/query/AlfrescoDefaultTextFields.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2019 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.solr.query; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.namespace.QName; + + +/** + * @author elia + * + * Default fields for a text search. + */ +public enum AlfrescoDefaultTextFields +{ + + NAME(ContentModel.PROP_NAME), + TITLE(ContentModel.PROP_TITLE), + DESCRIPTION(ContentModel.PROP_DESCRIPTION), + CONTENT(ContentModel.PROP_CONTENT); + + private QName field; + + public String getFieldName() + { + return field.toString(); + } + + AlfrescoDefaultTextFields(QName field) { + this.field = field; + } + +} diff --git a/search-services/alfresco-search/src/main/java/org/alfresco/solr/query/Solr4QueryParser.java b/search-services/alfresco-search/src/main/java/org/alfresco/solr/query/Solr4QueryParser.java index bd38a4c5b..5b138c593 100644 --- a/search-services/alfresco-search/src/main/java/org/alfresco/solr/query/Solr4QueryParser.java +++ b/search-services/alfresco-search/src/main/java/org/alfresco/solr/query/Solr4QueryParser.java @@ -38,7 +38,8 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; - +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.alfresco.model.ContentModel; import org.alfresco.repo.dictionary.IndexTokenisationMode; import org.alfresco.repo.search.MLAnalysisMode; @@ -72,6 +73,7 @@ import org.alfresco.solr.AlfrescoSolrDataModel.IndexedField; import org.alfresco.solr.SolrInformationServer; import org.alfresco.solr.component.FingerPrintComponent; import org.alfresco.solr.content.SolrContentStore; +import org.alfresco.solr.utils.ThrowingFunction; import org.alfresco.util.CachingDateFormat; import org.alfresco.util.Pair; import org.alfresco.util.SearchLanguageConversion; @@ -405,22 +407,7 @@ public class Solr4QueryParser extends QueryParser implements QueryConstants throw new UnsupportedOperationException("Span is not supported for " + FIELD_PATHWITHREPEATS); } else if (field.equals(FIELD_TEXT)) { - Set text = searchParameters.getTextAttributes(); - if ((text == null) || (text.size() == 0)) - { - Query query = getSpanQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), first, last, - slop, inOrder); - return query; - } else - { - BooleanQuery.Builder query = new BooleanQuery.Builder(); - for (String fieldName : text) - { - Query part = getSpanQuery(fieldName, first, last, slop, inOrder); - query.add(part, Occur.SHOULD); - } - return query.build(); - } + return createDefaultTextQuery(textField -> getSpanQuery(textField, first, last, slop, inOrder)); } else if (field.equals(FIELD_CLASS)) { throw new UnsupportedOperationException("Span is not supported for " + FIELD_CLASS); @@ -1485,35 +1472,46 @@ public class Solr4QueryParser extends QueryParser implements QueryConstants return createTermQuery(field, queryText); } + + + /** + * + * Get generic text query + * + * + */ + protected Query createDefaultTextQuery(ThrowingFunction getQuery) throws ParseException + { + + Set text = searchParameters.getTextAttributes(); + if (text == null || text.isEmpty()) + { + text = Stream.of(AlfrescoDefaultTextFields.values()) + .map(t -> PROPERTY_FIELD_PREFIX + t.getFieldName()) + .collect(Collectors.toSet()); + } + + BooleanQuery.Builder query = new BooleanQuery.Builder(); + for (String fieldName : text) + { + Query part = getQuery.apply(fieldName); + if (part != null) + { + query.add(part, Occur.SHOULD); + } else + { + query.add(createNoMatchQuery(), Occur.SHOULD); + } + } + return query.build(); + + } + + protected Query createTextQuery(String queryText, AnalysisMode analysisMode, LuceneFunction luceneFunction) throws ParseException { - Set text = searchParameters.getTextAttributes(); - if ((text == null) || (text.size() == 0)) - { - Query query = getFieldQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), queryText, - analysisMode, luceneFunction); - if (query == null) - { - return createNoMatchQuery(); - } - return query; - } else - { - BooleanQuery.Builder query = new BooleanQuery.Builder(); - for (String fieldName : text) - { - Query part = getFieldQuery(fieldName, queryText, analysisMode, luceneFunction); - if (part != null) - { - query.add(part, Occur.SHOULD); - } else - { - query.add(createNoMatchQuery(), Occur.SHOULD); - } - } - return query.build(); - } + return createDefaultTextQuery(textField -> getFieldQuery(textField, queryText, analysisMode, luceneFunction)); } @SuppressWarnings("unchecked") @@ -2951,34 +2949,10 @@ public class Solr4QueryParser extends QueryParser implements QueryConstants throw new UnsupportedOperationException("Range Queries are not support for " + FIELD_PATHWITHREPEATS); } else if (field.equals(FIELD_TEXT)) { - Set text = searchParameters.getTextAttributes(); - if ((text == null) || (text.size() == 0)) - { - Query query = getRangeQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), part1, part2, - includeLower, includeUpper, analysisMode, luceneFunction); - if (query == null) - { - return createNoMatchQuery(); - } - return query; - } else - { - BooleanQuery.Builder query = new BooleanQuery.Builder(); - for (String fieldName : text) - { - Query part = getRangeQuery(fieldName, part1, part2, includeLower, includeUpper, analysisMode, - luceneFunction); - if (part != null) - { - query.add(part, Occur.SHOULD); - } else - { - query.add(createNoMatchQuery(), Occur.SHOULD); - } - } - return query.build(); - } + return createDefaultTextQuery(textField -> getRangeQuery(textField, part1, part2, includeLower, + includeUpper, analysisMode, luceneFunction)); + } else if (field.equals(FIELD_CASCADETX)) { @@ -3384,33 +3358,9 @@ public class Solr4QueryParser extends QueryParser implements QueryConstants throw new UnsupportedOperationException("Prefix Queries are not support for " + FIELD_PATHWITHREPEATS); } else if (field.equals(FIELD_TEXT)) { - Set text = searchParameters.getTextAttributes(); - if ((text == null) || (text.size() == 0)) - { - Query query = getPrefixQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), termStr, - analysisMode); - if (query == null) - { - return createNoMatchQuery(); - } - return query; - } else - { - BooleanQuery.Builder query = new BooleanQuery.Builder(); - for (String fieldName : text) - { - Query part = getPrefixQuery(fieldName, termStr, analysisMode); - if (part != null) - { - query.add(part, Occur.SHOULD); - } else - { - query.add(createNoMatchQuery(), Occur.SHOULD); - } - } - return query.build(); - } + return createDefaultTextQuery(textField -> getPrefixQuery(textField, termStr, analysisMode)); + } else if (field.equals(FIELD_ID)) { boolean lowercaseExpandedTerms = getLowercaseExpandedTerms(); @@ -3572,33 +3522,9 @@ public class Solr4QueryParser extends QueryParser implements QueryConstants throw new UnsupportedOperationException("Wildcard Queries are not support for " + FIELD_PATHWITHREPEATS); } else if (field.equals(FIELD_TEXT)) { - Set text = searchParameters.getTextAttributes(); - if ((text == null) || (text.size() == 0)) - { - Query query = getWildcardQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), termStr, - analysisMode); - if (query == null) - { - return createNoMatchQuery(); - } - return query; - } else - { - BooleanQuery.Builder query = new BooleanQuery.Builder(); - for (String fieldName : text) - { - Query part = getWildcardQuery(fieldName, termStr, analysisMode); - if (part != null) - { - query.add(part, Occur.SHOULD); - } else - { - query.add(createNoMatchQuery(), Occur.SHOULD); - } - } - return query.build(); - } + return createDefaultTextQuery(textField -> getWildcardQuery(textField, termStr, analysisMode)); + } else if (field.equals(FIELD_ID)) { boolean lowercaseExpandedTerms = getLowercaseExpandedTerms(); @@ -3755,33 +3681,9 @@ public class Solr4QueryParser extends QueryParser implements QueryConstants throw new UnsupportedOperationException("Fuzzy Queries are not support for " + FIELD_PATHWITHREPEATS); } else if (field.equals(FIELD_TEXT)) { - Set text = searchParameters.getTextAttributes(); - if ((text == null) || (text.size() == 0)) - { - Query query = getFuzzyQuery(PROPERTY_FIELD_PREFIX + ContentModel.PROP_CONTENT.toString(), termStr, - minSimilarity); - if (query == null) - { - return createNoMatchQuery(); - } - return query; - } else - { - BooleanQuery.Builder query = new BooleanQuery.Builder(); - for (String fieldName : text) - { - Query part = getFuzzyQuery(fieldName, termStr, minSimilarity); - if (part != null) - { - query.add(part, Occur.SHOULD); - } else - { - query.add(createNoMatchQuery(), Occur.SHOULD); - } - } - return query.build(); - } + return createDefaultTextQuery(textField -> getFuzzyQuery(textField, termStr, minSimilarity)); + } else if (field.equals(FIELD_ID) || field.equals(FIELD_DBID) || field.equals(FIELD_ISROOT) || field.equals(FIELD_ISCONTAINER) || field.equals(FIELD_ISNODE) || field.equals(FIELD_TX) || field.equals(FIELD_PARENT) || field.equals(FIELD_PRIMARYPARENT) || field.equals(FIELD_QNAME) diff --git a/search-services/alfresco-search/src/main/java/org/alfresco/solr/utils/ThrowingFunction.java b/search-services/alfresco-search/src/main/java/org/alfresco/solr/utils/ThrowingFunction.java new file mode 100644 index 000000000..b21c22bf4 --- /dev/null +++ b/search-services/alfresco-search/src/main/java/org/alfresco/solr/utils/ThrowingFunction.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2019 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.solr.utils; + +/** + * @author elia + * + * Class used to use a lambda throwing a catched exceptino + * + * @param the type of the input to the function + * @param the type of the result of the function + * + */ +@FunctionalInterface +public interface ThrowingFunction { + R apply(T t) throws E; +} + diff --git a/search-services/alfresco-search/src/test/java/org/alfresco/solr/highlight/AlfrescoHighlighterTest.java b/search-services/alfresco-search/src/test/java/org/alfresco/solr/highlight/AlfrescoHighlighterTest.java index dc568bdea..38db5945e 100644 --- a/search-services/alfresco-search/src/test/java/org/alfresco/solr/highlight/AlfrescoHighlighterTest.java +++ b/search-services/alfresco-search/src/test/java/org/alfresco/solr/highlight/AlfrescoHighlighterTest.java @@ -310,23 +310,6 @@ public class AlfrescoHighlighterTest extends AbstractAlfrescoSolrTests logger.info("######### MultiTerm ###########"); - - req = areq(params( "q", "name:long", "qt", "/afts", "start", "0", "rows", "5", - HighlightParams.HIGHLIGHT, "true", - HighlightParams.Q, "lon*", - HighlightParams.FIELDS, "name", - HighlightParams.HIGHLIGHT_MULTI_TERM, "false", - HighlightParams.SIMPLE_PRE, "{", - HighlightParams.SIMPLE_POST, "}", - HighlightParams.SNIPPETS, String.valueOf(1), - HighlightParams.FRAGSIZE, String.valueOf(100)), - "{\"locales\":[\"en\"], \"tenants\": [ \"\" ]}"); - - assertQ(req, - "*[count(//lst[@name='highlighting']/lst)=2]", - "*[count(//lst[@name='highlighting']/lst/arr[@name='title'])=0]", - "*[count(//lst[@name='highlighting']/lst/arr[@name='name'])=0]"); - logger.info("######### CamelCase ###########"); diff --git a/search-services/alfresco-search/src/test/java/org/alfresco/solr/query/afts/qparser/QParserPluginTest.java b/search-services/alfresco-search/src/test/java/org/alfresco/solr/query/afts/qparser/QParserPluginTest.java index c3575f660..72858fb01 100644 --- a/search-services/alfresco-search/src/test/java/org/alfresco/solr/query/afts/qparser/QParserPluginTest.java +++ b/search-services/alfresco-search/src/test/java/org/alfresco/solr/query/afts/qparser/QParserPluginTest.java @@ -293,7 +293,8 @@ public class QParserPluginTest extends AbstractQParserPluginTest implements Quer assertAQuery("TEXT:\"alf?????\"", 1); assertAQuery("TEXT:\"al??????\"", 1); assertAQuery("TEXT:\"a???????\"", 1); - assertAQuery("TEXT:\"????????\"", 1); + // it finds thirteen and fourteen in cm:names. file fourteen contains alfresco in content. + assertAQuery("TEXT:\"????????\"", 2); assertAQuery("TEXT:\"a??re???\"", 1); assertAQuery("TEXT:\"?lfresco\"", 1); assertAQuery("TEXT:\"??fresco\"", 1); @@ -321,7 +322,8 @@ public class QParserPluginTest extends AbstractQParserPluginTest implements Quer assertAQuery("TEXT:\"*esco\"", 1); assertAQuery("TEXT:\"*sco\"", 1); assertAQuery("TEXT:\"*co\"", 1); - assertAQuery("TEXT:\"*o\"", 1); + // it finds cm:name:two and file fourteen because of content + assertAQuery("TEXT:\"*o\"", 2); assertAQuery("TEXT:\"****lf**sc***\"", 1); assertAQuery("TEXT:\"??lf**sc***\"", 0); assertAQuery("TEXT:\"alfresc*tutorial\"", 0); @@ -426,8 +428,10 @@ public class QParserPluginTest extends AbstractQParserPluginTest implements Quer @Test public void nonFields() { + /* the query is executed on cm:name, cm:title, cm:description and cm:content + * TEXT:fo* finds nodes with names four and fourteen */ assertAQuery("TEXT:fox", 1); - assertAQuery("TEXT:fo*", 1); + assertAQuery("TEXT:fo*", 2); assertAQuery("TEXT:f*x", 1); assertAQuery("TEXT:*ox", 1); diff --git a/search-services/alfresco-search/src/test/java/org/alfresco/solr/query/afts/requestHandler/AFTSDefaultTextQueryTest.java b/search-services/alfresco-search/src/test/java/org/alfresco/solr/query/afts/requestHandler/AFTSDefaultTextQueryTest.java new file mode 100644 index 000000000..cb49e7fd2 --- /dev/null +++ b/search-services/alfresco-search/src/test/java/org/alfresco/solr/query/afts/requestHandler/AFTSDefaultTextQueryTest.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2005-2019 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.solr.query.afts.requestHandler; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.solr.client.PropertyValue; +import org.alfresco.solr.client.StringPropertyValue; +import org.alfresco.solr.query.afts.TestDataProvider; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +import static java.util.Arrays.asList; +import static java.util.stream.IntStream.range; +import static org.alfresco.model.ContentModel.*; +import static org.alfresco.solr.AlfrescoSolrUtils.addNode; +import static com.google.common.collect.ImmutableMap.of; + + +/** + * @author elia + * + * The following test set needs to test that default text queries actually work by searching + * in cm:name, cm:title, cm:description and cm:content fields. + * + * THe default queries can be of the following types: + * exact, prefix, wildcard, fuzzy, span and range. + * This test set checks that in all these query types the default fields are involved in the search. + * + */ +public class AFTSDefaultTextQueryTest extends AbstractRequestHandlerTest +{ + + @BeforeClass + public static void beforeClass() throws Exception + { + TestDataProvider dataProvider = new TestDataProvider(h); + + List> data = asList( + of("name", "test1", + "description", "description of test 1", + "content", "test", + "title", "TITLE1", + "creator", "Luca"), + of("name", "test2", + "description", "description 2", + "content", "content of test 2", + "title", "Other Title", + "creator", "Mario"), + of("name", "file3", + "description", "this is not a description of test 1 and 2", + "content", "other content here ", + "title", "Third", + "creator", "Giovanni"), + of("name", "name of record 4", + "description", "other description right here", + "content", "content of file number 4", + "title", "Forth", + "creator", "Giuseppe")); + + + TEST_ROOT_NODEREF = dataProvider.getRootNode(); + + range(0, data.size()) + .forEach(dbId -> { + + Map record = data.get(dbId); + + String name = record.get("name"); + String description = record.get("description"); + String content = record.get("content"); + String title = record.get("title"); + String creator = record.get("creator"); + + Map properties = new HashMap<>(); + properties.put(PROP_NAME, new StringPropertyValue(name)); + properties.put(PROP_DESCRIPTION, new StringPropertyValue(description)); + properties.put(PROP_CONTENT, new StringPropertyValue(content)); + properties.put(PROP_TITLE, new StringPropertyValue(title)); + properties.put(PROP_CREATOR, new StringPropertyValue(creator)); + + addNode(getCore(), + dataModel, 1, dbId, 1, + TYPE_CONTENT, null, properties, null, + "the_owner_of_this_node_is" + name, + null, + new NodeRef[]{ TEST_ROOT_NODEREF }, + new String[]{ "/" + dataProvider.qName("a_qname_for_node_" + name) }, + dataProvider.newNodeRef(), true); + }); + } + + + /** + * Test exact search is working. + */ + @Test + public void defaultExactQueryTest() + { + + /* + * 3 results expected: + * record 1 ("test" in name, description and content) + * record 2 ("test" in name, and content) + * record 3 ("test" in description) + */ + assertResponseCardinality("test", 3); + + /* + * 3 results expected: + * record 2 ("Other" in title ) + * record 3 ("other" in content) + * record 4 ("other" in description) + */ + assertResponseCardinality("Other", 3); + + /* + * No results expected because creator should not be considered in default text search. + */ + assertResponseCardinality("Giovanni", 0); + } + + + /** + * Test range queries. + */ + @Test + public void defaultRangeQueryTest() + { + /* + * 3 results expected: + * record 1 ("test" in name, description and content) + * record 2 ("test" in name, and content) + * record 3 ("test" in description) + */ + assertResponseCardinality("[te to test]", 3); + + /* + * 3 results expected: + * record 2 ("Other" in title ) + * record 3 ("other" in content) + * record 4 ("other" in description) + */ + assertResponseCardinality("other to otherz ", 3); + + } + + /** + * Test wildcard queries. + */ + @Test + public void defaultWildCardQueryTest() + { + /* + * 3 results expected: + * record 1 ("test" in name, description and content) + * record 2 ("test" in name, and content) + * record 3 ("test" in description) + */ + assertResponseCardinality("?est", 3); + + /* + * 3 results expected: + * record 2 ("Other" in title ) + * record 3 ("other" in content) + * record 4 ("other" in description) + */ + assertResponseCardinality("?ther", 3); + + } + + + /** + * Thes span queries + */ + @Test + public void defaultSpanQueryTest() + { + /* + * 1 result expected + * record 1 ("Other title" in title) + */ + assertResponseCardinality("Other *(0) title", 1); + + /* + * 2 results expected + * record 1 ("description of test" in description) + * record 3 ("description of test" in description) + */ + assertResponseCardinality("description *(1) test ", 2); + + /* + * No results expected. + * There is no "description test" in any field. + */ + assertResponseCardinality("description *(0) test ", 0); + + /* + * 1 result expected + * record 4 ("name of record" in name) + */ + assertResponseCardinality("name *(1) record ", 1); + + /* + * 2 results expected + * record 2 ("other content here" in content) + */ + assertResponseCardinality("other *(1) here ", 1); + + } + + /** + * Test prefix queries + */ + @Test + public void defaultPrefixQueryTest() + { + /* + * 3 results expected + * record 1 ("test" in name, description and content) + * record 2 ("test" in name, and content) + * record 3 ("test" in description) + */ + assertResponseCardinality("te*", 3); + + /* + * 3 results expected + * record 1 ("file" in name, description and content) + * record 2 ("file" in name, and content) + */ + assertResponseCardinality("fil*", 2); + + /* + * 3 results expected: + * record 2 ("Other" in title ) + * record 3 ("other" in content) + * record 4 ("other" in description) + */ + assertResponseCardinality("oth*", 3); + } + + +}