diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScore.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScore.java index 76c0721740800b7be703aea321125e2feb5b5420..feb6532229600715b43f744d6463f0a62284a27f 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScore.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScore.java @@ -47,10 +47,43 @@ public class SearchResultSorterByScore implements ISearchResultSorter // 1. Get terms to use as input for the main algorithm List<Pattern> partialMatchTerms = new ArrayList<Pattern>(); List<String> exactMatchTerms = new ArrayList<String>(); + List<Boost> boosts = new ArrayList<Boost>(); for (DetailedSearchCriterion criterion : criteria.getCriteria()) { partialMatchTerms.add(getPartialMatchTerm(criterion.getValue())); exactMatchTerms.add(getExactMatchTerm(criterion.getValue())); + + switch (criterion.getField().getKind()) + { + case ANY_FIELD: + boosts.add(new Boost(1, 1, 1, 1, null)); + break; + case ANY_PROPERTY: + boosts.add(new Boost(0, 0, 1, 1, null)); + break; + case PROPERTY: + boosts.add(new Boost(0, 0, 0, 1, criterion.getField().getPropertyCode())); + break; + case ATTRIBUTE: + if (criterion.getField().getAttributeCode().equalsIgnoreCase("code")) + { + boosts.add(new Boost(1, 0, 0, 0, null)); // Attribute code + } else if ( // TODO FIX hard coded types, will be clever to not naming the same thing differently internally to avoid this. + criterion.getField().getAttributeCode().equalsIgnoreCase("sample_type") + || criterion.getField().getAttributeCode().equalsIgnoreCase("data_set_type") + || criterion.getField().getAttributeCode().equalsIgnoreCase("material_type") + || criterion.getField().getAttributeCode().equalsIgnoreCase("experiment_type")) + { + boosts.add(new Boost(0, 1, 0, 0, null)); // Attribute type code + } else + { + boosts.add(new Boost(1, 1, 1, 1, null)); // Other attributes not supported, default to general case + } + break; + case REGISTRATOR: + boosts.add(new Boost(1, 1, 1, 1, null)); // Registrator not supported, default to general case + break; + } } // 2. Main algorithm @@ -58,7 +91,7 @@ public class SearchResultSorterByScore implements ISearchResultSorter for (IEntitySearchResult entity : entitiesToSort) { - int score = getScore(entity, partialMatchTerms, exactMatchTerms); + int score = getScore(entity, partialMatchTerms, exactMatchTerms, boosts); scores.put(entity, score); } @@ -82,41 +115,43 @@ public class SearchResultSorterByScore implements ISearchResultSorter }); } - private int getScore(IEntitySearchResult entity, List<Pattern> partialMatchTerms, List<String> exactMatchTerms) + private int getScore(IEntitySearchResult entity, List<Pattern> partialMatchTerms, List<String> exactMatchTerms, List<Boost> boosts) { int score = 0; for (int i = 0; i < exactMatchTerms.size(); i++) { Pattern partialTerm = partialMatchTerms.get(i); String exactTerm = exactMatchTerms.get(i); + Boost boost = boosts.get(i); // 1. Code if (isPartialMatch(entity.getCode(), partialTerm)) { // If code matches partially - score += 100000; + score += 100000 * boost.getCodeBoost(); if (isExactMatch(entity.getCode(), exactTerm)) { // If code matches exactly - score += 1000000; + score += 1000000 * boost.getCodeBoost(); } } // 2. Entity type code if (isExactMatch(entity.getTypeCode(), exactTerm)) { // If type matches exactly - score += 1000; + score += 1000 * boost.getTypeCodeBoost(); } // 3. Properties - if (entity.getProperties() != null && entity.getProperties().values() != null) + if (entity.getProperties() != null && entity.getProperties().keySet() != null) { - for (String propertyValue : entity.getProperties().values()) + for (String propertykey : entity.getProperties().keySet()) { + String propertyValue = entity.getProperties().get(propertykey); if (isPartialMatch(propertyValue, partialTerm)) { // If property matches partially - score += 100; + score += 100 * boost.getPropertyBoost(propertykey); if (isExactMatch(propertyValue, exactTerm)) { // If property matches exactly - score += 10000; + score += 10000 * boost.getPropertyBoost(propertykey); } } } @@ -126,6 +161,54 @@ public class SearchResultSorterByScore implements ISearchResultSorter return score; } + // + // Helper Methods + // + private static class Boost + { + private int codeBoost; + + private int typeCodeBoost; + + private int propertyBoost; + + private int propertyDefaultBoost; + + private String propertyName; + + public Boost(int codeBoost, int typeCodeBoost, int propertyDefaultBoost, int propertyBoost, String propertyName) + { + super(); + this.codeBoost = codeBoost; + this.typeCodeBoost = typeCodeBoost; + this.propertyDefaultBoost = propertyDefaultBoost; + this.propertyBoost = propertyBoost; + this.propertyName = propertyName; + } + + public int getCodeBoost() + { + return codeBoost; + } + + public int getTypeCodeBoost() + { + return typeCodeBoost; + } + + public int getPropertyBoost(String propertyNameToBoost) + { + if (this.propertyName != null && this.propertyName.equals(propertyNameToBoost)) + { + return propertyBoost; + } else + { + return propertyDefaultBoost; + } + } + + } + public Pattern getPartialMatchTerm(String term) { return Pattern.compile(("*" + term + "*").replace("*", ".*").replace("?", ".?"), Pattern.CASE_INSENSITIVE); diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScoreTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScoreTest.java index 7f760920166972e0d61a6ac5c7990ea74ea2eb0b..2166a75632c9261d9ab7ae1263f060d7444cde58 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScoreTest.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterByScoreTest.java @@ -3,6 +3,10 @@ package ch.systemsx.cisd.openbis.generic.server.business.search.sort; import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.assertEntities; import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.createEntity; import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.getAnyFieldCriterion; +import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.getAnyPropertyFieldCriterion; +import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.getCodeFieldCriterion; +import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.getPropertyFieldCriterion; +import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.getTypeCodeFieldCriterion; import static ch.systemsx.cisd.openbis.generic.server.business.search.sort.SearchResultSorterTestHelper.sort; import java.util.ArrayList; @@ -23,7 +27,7 @@ public class SearchResultSorterByScoreTest extends AssertJUnit { @Test - public void testCriteriaAnyCode() + public void testCriteriaAnyForCode() { // Entities Setup List<EntitySearchResult> entities = new ArrayList<EntitySearchResult>(); @@ -40,7 +44,25 @@ public class SearchResultSorterByScoreTest extends AssertJUnit } @Test - public void testCriteriaAnyTypeCode() + public void testCriteriaAtrForCode() + { + // Entities Setup + List<EntitySearchResult> entities = new ArrayList<EntitySearchResult>(); + entities.add(createEntity("OTHER_2", "TYPE_2", "CODE")); + entities.add(createEntity("CODE_OTHER", "TYPE")); + entities.add(createEntity("OTHER_1", "TYPE_1", "CODE", "CODE")); + entities.add(createEntity("OTHER_3", "TYPE_3", "CODE", "CODE", "CODE")); + entities.add(createEntity("CODE", "TYPE")); + + // Test hit only code + sort(entities, getCodeFieldCriterion("CODE")); + + // Verify results + assertEntities(entities, "CODE", "CODE_OTHER", "OTHER_1", "OTHER_2", "OTHER_3"); + } + + @Test + public void testCriteriaAnyForTypeCode() { // Entities Setup List<EntitySearchResult> entities = new ArrayList<EntitySearchResult>(); @@ -57,7 +79,25 @@ public class SearchResultSorterByScoreTest extends AssertJUnit } @Test - public void testCriteriaAnyProperties() + public void testCriteriaAtrForTypeCode() + { + // Entities Setup + List<EntitySearchResult> entities = new ArrayList<EntitySearchResult>(); + entities.add(createEntity("CODE_2", "TYPE_2", "TYPE", "TYPE", "TYPE")); + entities.add(createEntity("CODE_1", "TYPE_1", "TYPE")); + entities.add(createEntity("CODE_3", "OTHER")); + entities.add(createEntity("CODE_4", "TYPE_4", "TYPE", "TYPE")); + entities.add(createEntity("CODE", "TYPE")); + + // Test hit only type code + sort(entities, getTypeCodeFieldCriterion("TYPE")); + + // Verify results + assertEntities(entities, "CODE", "CODE_1", "CODE_2", "CODE_3", "CODE_4"); + } + + @Test + public void testCriteriaAnyForProperties() { // Entities Setup List<EntitySearchResult> entities = new ArrayList<EntitySearchResult>(); @@ -73,6 +113,40 @@ public class SearchResultSorterByScoreTest extends AssertJUnit assertEntities(entities, "CODE_4", "CODE_3", "CODE_2", "CODE_1"); } + @Test + public void testCriteriaPropForProperties() + { + // Entities Setup + List<EntitySearchResult> entities = new ArrayList<EntitySearchResult>(); + entities.add(createEntity("CODE_2", "TYPE_2", "ABC_1", "ABC_2")); + entities.add(createEntity("CODE_1", "TYPE_1", "ABC_1", "ABC")); + entities.add(createEntity("CODE_3", "TYPE_3", "ABC_1", "ABC_2", "ABC_3")); + entities.add(createEntity("CODE_4", "TYPE_4", "ABC")); + + // Test hit only properties, partial and exact + sort(entities, getPropertyFieldCriterion("PROP_1", "ABC")); + + // Verify results + assertEntities(entities, "CODE_4", "CODE_1", "CODE_2", "CODE_3"); + } + + @Test + public void testCriteriaAnyPropForProperties() + { + // Entities Setup + List<EntitySearchResult> entities = new ArrayList<EntitySearchResult>(); + entities.add(createEntity("CODE_2", "TYPE_2", "ABC_1", "ABC_2")); + entities.add(createEntity("CODE_1", "TYPE_1", "ABC_1", "ABC")); + entities.add(createEntity("CODE_3", "TYPE_3", "ABC_1", "ABC_2", "ABC_3")); + entities.add(createEntity("CODE_4", "TYPE_4", "ABC")); + + // Test hit only properties, partial and exact + sort(entities, getAnyPropertyFieldCriterion("ABC")); + + // Verify results + assertEntities(entities, "CODE_1", "CODE_4", "CODE_3", "CODE_2"); + } + @Test public void testCriteriaAnyAll() { diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterTestHelper.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterTestHelper.java index bf32faa1c214a80f5c98bc7a7206de46099e203b..6b6f5d4bb0062410483ef6ca8dbb7c757edb8065 100644 --- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterTestHelper.java +++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/search/sort/SearchResultSorterTestHelper.java @@ -10,6 +10,7 @@ import junit.framework.Assert; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriteria; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchCriterion; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DetailedSearchField; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleAttributeSearchFieldKind; public class SearchResultSorterTestHelper { @@ -53,6 +54,28 @@ public class SearchResultSorterTestHelper return new DetailedSearchCriterion(DetailedSearchField.createAnyField(Arrays.asList("ANY")), value); } + public static DetailedSearchCriterion getPropertyFieldCriterion(String propertyCode, String value) + { + return new DetailedSearchCriterion(DetailedSearchField.createPropertyField(propertyCode), value); + } + + public static DetailedSearchCriterion getAnyPropertyFieldCriterion(String value) + { + return new DetailedSearchCriterion(DetailedSearchField.createAnyPropertyField(Arrays.asList("ANY")), value); + } + + public static DetailedSearchCriterion getCodeFieldCriterion(String value) + { + // TODO refactor it not to use sample specific class + return new DetailedSearchCriterion(DetailedSearchField.createAttributeField(SampleAttributeSearchFieldKind.CODE), value); + } + + public static DetailedSearchCriterion getTypeCodeFieldCriterion(String value) + { + // TODO refactor it not to use sample specific class + return new DetailedSearchCriterion(DetailedSearchField.createAttributeField(SampleAttributeSearchFieldKind.SAMPLE_TYPE), value); + } + public static EntitySearchResult createEntity(String code, String typeCode, String... propertyValues) { Map<String, String> properties = new HashMap<String, String>();