diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index b736cb43ad..4030ab5faa 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -113,6 +113,12 @@ + + + + + + ${spaces.store} diff --git a/config/alfresco/solr-facets-context.xml b/config/alfresco/solr-facets-context.xml index 7bb588883c..401cd0e2e8 100644 --- a/config/alfresco/solr-facets-context.xml +++ b/config/alfresco/solr-facets-context.xml @@ -43,4 +43,118 @@ ${solr_facets.root} + + + + + + + + + + + + + + + + + + @{http://www.alfresco.org/model/content/1.0}content.mimetype + + + + + + + + @{http://www.alfresco.org/model/content/1.0}creator + @{http://www.alfresco.org/model/content/1.0}modifier + + + + + + + + SITE + + + + + + + + @{http://www.alfresco.org/model/content/1.0}content.size + + + + + + java.util.LinkedHashMap + + + + + + + + + + + + + + + + + + + @{http://www.alfresco.org/model/content/1.0}created + @{http://www.alfresco.org/model/content/1.0}modified + + + + + + java.util.LinkedHashMap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SITE + TAG + ANCESTOR + PARENT + ASPECT + TYPE + OWNER + + + \ No newline at end of file diff --git a/config/alfresco/subsystems/Search/solr/facet/solr-facets-config.properties b/config/alfresco/subsystems/Search/solr/facet/solr-facets-config.properties index fabed8fbb6..d52949e180 100644 --- a/config/alfresco/subsystems/Search/solr/facet/solr-facets-config.properties +++ b/config/alfresco/subsystems/Search/solr/facet/solr-facets-config.properties @@ -15,41 +15,41 @@ default.cm\:content.mimetype.scope=ALL default.cm\:content.mimetype.scopedSites= default.cm\:content.mimetype.isEnabled=true -# Field-Facet-Qname => cm:description.__ -default.cm\:description.__.filterID=filter_description -default.cm\:description.__.displayName=faceted-search.facet-menu.facet.description -default.cm\:description.__.displayControl=alfresco/search/FacetFilters -default.cm\:description.__.maxFilters=5 -default.cm\:description.__.hitThreshold=1 -default.cm\:description.__.minFilterValueLength=4 -default.cm\:description.__.sortBy=DESCENDING -default.cm\:description.__.scope=ALL -default.cm\:description.__.scopedSites= -default.cm\:description.__.isEnabled=false +# Field-Facet-Qname => cm:description +default.cm\:description.filterID=filter_description +default.cm\:description.displayName=faceted-search.facet-menu.facet.description +default.cm\:description.displayControl=alfresco/search/FacetFilters +default.cm\:description.maxFilters=5 +default.cm\:description.hitThreshold=1 +default.cm\:description.minFilterValueLength=4 +default.cm\:description.sortBy=DESCENDING +default.cm\:description.scope=ALL +default.cm\:description.scopedSites= +default.cm\:description.isEnabled=false -# Field-Facet-Qname => cm:creator.__.u -default.cm\:creator.__.u.filterID=filter_creator -default.cm\:creator.__.u.displayName=faceted-search.facet-menu.facet.creator -default.cm\:creator.__.u.displayControl=alfresco/search/FacetFilters -default.cm\:creator.__.u.maxFilters=5 -default.cm\:creator.__.u.hitThreshold=1 -default.cm\:creator.__.u.minFilterValueLength=4 -default.cm\:creator.__.u.sortBy=ALPHABETICALLY -default.cm\:creator.__.u.scope=ALL -default.cm\:creator.__.u.scopedSites= -default.cm\:creator.__.u.isEnabled=true +# Field-Facet-Qname => cm:creator +default.cm\:creator.filterID=filter_creator +default.cm\:creator.displayName=faceted-search.facet-menu.facet.creator +default.cm\:creator.displayControl=alfresco/search/FacetFilters +default.cm\:creator.maxFilters=5 +default.cm\:creator.hitThreshold=1 +default.cm\:creator.minFilterValueLength=4 +default.cm\:creator.sortBy=ALPHABETICALLY +default.cm\:creator.scope=ALL +default.cm\:creator.scopedSites= +default.cm\:creator.isEnabled=true -# Field-Facet-Qname => cm:modifier.__.u -default.cm\:modifier.__.u.filterID=filter_modifier -default.cm\:modifier.__.u.displayName=faceted-search.facet-menu.facet.modifier -default.cm\:modifier.__.u.displayControl=alfresco/search/FacetFilters -default.cm\:modifier.__.u.maxFilters=5 -default.cm\:modifier.__.u.hitThreshold=1 -default.cm\:modifier.__.u.minFilterValueLength=4 -default.cm\:modifier.__.u.sortBy=ALPHABETICALLY -default.cm\:modifier.__.u.scope=ALL -default.cm\:modifier.__.u.scopedSites= -default.cm\:modifier.__.u.isEnabled=true +# Field-Facet-Qname => cm:modifier +default.cm\:modifier.filterID=filter_modifier +default.cm\:modifier.displayName=faceted-search.facet-menu.facet.modifier +default.cm\:modifier.displayControl=alfresco/search/FacetFilters +default.cm\:modifier.maxFilters=5 +default.cm\:modifier.hitThreshold=1 +default.cm\:modifier.minFilterValueLength=4 +default.cm\:modifier.sortBy=ALPHABETICALLY +default.cm\:modifier.scope=ALL +default.cm\:modifier.scopedSites= +default.cm\:modifier.isEnabled=true # Field-Facet-Qname => cm:created default.cm\:created.filterID=filter_created @@ -62,7 +62,6 @@ default.cm\:created.sortBy=ALPHABETICALLY default.cm\:created.scope=ALL default.cm\:created.scopedSites= default.cm\:created.isEnabled=true -default.cm\:created.EXTRA-PROP.blockIncludeFacetRequest=true # Field-Facet-Qname => cm:modified default.cm\:modified.filterID=filter_modified @@ -75,7 +74,6 @@ default.cm\:modified.sortBy=ALPHABETICALLY default.cm\:modified.scope=ALL default.cm\:modified.scopedSites= default.cm\:modified.isEnabled=true -default.cm\:modified.EXTRA-PROP.blockIncludeFacetRequest=true # Field-Facet-Qname => cm:content.size default.cm\:content.size.filterID=filter_content_size @@ -88,4 +86,3 @@ default.cm\:content.size.sortBy=ALPHABETICALLY default.cm\:content.size.scope=ALL default.cm\:content.size.scopedSites= default.cm\:content.size.isEnabled=true -default.cm\:content.size.EXTRA-PROP.blockIncludeFacetRequest=true diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 60ddcd6207..ab0d7a01c3 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -34,8 +34,9 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.management.subsystems.SwitchableApplicationContextFactory; import org.alfresco.repo.model.Repository; import org.alfresco.repo.search.impl.solr.facet.SolrFacetHelper; -import org.alfresco.repo.search.impl.solr.facet.SolrFacetHelper.FacetLabel; -import org.alfresco.repo.search.impl.solr.facet.SolrFacetHelper.FacetLabelDisplayHandler; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabel; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandler; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandlerRegistry; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.NodeRef; @@ -96,12 +97,14 @@ public class Search extends BaseScopableProcessorExtension implements Initializi /** Solr facet helper */ private SolrFacetHelper solrFacetHelper; + private FacetLabelDisplayHandlerRegistry facetLabelDisplayHandlerRegistry; @Override public void afterPropertiesSet() throws Exception { PropertyCheck.mandatory(this, "services", services); - this.solrFacetHelper = new SolrFacetHelper(services); + PropertyCheck.mandatory(this, "solrFacetHelper", solrFacetHelper); + PropertyCheck.mandatory(this, "facetLabelDisplayHandlerRegistry", facetLabelDisplayHandlerRegistry); } /** @@ -146,6 +149,20 @@ public class Search extends BaseScopableProcessorExtension implements Initializi // JavaScript API + /** + * @param solrFacetHelper the solrFacetHelper to set + */ + public void setSolrFacetHelper(SolrFacetHelper solrFacetHelper) + { + this.solrFacetHelper = solrFacetHelper; + } + /** + * @param facetLabelDisplayHandlerRegistry the facetLabelDisplayHandlerRegistry to set + */ + public void setFacetLabelDisplayHandlerRegistry(FacetLabelDisplayHandlerRegistry facetLabelDisplayHandlerRegistry) + { + this.facetLabelDisplayHandlerRegistry = facetLabelDisplayHandlerRegistry; + } public String getSearchSubsystem() { return (searchSubsystem == null) ? "" : searchSubsystem.getCurrentSourceBeanName(); @@ -717,34 +734,28 @@ public class Search extends BaseScopableProcessorExtension implements Initializi { for (String field: facets) { - final FieldFacet fieldFacet; - if (solrFacetHelper.isSpecialFacetId(field)) + final String modifiedField = "@" + field; + if (solrFacetHelper.hasFacetQueries(modifiedField)) { - fieldFacet = new FieldFacet(field); + List facetQueries = solrFacetHelper.getFacetQueries(modifiedField); + addFacetQuery(sp, field, facetQueries, query); } else { - fieldFacet = new FieldFacet("@" + field); + final FieldFacet fieldFacet; + if (solrFacetHelper.isSpecialFacetId(field)) + { + fieldFacet = new FieldFacet(field); + } + else + { + fieldFacet = new FieldFacet(modifiedField); + } + sp.addFieldFacet(fieldFacet); } - sp.addFieldFacet(fieldFacet); - } - - List facetQueries = null; - // Workaround for ACE-1605 - if (query.indexOf("created:") < 0 && query.indexOf("modified:") < 0) - { - facetQueries = solrFacetHelper.getDefaultFacetQueries(); - } - else - { - facetQueries = solrFacetHelper.createFacetQueriesFromSearchQuery(query); - } - for (String fq : facetQueries) - { - sp.addFacetQuery(fq); } } - + // error handling opions boolean exceptionOnError = true; if (onerror != null) @@ -954,7 +965,7 @@ public class Search extends BaseScopableProcessorExtension implements Initializi if (f.getSecond() > 0) { String facetValue = f.getFirst(); - FacetLabelDisplayHandler handler = solrFacetHelper.getDisplayHandler(ff.getField()); + FacetLabelDisplayHandler handler = facetLabelDisplayHandlerRegistry.getDisplayHandler(ff.getField()); String label = (handler == null) ? facetValue : handler.getDisplayLabel(facetValue).getLabel(); facets.add(new ScriptFacetResult(facetValue, label, -1, f.getSecond())); @@ -979,23 +990,23 @@ public class Search extends BaseScopableProcessorExtension implements Initializi if (entry.getValue() > 0) { String key = entry.getKey(); - // for example the key could be: {!afts}@{http://www.alfresco.org/model/content/1.0}created:[2013-10-29 TO 2014-04-29] + // for example the key could be: {!afts}@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-1DAY TO NOW/DAY+1DAY] // qName => @{http://www.alfresco.org/model/content/1.0}created // 7 => {!afts} - String qName = key.substring(7, key.lastIndexOf(':')); + key = key.substring(7); + String qName = key.substring(0, key.lastIndexOf(':')); // Retrieve the previous facet queries List fqs = facetMeta.get(qName); if (fqs == null) { - // Shouldn't be here - throw new AlfrescoRuntimeException("Field facet [" + qName + "] has" - + " not been registered with SolrFacetHelper.BUCKETED_FIELD_FACETS."); + fqs = new ArrayList<>(); + logger.info("Field facet [" + key + "] has not been registered."); } - FacetLabelDisplayHandler handler = solrFacetHelper.getDisplayHandler(qName); - FacetLabel facetLabel = (handler == null) ? new FacetLabel(qName, key.substring(qName.length(), - key.length()), -1) : handler.getDisplayLabel(key); - + // Get the handler for this qName + FacetLabelDisplayHandler handler = facetLabelDisplayHandlerRegistry.getDisplayHandler(qName); + FacetLabel facetLabel = (handler == null) ? new FacetLabel(key, key, -1) : handler.getDisplayLabel(key); + fqs.add(new ScriptFacetResult(facetLabel.getValue(), facetLabel.getLabel(), facetLabel.getLabelIndex(), entry.getValue())); } }// End of bucketing @@ -1037,6 +1048,33 @@ public class Search extends BaseScopableProcessorExtension implements Initializi return new Pair>(res, meta); } + /** + * Adds facet queries to the {@code SearchParameters} + * + * @param sp the SearchParameters + * @param field the requested field facet + * @param facetQueries list of generated facet queries + * @param query the requested search query + */ + protected void addFacetQuery(SearchParameters sp, String field, List facetQueries, String query) + { + // Workaround for ACE-1605 + if (query.indexOf(field) < 0) + { + for (String fq : facetQueries) + { + sp.addFacetQuery(fq); + } + } + else + { + String fq = solrFacetHelper.createFacetQueriesFromSearchQuery(field, query); + if (fq != null) + { + sp.addFacetQuery(fq); + } + } + } /** * Search sort column diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/FacetQueryProvider.java b/source/java/org/alfresco/repo/search/impl/solr/facet/FacetQueryProvider.java new file mode 100644 index 0000000000..2af83169d9 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/FacetQueryProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet; + +import java.util.List; +import java.util.Map; + +/** + * A contract for classes which need to create a set of predefined facet queries. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public interface FacetQueryProvider +{ + + /** + * Gets the created facet queries + * + * @return read-only map of facet queries or an empty map. + */ + public Map> getFacetQueries(); +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelper.java b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelper.java index 70bfa858a4..c63b9666dc 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelper.java +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelper.java @@ -16,273 +16,104 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ + package org.alfresco.repo.search.impl.solr.facet; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.joda.time.LocalDate; /** - * A helper class to overcome the limitation of Solr 1.4 for dealing with facets. - *

- * Notice: probably this class or most of its functionalities will be removed - * when we upgrade to Solr 4 + * A helper class for facet queries. * * @author Jamal Kaabi-Mofrad + * @since 5.0 */ -// TODO use Solr4 date math for date buckets... public class SolrFacetHelper { private static Log logger = LogFactory.getLog(SolrFacetHelper.class); - private static final String FQ_NS_PREFIX = "@{http://www.alfresco.org/model/content/1.0}"; - private static final String CREATED_FIELD_FACET_QUERY = FQ_NS_PREFIX + "created"; - private static final String MODIFIED_FIELD_FACET_QUERY = FQ_NS_PREFIX + "modified"; - private static final String CONTENT_SIZE_FIELD_FACET_QUERY = FQ_NS_PREFIX + "content.size"; + private final Map> facetQueries; - // Content size buckets - private static final int KB = 1024; - private static final int MB = KB * 1024; - private static final int TINY = 10 * KB; - private static final int SMALL = 100 * KB; - private static final int MEDIUM = MB; - private static final int LARGE = 16 * MB; - private static final int HUGE = 128 * MB; - private static final String SIZE_BUCKETS_CACHE_KEY = "sizeBucketsCacheKey"; - - // Positive-look-behind RegEx. - // E.g. pattern-input => {http://www.alfresco.org/model/content/1.0}created:("2014-04-07".."2014-05-07") - // the matcher-output => 2014-04-07".."2014-05-07 - /** Pattern to search for created date */ - private static final Pattern CREATED_DATE_PATTERN = Pattern.compile("(?<=created:\\(\")(\\d{4}-\\d{2}-\\d{2})(\"..\")(\\d{4}-\\d{2}-\\d{2})"); - - /** Pattern to search for modified date */ - private static final Pattern MODIFIED_DATE_PATTERN = Pattern.compile("(?<=modified:\\(\")(\\d{4}-\\d{2}-\\d{2})(\"..\")(\\d{4}-\\d{2}-\\d{2})"); - - /** Content size buckets */ - private static final List CONTENT_SIZE_BUCKETS = new ArrayList<>(6); - static - { - CONTENT_SIZE_BUCKETS.add("0 TO " + TINY); - CONTENT_SIZE_BUCKETS.add(TINY + " TO " + SMALL); - CONTENT_SIZE_BUCKETS.add(SMALL + " TO " + MEDIUM); - CONTENT_SIZE_BUCKETS.add(MEDIUM + " TO " + LARGE); - CONTENT_SIZE_BUCKETS.add(LARGE + " TO " + HUGE); - CONTENT_SIZE_BUCKETS.add(HUGE + " TO MAX"); - } + /** These facet IDs are recognised by SOLR and can be used directly within faceted searches. */ + private Set specialFacetIds = Collections.emptySet(); - /** Field facet buckets */ - private static final Set BUCKETED_FIELD_FACETS = new HashSet<>(3); - static - { - BUCKETED_FIELD_FACETS.add(CREATED_FIELD_FACET_QUERY); - BUCKETED_FIELD_FACETS.add(MODIFIED_FIELD_FACET_QUERY); - BUCKETED_FIELD_FACETS.add(CONTENT_SIZE_FIELD_FACET_QUERY); - } - - /** These facet IDs are recognised by SOLR and can be used directly within facetted searches. */ - private final List specialFacetIds = Arrays.asList(new String[] { - "SITE", "TAG", "ANCESTOR", - "PARENT", "ASPECT", "TYPE", "OWNER" }); - - /** Facet value and facet query display label handlers */ - private Map displayHandlers; - - /** Thread safe cache for storing the Date buckets facet query */ - private BucketsCache> fqDateCache = null; - /** * Constructor * * @param serviceRegistry */ - public SolrFacetHelper(ServiceRegistry serviceRegistry) + public SolrFacetHelper(List facetQueryProviders) { - this.fqDateCache = new BucketsCache<>(new FacetQueryParamDateBuckets()); - this.displayHandlers = new HashMap<>(6); - - UserNameDisplayHandler userNameDisplayHandler = new UserNameDisplayHandler(serviceRegistry); - MimetypeDisplayHandler mimetypeDisplayHandler = new MimetypeDisplayHandler(serviceRegistry); - DateBucketsDisplayHandler dateBucketsDisplayHandler = new DateBucketsDisplayHandler(); - ContentSizeBucketsDisplayHandler contentSizeBucketsDisplayHandler = new ContentSizeBucketsDisplayHandler(); - - this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}creator.__.u", userNameDisplayHandler); - this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}modifier.__.u", userNameDisplayHandler); - this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}content.mimetype", mimetypeDisplayHandler); - this.displayHandlers.put(CREATED_FIELD_FACET_QUERY, dateBucketsDisplayHandler); - this.displayHandlers.put(MODIFIED_FIELD_FACET_QUERY, dateBucketsDisplayHandler); - this.displayHandlers.put(CONTENT_SIZE_FIELD_FACET_QUERY, contentSizeBucketsDisplayHandler); + PropertyCheck.mandatory(this, "facetQueryProviders", facetQueryProviders); + + facetQueries = new LinkedHashMap<>(); + for (FacetQueryProvider queryProvider : facetQueryProviders) + { + for (Entry> entry : queryProvider.getFacetQueries().entrySet()) + { + facetQueries.put(entry.getKey(), entry.getValue()); + } + } } - + + public void setSpecialFacetIds(Set ids) + { + this.specialFacetIds = ids; + } + /** - * Gets the predefined set of facet queries. Currently the facet queries are: - *

  • Created date buckets
  • - *
  • Modified date buckets
  • - *
  • Content size buckets
  • + * Gets the predefined set of facet queries. Currently the facet queries + * are:
  • Created date buckets
  • Modified date buckets
  • + * Content size buckets
  • * * @return list of facet queries */ public List getDefaultFacetQueries() { - List facetQueries = new ArrayList<>(); - List dateBuckets = getDateBuckets(); - - // Created and Modified dates facet queries - for (String bucket : dateBuckets) + Collection> queries = facetQueries.values(); + List list = new ArrayList(); + for (List q : queries) { - facetQueries.add(CREATED_FIELD_FACET_QUERY + ":[" + bucket + ']'); - facetQueries.add(MODIFIED_FIELD_FACET_QUERY + ":[" + bucket + ']'); + list.addAll(q); } - - // Content size facet query - for (String bucket : CONTENT_SIZE_BUCKETS) - { - facetQueries.add(CONTENT_SIZE_FIELD_FACET_QUERY + ":[" + bucket + ']'); - } - - return facetQueries; - } - - /** - * Creates Created and Modified facet queries by trying to - * extract the date range from the Created and/or Modified filter(s) within - * the search query. If either of the Created or Modified - * filter is missing from the search query, the default set of facet queries - * will be created for the missing date filter. Also, creates the default - * Content.Size facet queries. - * - * @return list of facet queries - */ - // ACE-1605 - public List createFacetQueriesFromSearchQuery(String searchQuery) - { - String createdDateRange = extractDateRange(searchQuery, CREATED_DATE_PATTERN); - String modifiedDateRange = extractDateRange(searchQuery, MODIFIED_DATE_PATTERN); - - if (createdDateRange == null && modifiedDateRange == null) - { - return getDefaultFacetQueries(); - } - - List facetQueries = new ArrayList<>(); - List dateBuckets = getDateBuckets(); - - if (createdDateRange != null && modifiedDateRange != null) - { - // Add the Created and Modified dates facet queries - facetQueries.add(CREATED_FIELD_FACET_QUERY + ":[" + createdDateRange + ']'); - facetQueries.add(MODIFIED_FIELD_FACET_QUERY + ":[" + modifiedDateRange + ']'); - } - else if (createdDateRange == null) - { - // As Created date is null, add all the the predefined created buckets. - for (String bucket : dateBuckets) - { - facetQueries.add(CREATED_FIELD_FACET_QUERY + ":[" + bucket + ']'); - } - // We can be sure, that modifiedDateRange is not null (see the first 'if' statement). - facetQueries.add(MODIFIED_FIELD_FACET_QUERY + ":[" + modifiedDateRange + ']'); - } - else - { - // If we are here, it means, Modified date is null, hence, add - // all the the predefined modified buckets. - for (String bucket : dateBuckets) - { - facetQueries.add(MODIFIED_FIELD_FACET_QUERY + ":[" + bucket + ']'); - } - facetQueries.add(CREATED_FIELD_FACET_QUERY + ":[" + createdDateRange + ']'); - } - - // Always add the content size facet query - for (String bucket : CONTENT_SIZE_BUCKETS) - { - facetQueries.add(CONTENT_SIZE_FIELD_FACET_QUERY + ":[" + bucket + ']'); - } - - return facetQueries; - } - - /** - * Gets the date range that will match against the pattern from the search query - * - * @param searchQuery the string input - * @param pattern the compiled representation of a RegEx - * @return the date range or null, if there is no match. An example of the - * date range string representation is: 2014-04-28 TO 2014-04-29 - */ - private String extractDateRange(String searchQuery, Pattern pattern) - { - String dateRange = null; - Matcher matcher = pattern.matcher(searchQuery); - if (matcher.find()) - { - dateRange = matcher.group(); - // E.g. dateRange => 2014-04-28".."2014-04-29 - } - - return (dateRange == null) ? null : dateRange.replace("\"..\"", " TO "); - } - - /** - * Gets the predefined date buckets from the cache - * - * @return list of date ranges. An example of the date range string - * representation is: 2014-04-28 TO 2014-04-29 - */ - private List getDateBuckets() - { - List dateBuckets = null; - try - { - dateBuckets = fqDateCache.getRangeBuckets(LocalDate.now()); - } - catch (Exception e) - { - logger.error( - "Error occured while trying to get the date buckets from the cache. Calculating the dates without the cache.", e); - dateBuckets = makeDateBuckets(LocalDate.now()); - } - return dateBuckets; + return list; } /** - * Gets the appropriate facet display label handler + * Whether the specified field is defined as a facet.query or not * - * @param qName the field facet QName - * @return the diplayHandler object or null if there is no handler - * registered for the given @{code qName} + * @param facetField + * @return true if the facet is facet.query, false otherwise */ - public FacetLabelDisplayHandler getDisplayHandler(String qName) + public boolean hasFacetQueries(String facetField) { - return displayHandlers.get(qName); + return facetQueries.containsKey(facetField); + } + + /** + * Gets all the defined facet queries for the specified field + * @param facetField the requested field + * @return an unmodifiable list of facet queries, or null if none found + */ + public List getFacetQueries(String facetField) + { + List queries = facetQueries.get(facetField); + if (queries == null) + { + return null; + } + return Collections.unmodifiableList(queries); } /** @@ -292,9 +123,50 @@ public class SolrFacetHelper */ public Set getBucketedFieldFacets() { - return Collections.unmodifiableSet(BUCKETED_FIELD_FACETS); + return Collections.unmodifiableSet(facetQueries.keySet()); + } + + /** + * Creates a facet query by trying to extract the date range from the the + * search query. + * + * @return the facet query, or null if the date range cannot be extracted + */ + // workaround for https://issues.alfresco.com/jira/browse/ACE-1605 + public String createFacetQueriesFromSearchQuery(String field, String searchQuery) + { + if (field == null) + { + return null; + } + try + { + if (field.startsWith("@")) + { + field = field.substring(1); + } + String escapedField = searchQuery.substring(searchQuery.indexOf(field)); + + String dateRange = escapedField.substring(field.length() + 2, escapedField.indexOf(")")).trim(); + // E.g. dateRange => "NOW/DAY-7DAYS".."NOW/DAY+1DAY" + + dateRange = dateRange.replace("\"..\"", " TO "); + // remove the date-range quotations marks + dateRange = dateRange.replace("\"", ""); + + // the processed dateRange will be, for example, NOW/DAY-7DAY TO NOW/DAY+1DAY + dateRange = (dateRange == null) ? null : field + ":[" + dateRange + "]"; + + return "@" + dateRange; + } + catch (Exception e) + { + logger.warn("Couldn’t extract " + field + " date range from the search query." + e); + return null; + } } + /** * Is the specified facet ID part of the list of "specials" which are * handled by our SOLR service as is? @@ -303,440 +175,4 @@ public class SolrFacetHelper { return specialFacetIds.contains(facetId); } - - /** - * Creates Date buckets. The dates are in ISO8601 format (yyyy-MM-dd) - * - * @return list of date ranges. An example of the date range string - * representation is: 2014-04-28 TO 2014-04-29 - */ - private static List makeDateBuckets(LocalDate currentDate) - { - List list = new ArrayList<>(5); - - String nowStr = " TO " + currentDate.toString(); - - // Bucket => yesterday TO today - list.add(currentDate.minusDays(1).toString() + nowStr); - - // Bucket => Last week TO today - list.add(currentDate.minusWeeks(1).toString() + nowStr); - - // Bucket => Last month TO today - list.add(currentDate.minusMonths(1).toString() + nowStr); - - // Bucket => Last 6 months TO today - list.add(currentDate.minusMonths(6).toString() + nowStr); - - // Bucket => Last year TO today - list.add(currentDate.minusYears(1).toString() + nowStr); - - return list; - } - - /** - * Creates display name for the Date buckets. - * - * @return Map of {@literal } - */ - private static Map> makeDateBucketsDisplayLabel(LocalDate date) - { - List dateBuckets = makeDateBuckets(date); - Map> bucketDisplayName = new HashMap<>(5); - - if (dateBuckets.size() != 5) - { - throw new AlfrescoRuntimeException("Date buckets size does not match the bucket display label size!"); - } - - bucketDisplayName.put(dateBuckets.get(0), new Pair("faceted-search.date.one-day.label", 0)); - bucketDisplayName.put(dateBuckets.get(1), new Pair("faceted-search.date.one-week.label", 1)); - bucketDisplayName.put(dateBuckets.get(2), new Pair("faceted-search.date.one-month.label", 2)); - bucketDisplayName.put(dateBuckets.get(3), new Pair("faceted-search.date.six-months.label", 3)); - bucketDisplayName.put(dateBuckets.get(4), new Pair("faceted-search.date.one-year.label", 4)); - - return bucketDisplayName; - } - - /** - * Creates display name for the Content size buckets. - * - * @return Map of {@literal } - */ - private static Map> makeContentSizeBucketsDisplayLabel() - { - Map> bucketDisplayName = new HashMap<>(6); - - if (CONTENT_SIZE_BUCKETS.size() != 6) - { - throw new AlfrescoRuntimeException("Content size buckets size does not match the bucket display label size!"); - } - - bucketDisplayName.put(CONTENT_SIZE_BUCKETS.get(0), new Pair("faceted-search.size.0-10KB.label", 0)); - bucketDisplayName.put(CONTENT_SIZE_BUCKETS.get(1), new Pair("faceted-search.size.10-100KB.label", 1)); - bucketDisplayName.put(CONTENT_SIZE_BUCKETS.get(2), new Pair("faceted-search.size.100KB-1MB.label", 2)); - bucketDisplayName.put(CONTENT_SIZE_BUCKETS.get(3), new Pair("faceted-search.size.1-16MB.label", 3)); - bucketDisplayName.put(CONTENT_SIZE_BUCKETS.get(4), new Pair("faceted-search.size.16-128MB.label", 4)); - bucketDisplayName.put(CONTENT_SIZE_BUCKETS.get(5), new Pair("faceted-search.size.over128.label", 5)); - - return bucketDisplayName; - } - - /** - * Single value cache for date and size buckets. - * - * @author Jamal Kaabi-Mofrad - */ - private static class BucketsCache - { - private final ConcurrentMap> cache = new ConcurrentHashMap<>(); - - private final Buckets buckets; - - public BucketsCache(Buckets buckets) - { - this.buckets = buckets; - } - - public V getRangeBuckets(final K arg) throws Exception - { - while (true) - { - Future future = cache.get(arg); - // first checks to see if the buckets computation has been started - if (future == null) - { - Callable result = new Callable() - { - public V call() throws Exception - { - // remove the previous entry - Set keys = cache.keySet(); - for (K key : keys) - { - if (!key.equals(arg)) - { - cache.remove(key); - } - } - return buckets.compute(arg); - } - }; - // If the calculation has been started, creates a - // FutureTask, registers it in the Map, and starts the computation - FutureTask futureTask = new FutureTask<>(result); - - future = cache.putIfAbsent(arg, futureTask); - if (future == null) - { - future = futureTask; - futureTask.run(); - } - } - try - { - return future.get(); - } - catch (CancellationException ce) - { - // Removes cache pollution. If the calculation is cancelled - // or failed. As caching a Future instead of a value creates - // the possibility of cache pollution - cache.remove(arg, future); - } - catch (ExecutionException e) - { - throw new IllegalStateException(e); - } - } - } - } - - /** - * Interface to be implemented by classes that wish to create buckets. - * - * @author Jamal Kaabi-Mofrad - */ - private static interface Buckets - { - V compute(K arg) throws Exception; - } - - /** - * A simple implementation which creates Date buckets for the facet query. - * - * @author Jamal Kaabi-Mofrad - */ - private static class FacetQueryParamDateBuckets implements Buckets> - { - @Override - public List compute(LocalDate localDate) throws Exception - { - return makeDateBuckets(localDate); - } - } - - /** - * A simple implementation which creates display label for the Date buckets - * from the facet query result. - * - * @author Jamal Kaabi-Mofrad - */ - private static class FacetQueryResultDateBuckets implements Buckets>> - { - @Override - public Map> compute(LocalDate localDate) throws Exception - { - return makeDateBucketsDisplayLabel(localDate); - } - } - - /** - * A simple implementation which creates display label for the Content size - * buckets from the facet query result. - * - * @author Jamal Kaabi-Mofrad - */ - private static class FacetQueryResultContentSizeBuckets implements Buckets>> - { - @Override - public Map> compute(String arg) throws Exception - { - return makeContentSizeBucketsDisplayLabel(); - } - } - - /** - * Solr facet value and facet query result display label handler - * - * @author Jamal Kaabi-Mofrad - */ - public static interface FacetLabelDisplayHandler - { - FacetLabel getDisplayLabel(String value); - } - - /** - * A class to encapsulate the result of the facet label display handler - * - * @author Jamal Kaabi-Mofrad - */ - public static class FacetLabel - { - private final String value; - private final String label; - private final int labelIndex; - - /** - * @param value - * @param label - * @param labelIndex - */ - public FacetLabel(String value, String label, int labelIndex) - { - this.value = value; - this.label = label; - this.labelIndex = labelIndex; - } - - /** - * Gets the original facet value or a new modified value - * - * @return the original facet value or a new modified value - */ - public String getValue() - { - return this.value; - } - - /** - * Gets the facet display label - * - * @return the label - */ - public String getLabel() - { - return this.label; - } - - /** - * Gets the label index to be used for sorting. The index only relevant - * to to Date and Size facets. - * - * @return the index or -1, if it isn't relevant to the facet label - */ - public int getLabelIndex() - { - return this.labelIndex; - } - - /* - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result + ((this.label == null) ? 0 : this.label.hashCode()); - result = prime * result + this.labelIndex; - result = prime * result + ((this.value == null) ? 0 : this.value.hashCode()); - return result; - } - - /* - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) - { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof FacetLabel)) - return false; - FacetLabel other = (FacetLabel) obj; - if (this.label == null) - { - if (other.label != null) - return false; - } - else if (!this.label.equals(other.label)) - return false; - if (this.labelIndex != other.labelIndex) - return false; - if (this.value == null) - { - if (other.value != null) - return false; - } - else if (!this.value.equals(other.value)) - return false; - return true; - } - } - - /** - * A simple handler to get the full user name from the userID - * - * @author Jamal Kaabi-Mofrad - */ - public static class UserNameDisplayHandler implements FacetLabelDisplayHandler - { - private final PersonService personService; - private final NodeService nodeService; - - public UserNameDisplayHandler(ServiceRegistry services) - { - this.personService = services.getPersonService(); - this.nodeService = services.getNodeService(); - } - - @Override - public FacetLabel getDisplayLabel(String value) - { - String name = null; - - final NodeRef personRef = personService.getPersonOrNull(value); - if (personRef != null) - { - final String firstName = (String) nodeService.getProperty(personRef, ContentModel.PROP_FIRSTNAME); - final String lastName = (String) nodeService.getProperty(personRef, ContentModel.PROP_LASTNAME); - name = (firstName != null ? firstName + " " : "") + (lastName != null ? lastName : ""); - } - return new FacetLabel(value, name == null ? value : name.trim(), -1); - } - } - - /** - * A simple handler to get the Mimetype display label. - * - * @author Jamal Kaabi-Mofrad - */ - public static class MimetypeDisplayHandler implements FacetLabelDisplayHandler - { - private final MimetypeService mimetypeService; - - public MimetypeDisplayHandler(ServiceRegistry services) - { - this.mimetypeService = services.getMimetypeService(); - } - - @Override - public FacetLabel getDisplayLabel(String value) - { - Map mimetypes = mimetypeService.getDisplaysByMimetype(); - String displayName = mimetypes.get(value); - return new FacetLabel(value, displayName == null ? value : displayName.trim(), -1); - } - } - - /** - * A simple handler to get the appropriate display label for the date buckets. - * - * @author Jamal Kaabi-Mofrad - */ - public static class DateBucketsDisplayHandler implements FacetLabelDisplayHandler - { - private final BucketsCache>> cache = new BucketsCache<>( - new FacetQueryResultDateBuckets()); - - @Override - public FacetLabel getDisplayLabel(String value) - { - Map> dateBuckets = null; - - String dateRange = value.substring(value.indexOf('[') + 1, value.length() - 1); - String[] lowerUpperDates = dateRange.split("\\sTO\\s"); - LocalDate date = LocalDate.parse(lowerUpperDates[1]); - try - { - dateBuckets = cache.getRangeBuckets(date); - } - catch (Exception e) - { - logger.error( - "Error occurred while trying to get the date buckets from the cache. Calculating the dates without the cache.", e); - dateBuckets = makeDateBucketsDisplayLabel(date); - } - String newValue = lowerUpperDates[0] + "\"..\"" + lowerUpperDates[1]; - Pair labelIndexPair = dateBuckets.get(dateRange); - return new FacetLabel(newValue, labelIndexPair.getFirst(), labelIndexPair.getSecond()); - } - } - - /** - * A simple handler to get the appropriate display label for the content size buckets. - * - * @author Jamal Kaabi-Mofrad - */ - public static class ContentSizeBucketsDisplayHandler implements FacetLabelDisplayHandler - { - private final BucketsCache>> cache = new BucketsCache<>( - new FacetQueryResultContentSizeBuckets()); - - @Override - public FacetLabel getDisplayLabel(String value) - { - String sizeRange = value.substring(value.indexOf('[') + 1, value.length() - 1); - String[] lowerUppperSize = sizeRange.split("\\sTO\\s"); - - Map> sizeBuckets; - try - { - sizeBuckets = cache.getRangeBuckets(SIZE_BUCKETS_CACHE_KEY); - } - catch (Exception e) - { - logger.error( - "Error occurred while trying to get the content size buckets from the cache. Calculating the size without the cache.", e); - sizeBuckets = makeContentSizeBucketsDisplayLabel(); - } - - String newValue = lowerUppperSize[0] + "\"..\"" + lowerUppperSize[1]; - Pair labelIndexPair = sizeBuckets.get(sizeRange); - return new FacetLabel(newValue, labelIndexPair.getFirst(), labelIndexPair.getSecond()); - } - } } diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/AbstractFacetLabelDisplayHandler.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/AbstractFacetLabelDisplayHandler.java new file mode 100644 index 0000000000..5fdae7ad9b --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/AbstractFacetLabelDisplayHandler.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +import java.util.Set; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.util.PropertyCheck; + +/** + * A support class for facet label display handlers. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public abstract class AbstractFacetLabelDisplayHandler implements FacetLabelDisplayHandler +{ + protected ServiceRegistry serviceRegistry; + protected Set supportedFieldFacets; + private FacetLabelDisplayHandlerRegistry registry; + + /** + * Registers this instance of the facet handler with the registry. This will + * call the {@link #init()} method and then register if the registry is available. + * + */ + public final void register() + { + init(); + + for (String fieldFacet : supportedFieldFacets) + { + registry.addDisplayHandler(fieldFacet, this); + } + + } + + protected void init() + { + PropertyCheck.mandatory(this, "registry", registry); + PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); + PropertyCheck.mandatory(this, "supportedFieldFacets", supportedFieldFacets); + } + + /** + * @param supportedFieldFacets the supportedFieldFacets to set + */ + public void setSupportedFieldFacets(Set supportedFieldFacets) + { + this.supportedFieldFacets = supportedFieldFacets; + } + + /** + * Set the service registry + * + * @param services the service registry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * Set the registry to register with + * + * @param registry a metadata extracter registry + */ + public void setRegistry(FacetLabelDisplayHandlerRegistry registry) + { + this.registry = registry; + } +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/ContentSizeBucketsDisplayHandler.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/ContentSizeBucketsDisplayHandler.java new file mode 100644 index 0000000000..362470843d --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/ContentSizeBucketsDisplayHandler.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.Set; + +import org.alfresco.repo.search.impl.solr.facet.FacetQueryProvider; +import org.alfresco.repo.search.impl.solr.facet.SolrFacetConfigException; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * A simple handler to get the appropriate display label for the content size buckets. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class ContentSizeBucketsDisplayHandler extends AbstractFacetLabelDisplayHandler implements FacetQueryProvider +{ + private static final Pattern SIZE_RANGE_PATTERN = Pattern.compile("(\\[\\d+\\sTO\\s(\\d+|MAX)\\])"); + + private final Map facetLabelMap; + private final Map> facetQueriesMap; + + public ContentSizeBucketsDisplayHandler(Set facetQueryFields, LinkedHashMap sizeBucketsMap) + { + ParameterCheck.mandatory("facetQueryFields", facetQueryFields); + ParameterCheck.mandatory("sizeBucketsMap", sizeBucketsMap); + + this.supportedFieldFacets = Collections.unmodifiableSet(facetQueryFields); + + facetLabelMap = new HashMap<>(sizeBucketsMap.size()); + Map> facetQueries = new LinkedHashMap<>(facetQueryFields.size()); + + for (String facetQueryField : facetQueryFields) + { + List queries = new ArrayList<>(); + int index = 0; + for (Entry bucket : sizeBucketsMap.entrySet()) + { + String sizeRange = bucket.getKey().trim(); + Matcher matcher = SIZE_RANGE_PATTERN.matcher(sizeRange); + if (!matcher.find()) + { + throw new SolrFacetConfigException( + "Invalid size range. Example of a valid size range is: [0 TO 1024]"); + } + // build the facet query. e.g. {http://www.alfresco.org/model/content/1.0}content.size:[0 TO 1024] + String facetQuery = facetQueryField + ':' + sizeRange; + queries.add(facetQuery); + + // indexOf('[') => 1 + String sizeRangeQuery = sizeRange.substring(1, sizeRange.length() - 1); + sizeRangeQuery = sizeRangeQuery.replaceFirst("\\sTO\\s", "\"..\""); + facetLabelMap.put(facetQuery, new FacetLabel(sizeRangeQuery, bucket.getValue(), index++)); + } + facetQueries.put(facetQueryField, queries); + } + this.facetQueriesMap = Collections.unmodifiableMap(facetQueries); + } + + @Override + public FacetLabel getDisplayLabel(String value) + { + FacetLabel facetLabel = facetLabelMap.get(value); + return (facetLabel == null) ? new FacetLabel(value, value, -1) : facetLabel; + } + + @Override + public Map> getFacetQueries() + { + return this.facetQueriesMap; + } +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/DateBucketsDisplayHandler.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/DateBucketsDisplayHandler.java new file mode 100644 index 0000000000..8bb894f6fb --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/DateBucketsDisplayHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.repo.search.impl.solr.facet.FacetQueryProvider; +import org.alfresco.repo.search.impl.solr.facet.SolrFacetConfigException; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * A simple handler to get the appropriate display label for the date buckets. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class DateBucketsDisplayHandler extends AbstractFacetLabelDisplayHandler implements FacetQueryProvider +{ + private static final Pattern DATE_RANGE_PATTERN = Pattern.compile("(\\[\\w+\\S+\\sTO\\s(\\w+\\S+)\\])"); + + private final Map facetLabelMap; + private final Map> facetQueriesMap; + + public DateBucketsDisplayHandler(Set facetQueryFields, LinkedHashMap dateBucketsMap) + { + ParameterCheck.mandatory("facetQueryFields", facetQueryFields); + ParameterCheck.mandatory("dateBucketsMap", dateBucketsMap); + + this.supportedFieldFacets = Collections.unmodifiableSet(facetQueryFields); + + facetLabelMap = new HashMap<>(dateBucketsMap.size()); + Map> facetQueries = new LinkedHashMap<>(facetQueryFields.size()); + + for (String facetQueryField : facetQueryFields) + { + List queries = new ArrayList<>(); + int index = 0; + for (Entry bucket : dateBucketsMap.entrySet()) + { + String dateRange = bucket.getKey().trim(); + Matcher matcher = DATE_RANGE_PATTERN.matcher(dateRange); + if (!matcher.find()) + { + throw new SolrFacetConfigException( + "Invalid date range. Example of a valid date range is: '[NOW/DAY-1DAY TO NOW/DAY+1DAY]'"); + } + // build the facet query. e.g. {http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-1DAY TO NOW/DAY+1DAY] + String facetQuery = facetQueryField + ':' + dateRange; + queries.add(facetQuery); + + // indexOf('[') => 1 + String dateRangeQuery = dateRange.substring(1, dateRange.length() - 1); + dateRangeQuery = dateRangeQuery.replaceFirst("\\sTO\\s", "\"..\""); + facetLabelMap.put(facetQuery, new FacetLabel(dateRangeQuery, bucket.getValue(), index++)); + } + facetQueries.put(facetQueryField, queries); + } + this.facetQueriesMap = Collections.unmodifiableMap(facetQueries); + } + + @Override + public FacetLabel getDisplayLabel(String value) + { + FacetLabel facetLabel = facetLabelMap.get(value); + return (facetLabel == null) ? new FacetLabel(value, value, -1) : facetLabel; + } + + @Override + public Map> getFacetQueries() + { + return this.facetQueriesMap; + } + +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabel.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabel.java new file mode 100644 index 0000000000..06b2bdf29b --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabel.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +/** + * A class to encapsulate the result of the facet label display handler. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class FacetLabel +{ + private final String value; + private final String label; + private final int labelIndex; + + /** + * @param value + * @param label + * @param labelIndex + */ + public FacetLabel(String value, String label, int labelIndex) + { + this.value = value; + this.label = label; + this.labelIndex = labelIndex; + } + + /** + * Gets the original facet value or a new modified value + * + * @return the original facet value or a new modified value + */ + public String getValue() + { + return this.value; + } + + /** + * Gets the facet display label + * + * @return the label + */ + public String getLabel() + { + return this.label; + } + + /** + * Gets the label index to be used for sorting. The index only relevant to + * to Date and Size facets. + * + * @return the index or -1, if it isn't relevant to the facet label + */ + public int getLabelIndex() + { + return this.labelIndex; + } + + /* + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.label == null) ? 0 : this.label.hashCode()); + result = prime * result + this.labelIndex; + result = prime * result + ((this.value == null) ? 0 : this.value.hashCode()); + return result; + } + + /* + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof FacetLabel)) + return false; + FacetLabel other = (FacetLabel) obj; + if (this.label == null) + { + if (other.label != null) + return false; + } + else if (!this.label.equals(other.label)) + return false; + if (this.labelIndex != other.labelIndex) + return false; + if (this.value == null) + { + if (other.value != null) + return false; + } + else if (!this.value.equals(other.value)) + return false; + return true; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabelDisplayHandler.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabelDisplayHandler.java new file mode 100644 index 0000000000..42854f30c5 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabelDisplayHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + + +/** + * An interface for Solr facet value and facet query result display label handler. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public interface FacetLabelDisplayHandler +{ + /** + * Gets the user friendly display label for the returned facet value + * + * @param value the facet value + * @return user friendly display label or the original value, if there is no result + */ + public FacetLabel getDisplayLabel(String value); +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabelDisplayHandlerRegistry.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabelDisplayHandlerRegistry.java new file mode 100644 index 0000000000..1979dbf660 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/FacetLabelDisplayHandlerRegistry.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A registry which holds and provides the appropriate display handler for a + * particular facet field. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class FacetLabelDisplayHandlerRegistry +{ + private final ConcurrentMap registry; + + public FacetLabelDisplayHandlerRegistry() + { + this.registry = new ConcurrentHashMap(); + } + + /** + * Register an instance of {@code FacetLabelDisplayHandler} with the + * specified field facet. + * + * @param fieldFacet the field facet + * @param displayHandler the display handler + */ + public void addDisplayHandler(String fieldFacet, FacetLabelDisplayHandler displayHandler) + { + registry.putIfAbsent(fieldFacet, displayHandler); + } + + /** + * Gets the display handler. + * + * @param fieldFacet the field facet to perform the lookup + * @return the display handler or null if none found + */ + public FacetLabelDisplayHandler getDisplayHandler(String fieldFacet) + { + return registry.get(fieldFacet); + } +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/MimetypeDisplayHandler.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/MimetypeDisplayHandler.java new file mode 100644 index 0000000000..5482138b04 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/MimetypeDisplayHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * A simple handler to get the Mimetype display label. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class MimetypeDisplayHandler extends AbstractFacetLabelDisplayHandler +{ + + public MimetypeDisplayHandler(Set supportedFieldFacets) + { + ParameterCheck.mandatory("supportedFieldFacets", supportedFieldFacets); + + this.supportedFieldFacets = Collections.unmodifiableSet(new HashSet<>(supportedFieldFacets)); + } + + @Override + public FacetLabel getDisplayLabel(String value) + { + Map mimetypes = serviceRegistry.getMimetypeService().getDisplaysByMimetype(); + String displayName = mimetypes.get(value); + return new FacetLabel(value, displayName == null ? value : displayName.trim(), -1); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/SiteTitleDisplayHandler.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/SiteTitleDisplayHandler.java new file mode 100644 index 0000000000..5e24a67c4c --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/SiteTitleDisplayHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * A simple handler to get the site title from the site short name. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class SiteTitleDisplayHandler extends AbstractFacetLabelDisplayHandler +{ + + public SiteTitleDisplayHandler(Set supportedFieldFacets) + { + ParameterCheck.mandatory("supportedFieldFacets", supportedFieldFacets); + + this.supportedFieldFacets = Collections.unmodifiableSet(new HashSet<>(supportedFieldFacets)); + } + + @Override + public FacetLabel getDisplayLabel(String value) + { + SiteService siteService = serviceRegistry.getSiteService(); + SiteInfo siteInfo = siteService.getSite(value); + String title = siteInfo != null ? siteInfo.getTitle() : value; + return new FacetLabel(value, title, -1); + } +} diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/handler/UserNameDisplayHandler.java b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/UserNameDisplayHandler.java new file mode 100644 index 0000000000..c3db3286fb --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/handler/UserNameDisplayHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2014 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.repo.search.impl.solr.facet.handler; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.springframework.extensions.surf.util.ParameterCheck; + +/** + * A simple handler to get the full user name from the userID. + * + * @author Jamal Kaabi-Mofrad + * @since 5.0 + */ +public class UserNameDisplayHandler extends AbstractFacetLabelDisplayHandler +{ + + public UserNameDisplayHandler(Set supportedFieldFacets) + { + ParameterCheck.mandatory("supportedFieldFacets", supportedFieldFacets); + + this.supportedFieldFacets = Collections.unmodifiableSet(new HashSet<>(supportedFieldFacets)); + } + + @Override + public FacetLabel getDisplayLabel(String value) + { + String name = null; + + final NodeRef personRef = serviceRegistry.getPersonService().getPersonOrNull(value); + if (personRef != null) + { + NodeService nodeService = serviceRegistry.getNodeService(); + final String firstName = (String) nodeService.getProperty(personRef, ContentModel.PROP_FIRSTNAME); + final String lastName = (String) nodeService.getProperty(personRef, ContentModel.PROP_LASTNAME); + name = (firstName != null ? firstName + " " : "") + (lastName != null ? lastName : ""); + } + return new FacetLabel(value, name == null ? value : name.trim(), -1); + } +} diff --git a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfigTest.java b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfigTest.java index f2586a58e0..b0910d79e5 100644 --- a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfigTest.java +++ b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfigTest.java @@ -133,7 +133,7 @@ public class SolrFacetConfigTest // See if the overrides worked SolrFacetProperties creatorFP = defaultProps.get("test_filter_creator"); - assertEquals("Incorrect QNAME", "{http://www.alfresco.org/model/content/1.0}creator.__.u", creatorFP.getFacetQName().toString()); + assertEquals("Incorrect QNAME", "{http://www.alfresco.org/model/content/1.0}creator", creatorFP.getFacetQName().toString()); String msg = "The value has not been overridden with the value from the custom properties"; assertEquals(msg, 10, creatorFP.getMaxFilters()); @@ -160,7 +160,7 @@ public class SolrFacetConfigTest config.onBootstrap(applicationEvent); SolrFacetProperties creatorFP = config.getDefaultFacets().get("test_filter_creator"); - assertEquals("Incorrect QNAME", "{http://www.alfresco.org/model/content/1.0}creator.__.u", creatorFP.getFacetQName().toString()); + assertEquals("Incorrect QNAME", "{http://www.alfresco.org/model/content/1.0}creator", creatorFP.getFacetQName().toString()); assertEquals(10, creatorFP.getMaxFilters()); assertEquals(5, creatorFP.getHitThreshold()); assertEquals(14, creatorFP.getMinFilterValueLength()); @@ -175,7 +175,7 @@ public class SolrFacetConfigTest config.onBootstrap(applicationEvent); creatorFP = config.getDefaultFacets().get("test_filter_creator"); - assertEquals("Incorrect QNAME", "{http://www.alfresco.org/model/content/1.0}creator.__.u", creatorFP.getFacetQName().toString()); + assertEquals("Incorrect QNAME", "{http://www.alfresco.org/model/content/1.0}creator", creatorFP.getFacetQName().toString()); assertEquals(5, creatorFP.getMaxFilters()); assertEquals(1, creatorFP.getHitThreshold()); assertEquals(4, creatorFP.getMinFilterValueLength()); diff --git a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelperTest.java b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetQueriesDisplayHandlersTest.java similarity index 51% rename from source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelperTest.java rename to source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetQueriesDisplayHandlersTest.java index 0432cafb1b..8faf23fe59 100644 --- a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelperTest.java +++ b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetQueriesDisplayHandlersTest.java @@ -21,34 +21,38 @@ package org.alfresco.repo.search.impl.solr.facet; import java.util.List; import java.util.Set; -import org.alfresco.repo.search.impl.solr.facet.SolrFacetHelper.FacetLabel; + +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabel; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandler; +import org.alfresco.repo.search.impl.solr.facet.handler.FacetLabelDisplayHandlerRegistry; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.ServiceRegistry; import org.alfresco.util.ApplicationContextHelper; -import org.joda.time.LocalDate; import org.junit.*; import org.springframework.context.ApplicationContext; import static org.junit.Assert.*; /** - * This class contains tests for the class {@link SolrFacetHelper}. + * This class contains tests for the class {@link SolrFacetHelper} and {@link FacetLabelDisplayHandler}. * * @author Jamal Kaabi-Mofrad + * @since 5.0 */ -public class SolrFacetHelperTest +public class SolrFacetQueriesDisplayHandlersTest { private static ApplicationContext context; private static SolrFacetHelper helper; - + private static FacetLabelDisplayHandlerRegistry displayHandlerRegistry; + @BeforeClass public static void initStaticData() throws Exception { context = ApplicationContextHelper.getApplicationContext(); - ServiceRegistry serviceRegistry = (ServiceRegistry) context.getBean("ServiceRegistry"); - helper = new SolrFacetHelper(serviceRegistry); + + helper = (SolrFacetHelper) context.getBean("facet.solrFacetHelper"); + displayHandlerRegistry = (FacetLabelDisplayHandlerRegistry) context.getBean("facet.facetLabelDisplayHandlerRegistry"); } - + /** * Perform pre-test initialization. * @@ -76,85 +80,44 @@ public class SolrFacetHelperTest } /** - * Run the List createFacetQueriesFromSearchQuery(String) method test. + * Run the String createFacetQueriesFromSearchQuery(String) method test. * * @throws Exception * */ @Test - public void testCreateFacetQueriesFromSearchQuery() throws Exception + public void testCreateFacetQueryFromSearchQuery() throws Exception { - String searchQueryWithCreatedDate = "query=(test AND ({http://www.alfresco.org/model/content/1.0}created:(\"2014-05-30\"..\"2014-06-06\" ))" - + " AND (+TYPE:\"cm:content\" OR +TYPE:\"cm:folder\")) AND -TYPE:\"cm:thumbnail\" AND" - + " -TYPE:\"cm:failedThumbnail\" AND -TYPE:\"cm:rating\" AND -TYPE:\"st:site\" AND" - + " -ASPECT:\"st:siteContainer\" AND -ASPECT:\"sys:hidden\" AND" - + " -cm:creator:system, stores=[workspace://SpacesStore]"; - - List result = helper.createFacetQueriesFromSearchQuery(searchQueryWithCreatedDate); - assertNotNull(result); - - LocalDate currentDate = LocalDate.now(); - String nowStr = " TO " + currentDate.toString(); - String yesterday = currentDate.minusDays(1).toString() + nowStr; - String lastWeek = currentDate.minusWeeks(1).toString() + nowStr; - String lastMonth = currentDate.minusMonths(1).toString() + nowStr; - String last6Months = currentDate.minusMonths(6).toString() + nowStr; - String lastYear = currentDate.minusYears(1).toString() + nowStr; - - // As the created date has been specified in the search, we don't create - // the rest of the buckets for the created date facet. - assertEquals(12, result.size()); - - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[2014-05-30 TO 2014-06-06]")); - - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + yesterday + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + lastWeek + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + lastMonth + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + last6Months + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + lastYear + "]")); - - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[0 TO 10240]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[10240 TO 102400]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[102400 TO 1048576]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[1048576 TO 16777216]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[16777216 TO 134217728]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[134217728 TO MAX]")); - - String searchQueryWithCreatedAndModifiedDate = "query=(test AND ({http://www.alfresco.org/model/content/1.0}created:(\"2014-05-30\"..\"2014-06-06\" ) AND AND ({http://www.alfresco.org/model/content/1.0}modified:(\"2014-05-30\"..\"2014-05-31\" ))" + String searchQueryWithCreatedDate = "query=(test AND ({http://www.alfresco.org/model/content/1.0}created:(\"NOW/DAY-7DAYS\"..\"NOW/DAY+1DAY\" ))" + " AND (+TYPE:\"cm:content\" OR +TYPE:\"cm:folder\")) AND -TYPE:\"cm:thumbnail\" AND" + " -TYPE:\"cm:failedThumbnail\" AND -TYPE:\"cm:rating\" AND -TYPE:\"st:site\" AND" + " -ASPECT:\"st:siteContainer\" AND -ASPECT:\"sys:hidden\" AND" + " -cm:creator:system, stores=[workspace://SpacesStore]"; - result = helper.createFacetQueriesFromSearchQuery(searchQueryWithCreatedAndModifiedDate); + String result = helper.createFacetQueriesFromSearchQuery("{http://www.alfresco.org/model/content/1.0}created", searchQueryWithCreatedDate); assertNotNull(result); + assertEquals("@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-7DAYS TO NOW/DAY+1DAY]", result); - currentDate = LocalDate.now(); - nowStr = " TO " + currentDate.toString(); - yesterday = currentDate.minusDays(1).toString() + nowStr; - lastWeek = currentDate.minusWeeks(1).toString() + nowStr; - lastMonth = currentDate.minusMonths(1).toString() + nowStr; - last6Months = currentDate.minusMonths(6).toString() + nowStr; - lastYear = currentDate.minusYears(1).toString() + nowStr; + String searchQueryWithCreatedAndModifiedDate = "query=(test AND ({http://www.alfresco.org/model/content/1.0}created:(\"NOW/DAY-7DAYS\"..\"NOW/DAY+1DAY\" ) AND ({http://www.alfresco.org/model/content/1.0}modified:(\"NOW/DAY-1DAY\"..\"NOW/DAY+1DAY\" ))" + + " AND (+TYPE:\"cm:content\" OR +TYPE:\"cm:folder\")) AND -TYPE:\"cm:thumbnail\" AND" + + " -TYPE:\"cm:failedThumbnail\" AND -TYPE:\"cm:rating\" AND -TYPE:\"st:site\" AND" + + " -ASPECT:\"st:siteContainer\" AND -ASPECT:\"sys:hidden\" AND" + + " -cm:creator:system, stores=[workspace://SpacesStore]"; - // As the created and modified dates have been specified in the search, - // we don't create the rest of the buckets for the created and modified date facets. - assertEquals(8, result.size()); + String[] fields = { "{http://www.alfresco.org/model/content/1.0}created", "{http://www.alfresco.org/model/content/1.0}modified" }; - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[2014-05-30 TO 2014-06-06]")); + result = helper.createFacetQueriesFromSearchQuery(fields[0], searchQueryWithCreatedAndModifiedDate); + assertNotNull(result); + assertEquals("@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-7DAYS TO NOW/DAY+1DAY]", result); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[2014-05-30 TO 2014-05-31]")); + result = helper.createFacetQueriesFromSearchQuery(fields[1], searchQueryWithCreatedAndModifiedDate); + assertNotNull(result); + assertEquals("@{http://www.alfresco.org/model/content/1.0}modified:[NOW/DAY-1DAY TO NOW/DAY+1DAY]", result); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[0 TO 10240]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[10240 TO 102400]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[102400 TO 1048576]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[1048576 TO 16777216]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[16777216 TO 134217728]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[134217728 TO MAX]")); } /** - * Run the Set getBucketedFieldFacets() method test. + * Run the List getBucketedFieldFacets() method test. * * @throws Exception * @@ -180,29 +143,21 @@ public class SolrFacetHelperTest public void testGetDefaultFacetQueries() throws Exception { List result = helper.getDefaultFacetQueries(); - - LocalDate currentDate = LocalDate.now(); - String nowStr = " TO " + currentDate.toString(); - String yesterday = currentDate.minusDays(1).toString() + nowStr; - String lastWeek = currentDate.minusWeeks(1).toString() + nowStr; - String lastMonth = currentDate.minusMonths(1).toString() + nowStr; - String last6Months =currentDate.minusMonths(6).toString() + nowStr; - String lastYear = currentDate.minusYears(1).toString() + nowStr; - + assertNotNull(result); assertEquals(16, result.size()); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[" + yesterday + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[" + lastWeek + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[" + lastMonth + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[" + last6Months + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[" + lastYear + "]")); - - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + yesterday + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + lastWeek + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + lastMonth + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + last6Months + "]")); - assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[" + lastYear + "]")); - + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-1DAY TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-7DAYS TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-1MONTH TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-6MONTHS TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}created:[NOW/DAY-1YEAR TO NOW/DAY+1DAY]")); + + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[NOW/DAY-1DAY TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[NOW/DAY-7DAYS TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[NOW/DAY-1MONTH TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[NOW/DAY-6MONTHS TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}modified:[NOW/DAY-1YEAR TO NOW/DAY+1DAY]")); + assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[0 TO 10240]")); assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[10240 TO 102400]")); assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[102400 TO 1048576]")); @@ -210,18 +165,16 @@ public class SolrFacetHelperTest assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[16777216 TO 134217728]")); assertTrue(result.contains("@{http://www.alfresco.org/model/content/1.0}content.size:[134217728 TO MAX]")); } - + /** - * Run the SolrFacetHelper.FacetLabelDisplayHandler getDisplayHandler(String) method test. + * User name display handler test. * * @throws Exception - * */ @Test public void testGetUserNameDisplayHandler() throws Exception { - // Username handler - SolrFacetHelper.FacetLabelDisplayHandler userNameHandler = helper.getDisplayHandler("@{http://www.alfresco.org/model/content/1.0}creator.__.u"); + FacetLabelDisplayHandler userNameHandler = displayHandlerRegistry.getDisplayHandler("@{http://www.alfresco.org/model/content/1.0}creator"); assertNotNull(userNameHandler); String randomUserName = "randomUserName" + System.currentTimeMillis(); FacetLabel name = userNameHandler.getDisplayLabel(randomUserName); @@ -231,9 +184,9 @@ public class SolrFacetHelperTest assertNotNull(name); assertEquals("Expected admin's full name.", "Administrator", name.getLabel()); } - + /** - * Run the SolrFacetHelper.FacetLabelDisplayHandler getDisplayHandler(String) method test. + * MimeType display handler test. * * @throws Exception * @@ -241,8 +194,8 @@ public class SolrFacetHelperTest @Test public void testGetMimetypeDisplayHandler() throws Exception { - // Mimetype handler - SolrFacetHelper.FacetLabelDisplayHandler mimeTypeHandler = helper.getDisplayHandler("@{http://www.alfresco.org/model/content/1.0}content.mimetype"); + // Mimetype handler + FacetLabelDisplayHandler mimeTypeHandler = displayHandlerRegistry.getDisplayHandler("@{http://www.alfresco.org/model/content/1.0}content.mimetype"); assertNotNull(mimeTypeHandler); FacetLabel mimetype = mimeTypeHandler.getDisplayLabel("someMimetype123"); assertNotNull(mimetype); @@ -251,56 +204,48 @@ public class SolrFacetHelperTest assertNotNull(mimetype); assertEquals("Expected [text/plain] display name.", "Plain Text", mimetype.getLabel()); } - + /** - * Run the SolrFacetHelper.FacetLabelDisplayHandler getDisplayHandler(String) method test. + * Created date buckets display handler test. * * @throws Exception * */ @Test - public void testGetDateBucketsDisplayHandler() throws Exception + public void testGetCreatedDateBucketsDisplayHandler() throws Exception { - // Date buckets handler - SolrFacetHelper.FacetLabelDisplayHandler dateBucketeHandler = helper.getDisplayHandler("@{http://www.alfresco.org/model/content/1.0}created"); + final String createdDateField = "@{http://www.alfresco.org/model/content/1.0}created"; + FacetLabelDisplayHandler dateBucketeHandler = displayHandlerRegistry.getDisplayHandler(createdDateField); assertNotNull(dateBucketeHandler); - - LocalDate currentDate = LocalDate.now(); - String nowStr = " TO " + currentDate.toString(); - String yesterday = '[' + currentDate.minusDays(1).toString() + nowStr + ']'; - String lastWeek = '[' + currentDate.minusWeeks(1).toString() + nowStr + ']'; - String lastMonth = '[' + currentDate.minusMonths(1).toString() + nowStr + ']'; - String last6Months = '[' + currentDate.minusMonths(6).toString() + nowStr + ']'; - String lastYear = '[' + currentDate.minusYears(1).toString() + nowStr + ']'; - - FacetLabel dateLabel = dateBucketeHandler.getDisplayLabel(yesterday); + + FacetLabel dateLabel = dateBucketeHandler.getDisplayLabel(createdDateField+":[NOW/DAY-1DAY TO NOW/DAY+1DAY]"); assertNotNull(dateLabel); assertEquals("faceted-search.date.one-day.label", dateLabel.getLabel()); assertEquals("Yesterday date bucket should have a sorting index of 0.", 0, dateLabel.getLabelIndex()); - - dateLabel = dateBucketeHandler.getDisplayLabel(lastWeek); + + dateLabel = dateBucketeHandler.getDisplayLabel(createdDateField+":[NOW/DAY-7DAYS TO NOW/DAY+1DAY]"); assertNotNull(dateLabel); assertEquals("faceted-search.date.one-week.label", dateLabel.getLabel()); assertEquals("Last week date bucket should have a sorting index of 1.", 1, dateLabel.getLabelIndex()); - - dateLabel = dateBucketeHandler.getDisplayLabel(lastMonth); + + dateLabel = dateBucketeHandler.getDisplayLabel(createdDateField+":[NOW/DAY-1MONTH TO NOW/DAY+1DAY]"); assertNotNull(dateLabel); assertEquals("faceted-search.date.one-month.label", dateLabel.getLabel()); assertEquals("Last month date bucket should have a sorting index of 2.", 2, dateLabel.getLabelIndex()); - - dateLabel = dateBucketeHandler.getDisplayLabel(last6Months); + + dateLabel = dateBucketeHandler.getDisplayLabel(createdDateField+":[NOW/DAY-6MONTHS TO NOW/DAY+1DAY]"); assertNotNull(dateLabel); assertEquals("faceted-search.date.six-months.label", dateLabel.getLabel()); assertEquals("Last 6 months date bucket should have a sorting index of 3.", 3, dateLabel.getLabelIndex()); - - dateLabel = dateBucketeHandler.getDisplayLabel(lastYear); + + dateLabel = dateBucketeHandler.getDisplayLabel(createdDateField+":[NOW/DAY-1YEAR TO NOW/DAY+1DAY]"); assertNotNull(dateLabel); assertEquals("faceted-search.date.one-year.label", dateLabel.getLabel()); assertEquals("Last year date bucket should have a sorting index of 4.", 4, dateLabel.getLabelIndex()); } - + /** - * Run the SolrFacetHelper.FacetLabelDisplayHandler getDisplayHandler(String) method test. + * Content size buckets display handler test. * * @throws Exception * @@ -308,10 +253,10 @@ public class SolrFacetHelperTest @Test public void testGetContentSizeBucketsDisplayHandler() throws Exception { - // Date buckets handler - SolrFacetHelper.FacetLabelDisplayHandler contentSizeBucketeHandler = helper.getDisplayHandler("@{http://www.alfresco.org/model/content/1.0}content.size"); + final String contentSizeField = "@{http://www.alfresco.org/model/content/1.0}content.size"; + FacetLabelDisplayHandler contentSizeBucketeHandler = displayHandlerRegistry.getDisplayHandler(contentSizeField); assertNotNull(contentSizeBucketeHandler); - + int KB = 1024; int MB = KB * 1024; int tiny = 10 * KB; @@ -320,34 +265,56 @@ public class SolrFacetHelperTest int large = 16 * MB; int huge = 128 * MB; - FacetLabel sizeLabel = contentSizeBucketeHandler.getDisplayLabel("[0 TO " + tiny + "]"); - assertNotNull(sizeLabel); + + FacetLabel sizeLabel = contentSizeBucketeHandler.getDisplayLabel(contentSizeField + ":[0 TO " + tiny + "]"); + assertNotNull(sizeLabel); assertEquals("faceted-search.size.0-10KB.label", sizeLabel.getLabel()); assertEquals("0-10KB size bucket should have a sorting index of 0.", 0, sizeLabel.getLabelIndex()); - - sizeLabel = contentSizeBucketeHandler.getDisplayLabel("[" + tiny + " TO " + small + "]"); + + sizeLabel = contentSizeBucketeHandler.getDisplayLabel(contentSizeField + ":[" + tiny + " TO " + small + "]"); assertNotNull(sizeLabel); assertEquals("faceted-search.size.10-100KB.label", sizeLabel.getLabel()); assertEquals("10-100KB size bucket should have a sorting index of 1.", 1, sizeLabel.getLabelIndex()); - - sizeLabel = contentSizeBucketeHandler.getDisplayLabel("[" + small + " TO " + medium + "]"); + + sizeLabel = contentSizeBucketeHandler.getDisplayLabel(contentSizeField + ":[" + small + " TO " + medium + "]"); assertNotNull(sizeLabel); assertEquals("faceted-search.size.100KB-1MB.label", sizeLabel.getLabel()); assertEquals("100KB-1MB size bucket should have a sorting index of 2.", 2, sizeLabel.getLabelIndex()); - - sizeLabel = contentSizeBucketeHandler.getDisplayLabel("[" + medium + " TO " + large + "]"); + + sizeLabel = contentSizeBucketeHandler.getDisplayLabel(contentSizeField + ":[" + medium + " TO " + large + "]"); assertNotNull(sizeLabel); assertEquals("faceted-search.size.1-16MB.label", sizeLabel.getLabel()); assertEquals("1-16MB size bucket should have a sorting index of 3.", 3, sizeLabel.getLabelIndex()); - - sizeLabel = contentSizeBucketeHandler.getDisplayLabel("[" + large + " TO " + huge + "]"); + + sizeLabel = contentSizeBucketeHandler.getDisplayLabel(contentSizeField + ":[" + large + " TO " + huge + "]"); assertNotNull(sizeLabel); assertEquals("faceted-search.size.16-128MB.label", sizeLabel.getLabel()); assertEquals("16-128MB size bucket should have a sorting index of 4.", 4, sizeLabel.getLabelIndex()); - - sizeLabel = contentSizeBucketeHandler.getDisplayLabel("[" + huge + " TO MAX]"); + + sizeLabel = contentSizeBucketeHandler.getDisplayLabel(contentSizeField + ":[" + huge + " TO MAX]"); assertNotNull(sizeLabel); assertEquals("faceted-search.size.over128.label", sizeLabel.getLabel()); assertEquals("over128MB size bucket should have a sorting index of 5.", 5, sizeLabel.getLabelIndex()); } -} \ No newline at end of file + + /** + * Site title display handler test. + * + * @throws Exception + */ + @Test + public void testGetSiteTitleDisplayHandler() throws Exception + { + String defaultSiteName= "swsdp"; + FacetLabelDisplayHandler siteHandler = displayHandlerRegistry.getDisplayHandler("SITE"); + assertNotNull(siteHandler); + + String randomSiteName = "randomSiteName" + System.currentTimeMillis(); + FacetLabel name = siteHandler.getDisplayLabel(randomSiteName); + assertNotNull(name); + assertEquals("There is no site with the name [" + randomSiteName + "], hence, the handler should return the passed-in short name.", randomSiteName, name.getLabel()); + name = siteHandler.getDisplayLabel(defaultSiteName); + assertNotNull(name); + assertEquals("Sample: Web Site Design Project", name.getLabel()); + } +} diff --git a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java index 0613a2a63f..32f7a6b21a 100644 --- a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java +++ b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java @@ -37,7 +37,7 @@ public class SolrFacetTestSuite extends TestSuite public static Test suite() { TestSuite suite = new TestSuite(); - suite.addTest(new JUnit4TestAdapter(SolrFacetHelperTest.class)); + suite.addTest(new JUnit4TestAdapter(SolrFacetQueriesDisplayHandlersTest.class)); suite.addTest(new JUnit4TestAdapter(SolrFacetServiceImplTest.class)); suite.addTest(new JUnit4TestAdapter(SolrFacetConfigTest.class)); diff --git a/source/test-resources/facets/extension/facets-config-custom-sample.properties b/source/test-resources/facets/extension/facets-config-custom-sample.properties index 805e69cdbc..58dd18beba 100644 --- a/source/test-resources/facets/extension/facets-config-custom-sample.properties +++ b/source/test-resources/facets/extension/facets-config-custom-sample.properties @@ -1,15 +1,15 @@ # Overrides test_filter_creator in the /facets/facets-config-sample.properties -custom.cm\:creator.__.u.filterID=test_filter_creator -custom.cm\:creator.__.u.displayName=faceted-search.facet-menu.facet.creator -custom.cm\:creator.__.u.displayControl=alfresco/search/FacetFilters -custom.cm\:creator.__.u.maxFilters=10 -custom.cm\:creator.__.u.hitThreshold=5 -custom.cm\:creator.__.u.minFilterValueLength=14 -custom.cm\:creator.__.u.sortBy=ALPHABETICALLY -custom.cm\:creator.__.u.scope=ALL -custom.cm\:creator.__.u.scopedSites=site1 -custom.cm\:creator.__.u.index=0 -custom.cm\:creator.__.u.isEnabled=true +custom.cm\:creator.filterID=test_filter_creator +custom.cm\:creator.displayName=faceted-search.facet-menu.facet.creator +custom.cm\:creator.displayControl=alfresco/search/FacetFilters +custom.cm\:creator.maxFilters=10 +custom.cm\:creator.hitThreshold=5 +custom.cm\:creator.minFilterValueLength=14 +custom.cm\:creator.sortBy=ALPHABETICALLY +custom.cm\:creator.scope=ALL +custom.cm\:creator.scopedSites=site1 +custom.cm\:creator.index=0 +custom.cm\:creator.isEnabled=true # Add a new Filter # Field-Facet-Qname => cm:description.__ diff --git a/source/test-resources/facets/facets-config-sample.properties b/source/test-resources/facets/facets-config-sample.properties index 4a160172dd..abb50e35bd 100644 --- a/source/test-resources/facets/facets-config-sample.properties +++ b/source/test-resources/facets/facets-config-sample.properties @@ -1,26 +1,26 @@ -# Field-Facet-Qname => cm:creator.__.u -default.cm\:creator.__.u.filterID=test_filter_creator -default.cm\:creator.__.u.displayName=faceted-search.facet-menu.facet.creator -default.cm\:creator.__.u.displayControl=alfresco/search/FacetFilters -default.cm\:creator.__.u.maxFilters=5 -default.cm\:creator.__.u.hitThreshold=1 -default.cm\:creator.__.u.minFilterValueLength=4 -default.cm\:creator.__.u.sortBy=ALPHABETICALLY -default.cm\:creator.__.u.scope=ALL -default.cm\:creator.__.u.scopedSites= -default.cm\:creator.__.u.isEnabled=true +# Field-Facet-Qname => cm:creator +default.cm\:creator.filterID=test_filter_creator +default.cm\:creator.displayName=faceted-search.facet-menu.facet.creator +default.cm\:creator.displayControl=alfresco/search/FacetFilters +default.cm\:creator.maxFilters=5 +default.cm\:creator.hitThreshold=1 +default.cm\:creator.minFilterValueLength=4 +default.cm\:creator.sortBy=ALPHABETICALLY +default.cm\:creator.scope=ALL +default.cm\:creator.scopedSites= +default.cm\:creator.isEnabled=true -# Field-Facet-Qname => cm:modifier.__.u -default.cm\:modifier.__.u.filterID=test_filter_modifier -default.cm\:modifier.__.u.displayName=faceted-search.facet-menu.facet.modifier -default.cm\:modifier.__.u.displayControl=alfresco/search/FacetFilters -default.cm\:modifier.__.u.maxFilters=5 -default.cm\:modifier.__.u.hitThreshold=1 -default.cm\:modifier.__.u.minFilterValueLength=4 -default.cm\:modifier.__.u.sortBy=ALPHABETICALLY -default.cm\:modifier.__.u.scope=SCOPED_SITES -default.cm\:modifier.__.u.scopedSites= -default.cm\:modifier.__.u.isEnabled=true +# Field-Facet-Qname => cm:modifier +default.cm\:modifier.filterID=test_filter_modifier +default.cm\:modifier.displayName=faceted-search.facet-menu.facet.modifier +default.cm\:modifier.displayControl=alfresco/search/FacetFilters +default.cm\:modifier.maxFilters=5 +default.cm\:modifier.hitThreshold=1 +default.cm\:modifier.minFilterValueLength=4 +default.cm\:modifier.sortBy=ALPHABETICALLY +default.cm\:modifier.scope=SCOPED_SITES +default.cm\:modifier.scopedSites= +default.cm\:modifier.isEnabled=true # Field-Facet-Qname => cm:content.size default.cm\:content.size.filterID=test_filter_content_size