diff --git a/source/java/org/alfresco/cmis/search/CMISResultSetImpl.java b/source/java/org/alfresco/cmis/search/CMISResultSetImpl.java index 66c88887f5..d6f725aeab 100644 --- a/source/java/org/alfresco/cmis/search/CMISResultSetImpl.java +++ b/source/java/org/alfresco/cmis/search/CMISResultSetImpl.java @@ -348,4 +348,10 @@ public class CMISResultSetImpl implements CMISResultSet, Serializable } throw new IllegalStateException(); } + + @Override + public Map getFacetQueries() + { + return Collections.emptyMap(); + } } diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSet.java b/source/java/org/alfresco/opencmis/search/CMISResultSet.java index 6ccc108316..ac85d77e6f 100644 --- a/source/java/org/alfresco/opencmis/search/CMISResultSet.java +++ b/source/java/org/alfresco/opencmis/search/CMISResultSet.java @@ -353,4 +353,10 @@ public class CMISResultSet implements ResultSetSPI getFacetQueries() + { + return Collections.emptyMap(); + } } diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 3f2e10228b..3607c2d4a9 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -26,14 +26,17 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; 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.FacetLabelDisplayHandler; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentReader; -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.repository.StoreRef; @@ -48,6 +51,7 @@ import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.util.ISO9075; import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Document; @@ -56,6 +60,7 @@ import org.dom4j.io.SAXReader; import org.jaxen.saxpath.base.XPathReader; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; +import org.springframework.beans.factory.InitializingBean; import org.springframework.extensions.surf.util.ParameterCheck; /** @@ -71,7 +76,7 @@ import org.springframework.extensions.surf.util.ParameterCheck; * * @author Kevin Roast */ -public class Search extends BaseScopableProcessorExtension +public class Search extends BaseScopableProcessorExtension implements InitializingBean { private static Log logger = LogFactory.getLog(Search.class); @@ -85,7 +90,18 @@ public class Search extends BaseScopableProcessorExtension protected Repository repository; private SwitchableApplicationContextFactory searchSubsystem; + + /** Solr facet helper */ + private SolrFacetHelper solrFacetHelper; + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "services", services); + + this.solrFacetHelper = new SolrFacetHelper(services); + } /** * Set the default store reference * @@ -694,6 +710,12 @@ public class Search extends BaseScopableProcessorExtension { sp.addFieldFacet(new FieldFacet("@" + field)); } + + List facetQueries = solrFacetHelper.getFacetQueries(); + for (String fq : facetQueries) + { + sp.addFacetQuery(fq); + } } // error handling opions @@ -905,15 +927,47 @@ public class Search extends BaseScopableProcessorExtension if (f.getSecond() > 0) { String facetValue = f.getFirst(); - Field field = getFieldType(ff.getField()); - String label = (field == null) ? facetValue : field.getLabel(services, facetValue); + FacetLabelDisplayHandler handler = solrFacetHelper.getDisplayHandler(ff.getField()); + String label = (handler == null) ? facetValue : handler.getDisplayLabel(facetValue).getSecond(); + facets.add(new ScriptFacetResult(facetValue, label, f.getSecond())); } } // store facet results per field facetMeta.put(ff.getField(), facets); + } + + Set> facetQueries = results.getFacetQueries().entrySet(); + Map> facetQueryMeta = new HashMap<>(facetQueries.size()); + for(Entry entry : facetQueries) + { + // ignore zero hit facet queries + 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] + // qName => @{http://www.alfresco.org/model/content/1.0}created + // 7 => {!afts} + String qName = key.substring(7, key.lastIndexOf(':')); + + // Retrieve the previous facet queries + List fqs = facetQueryMeta.get(qName); + if (fqs == null) + { + fqs = new ArrayList<>(); + } + FacetLabelDisplayHandler handler = solrFacetHelper.getDisplayHandler(qName); + Pair valueLabelPair = (handler == null) ? new Pair(qName, + key.substring(qName.length(), key.length())) : handler.getDisplayLabel(key); + + fqs.add(new ScriptFacetResult(valueLabelPair.getFirst(), valueLabelPair.getSecond(), entry.getValue())); + + // store facet query results per field + facetQueryMeta.put(qName, fqs); + } } meta.put("facets", facetMeta); + meta.put("facetQueries", facetQueryMeta); } catch (Throwable err) { @@ -961,83 +1015,4 @@ public class Search extends BaseScopableProcessorExtension public String column; public boolean asc; } - - /** - * @author Jamal Kaabi-Mofrad - */ - private enum Field - { - CREATOR("creator.__"), MODIFIER("modifier.__"), MIMETYPE("content.mimetype") - { - @Override - /*Package access level*/ - String getLabel(ServiceRegistry services, String facetValue) - { - MimetypeService mimetypeService = services.getMimetypeService(); - Map mimetypes = mimetypeService.getDisplaysByMimetype(); - String displayName = mimetypes.get(facetValue); - return displayName == null ? facetValue : displayName.trim(); - } - }; - - private Field(String facetField) - { - this.facetField = facetField; - } - - private String facetField; - - private String getFacetField() - { - return facetField; - } - - /** - * Default implementation which will return the full user name from - * the facetValue, if the facetValue represent a userID - * - * @param services the ServiceRegistry - * @param facetValue the facet value - * @return the full user name. If the user doesn't exist then, the - * {@code facetValue} will be returned. - */ - /*Package access level*/ - String getLabel(ServiceRegistry services, String facetValue) - { - String name = null; - - final NodeRef personRef = services.getPersonService().getPersonOrNull(facetValue); - if (personRef != null) - { - final NodeService nodeService = services.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 name == null ? facetValue : name.trim(); - } - } - - /** - * Gets the facet field. - * - * @param facetField the facet field value - * @return the Field type - */ - private Field getFieldType(String facetField) - { - if (facetField == null) - { - return null; - } - - for (Field val : Field.values()) - { - if (facetField.endsWith(val.getFacetField())) - { - return val; - } - } - return null; - } } diff --git a/source/java/org/alfresco/repo/search/AbstractResultSet.java b/source/java/org/alfresco/repo/search/AbstractResultSet.java index ea8ba380ec..32401c59c0 100644 --- a/source/java/org/alfresco/repo/search/AbstractResultSet.java +++ b/source/java/org/alfresco/repo/search/AbstractResultSet.java @@ -21,6 +21,7 @@ package org.alfresco.repo.search; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -121,5 +122,10 @@ public abstract class AbstractResultSet implements ResultSet return Collections.>emptyList(); } + @Override + public Map getFacetQueries() + { + return Collections.emptyMap(); + } } diff --git a/source/java/org/alfresco/repo/search/EmptyResultSet.java b/source/java/org/alfresco/repo/search/EmptyResultSet.java index 40279617d6..24df18745d 100644 --- a/source/java/org/alfresco/repo/search/EmptyResultSet.java +++ b/source/java/org/alfresco/repo/search/EmptyResultSet.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -164,4 +165,10 @@ public class EmptyResultSet implements ResultSet { return 0; } + + @Override + public Map getFacetQueries() + { + return Collections.emptyMap(); + } } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java b/source/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java index 169377a1a8..f5646e89b1 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.alfresco.repo.search.impl.querymodel.Query; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -250,4 +251,10 @@ public class PagingLuceneResultSet implements ResultSet, Serializable { return wrapped.getNumberFound(); } + + @Override + public Map getFacetQueries() + { + return wrapped.getFacetQueries(); + } } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java b/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java index 3c8452ad5c..8fd7018258 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.search.SimpleResultSetMetaData; @@ -72,6 +73,8 @@ public class SolrJSONResultSet implements ResultSet private HashMap>> fieldFacets = new HashMap>>(1); + private Map facetQueries = new HashMap(); + private NodeDAO nodeDao; private long lastIndexedTxId; @@ -143,6 +146,16 @@ public class SolrJSONResultSet implements ResultSet if(json.has("facet_counts")) { JSONObject facet_counts = json.getJSONObject("facet_counts"); + if(facet_counts.has("facet_queries")) + { + JSONObject facet_queries = facet_counts.getJSONObject("facet_queries"); + for(Iterator it = facet_queries.keys(); it.hasNext(); /**/) + { + String fq = (String) it.next(); + Integer count =Integer.parseInt(facet_queries.getString(fq)); + facetQueries.put(fq, count); + } + } if(facet_counts.has("facet_fields")) { JSONObject facet_fields = facet_counts.getJSONObject("facet_fields"); @@ -391,4 +404,10 @@ public class SolrJSONResultSet implements ResultSet { return lastIndexedTxId; } + + @Override + public Map getFacetQueries() + { + return Collections.unmodifiableMap(facetQueries); + } } diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 7edfd4218b..7f6a6fd9b8 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -348,6 +347,10 @@ public class SolrQueryHTTPClient implements BeanFactoryAware } } + for(String facetQuery : searchParameters.getFacetQueries()) + { + url.append("&facet.query=").append(encoder.encode("{!afts}"+facetQuery, "UTF-8")); + } } // end of field factes 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 new file mode 100644 index 0000000000..e7e3542a0f --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetHelper.java @@ -0,0 +1,531 @@ +/* + * 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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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 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.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 + * + * @author Jamal Kaabi-Mofrad + */ +// 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_FACET_QUERY_PREFIX = FQ_NS_PREFIX + "created:"; + private static final String MODIFIED_FACET_QUERY_PREFIX = FQ_NS_PREFIX + "modified:"; + private static final String CONTENT_SIZE_FACET_QUERY_PREFIX = FQ_NS_PREFIX + "content.size:"; + + // 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"; + + /** 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) + { + this.fqDateCache = new BucketsCache<>(new FacetQueryParamDateBuckets()); + this.displayHandlers = new HashMap<>(6); + + UserNameDisplayHandler userNameDisplayHandler = new UserNameDisplayHandler(serviceRegistry); + MimetypeDisplayHandler mimetypeDisplayHandler = new MimetypeDisplayHandler(serviceRegistry); + DateBucketsDisplayHandler createdDateBucketsDisplayHandler = new DateBucketsDisplayHandler("cm:created"); + DateBucketsDisplayHandler modifiedDateBucketsDisplayHandler = new DateBucketsDisplayHandler("cm:modified"); + ContentSizeBucketsDisplayHandler contentSizeBucketsDisplayHandler = new ContentSizeBucketsDisplayHandler("cm:content.size"); + + this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}creator.__", userNameDisplayHandler); + this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}modifier.__", userNameDisplayHandler); + this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}content.mimetype", mimetypeDisplayHandler); + this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}created", createdDateBucketsDisplayHandler); + this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}modified", modifiedDateBucketsDisplayHandler); + this.displayHandlers.put("@{http://www.alfresco.org/model/content/1.0}content.size", contentSizeBucketsDisplayHandler); + } + + /** + * Set the facet value and facet query display label handlers. + * + * @param displayHandlers the displayHandlers to set + */ + public void setDisplayHandlers(Map displayHandlers) + { + this.displayHandlers = displayHandlers; + } + + /** + * Gets 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 getFacetQueries() + { + List facetQueries = new ArrayList<>(); + 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()); + } + + // Created and Modified dates facet queries + for (String bucket : dateBuckets) + { + facetQueries.add(CREATED_FACET_QUERY_PREFIX + '[' + bucket + ']'); + facetQueries.add(MODIFIED_FACET_QUERY_PREFIX + '[' + bucket + ']'); + } + + // Content size facet query + for (String bucket : makeContentSizeBuckets()) + { + facetQueries.add(CONTENT_SIZE_FACET_QUERY_PREFIX + '[' + bucket + ']'); + } + + return facetQueries; + } + + /** + * Gets the appropriate facet display label handler + * + * @param qName + * @return the diplayHandler object or null if there is no handler + * registered for the given @{code qName} + */ + public FacetLabelDisplayHandler getDisplayHandler(String qName) + { + return displayHandlers.get(qName); + } + + /** + * Creates Date buckets. The dates are in ISO8601 format (yyyy-MM-dd) + * + * @return list of date ranges. e.g. "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), "faceted-search.date.one-day.label"); + bucketDisplayName.put(dateBuckets.get(1), "faceted-search.date.one-week.label"); + bucketDisplayName.put(dateBuckets.get(2), "faceted-search.date.one-month.label"); + bucketDisplayName.put(dateBuckets.get(3), "faceted-search.date.six-months.label"); + bucketDisplayName.put(dateBuckets.get(4), "faceted-search.date.one-year.label"); + + return bucketDisplayName; + } + + /** + * Creates Content size buckets + * + * @return list of size ranges. e.g. "0 TO 1024" + */ + private static List makeContentSizeBuckets() + { + List sizeBuckets = new ArrayList<>(6); + sizeBuckets.add("0 TO " + TINY); + sizeBuckets.add(TINY + " TO " + SMALL); + sizeBuckets.add(SMALL + " TO " + MEDIUM); + sizeBuckets.add(MEDIUM + " TO " + LARGE); + sizeBuckets.add(LARGE + " TO " + HUGE); + sizeBuckets.add(HUGE + " TO MAX"); + + return sizeBuckets; + } + + /** + * Creates display name for the Content size buckets. + * + * @return Map of {@literal } + */ + private static Map makeContentSizeBucketsDisplayLabel() + { + List sizeBuckets = makeContentSizeBuckets(); + Map bucketDisplayName = new HashMap<>(6); + + if (sizeBuckets.size() != 6) + { + throw new AlfrescoRuntimeException("Content size buckets size does not match the bucket display label size!"); + } + + bucketDisplayName.put(sizeBuckets.get(0), "faceted-search.size.0-10KB.label"); + bucketDisplayName.put(sizeBuckets.get(1), "faceted-search.size.10-100KB.label"); + bucketDisplayName.put(sizeBuckets.get(2), "faceted-search.size.100KB-1MB.label"); + bucketDisplayName.put(sizeBuckets.get(3), "faceted-search.size.1-16MB.label"); + bucketDisplayName.put(sizeBuckets.get(4), "faceted-search.size.16-128MB.label"); + bucketDisplayName.put(sizeBuckets.get(5), "faceted-search.size.over128.label"); + + 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) + { + 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 + { + Pair getDisplayLabel(String value); + } + + /** + * 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 Pair 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 Pair(value, name == null ? value : name.trim()); + } + } + + /** + * 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 Pair getDisplayLabel(String value) + { + Map mimetypes = mimetypeService.getDisplaysByMimetype(); + String displayName = mimetypes.get(value); + return new Pair(value, displayName == null ? value : displayName.trim()); + } + } + + /** + * 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()); + private final String fq; + + public DateBucketsDisplayHandler(String fq) + { + this.fq = fq; + } + + @Override + public Pair 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 = fq + ":\"" + lowerUpperDates[0] + "\"..\"" + lowerUpperDates[1] + '"'; + String label = dateBuckets.get(dateRange); + return new Pair(newValue, label); + } + } + + /** + * 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()); + private final String fq; + + public ContentSizeBucketsDisplayHandler(String fq) + { + this.fq = fq; + } + + @Override + public Pair 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 = fq + ":\"" + lowerUppperSize[0] + "\"..\"" + lowerUppperSize[1] + '"'; + String label = sizeBuckets.get(sizeRange); + return new Pair(newValue, label); + } + } +} diff --git a/source/java/org/alfresco/repo/search/results/ResultSetSPIWrapper.java b/source/java/org/alfresco/repo/search/results/ResultSetSPIWrapper.java index cd6f31ddfb..339cfce3ca 100644 --- a/source/java/org/alfresco/repo/search/results/ResultSetSPIWrapper.java +++ b/source/java/org/alfresco/repo/search/results/ResultSetSPIWrapper.java @@ -20,6 +20,7 @@ package org.alfresco.repo.search.results; import java.util.Iterator; import java.util.List; +import java.util.Map; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -156,6 +157,12 @@ public class ResultSetSPIWrapper getFacetQueries() + { + return wrapped.getFacetQueries(); + } + private static class WrappedIterator implements Iterator { private Iterator wrapped; diff --git a/source/java/org/alfresco/repo/search/results/SortedResultSet.java b/source/java/org/alfresco/repo/search/results/SortedResultSet.java index 4e958ff657..61f4afb586 100644 --- a/source/java/org/alfresco/repo/search/results/SortedResultSet.java +++ b/source/java/org/alfresco/repo/search/results/SortedResultSet.java @@ -26,6 +26,7 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.search.impl.lucene.LuceneResultSetRow; @@ -932,5 +933,11 @@ public class SortedResultSet implements ResultSet { return resultSet.getNumberFound(); } + + @Override + public Map getFacetQueries() + { + return resultSet.getFacetQueries(); + } } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/FilteringResultSet.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/FilteringResultSet.java index cb08a69ff2..d0aa0b0cf4 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/FilteringResultSet.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/FilteringResultSet.java @@ -23,6 +23,7 @@ import java.util.BitSet; import java.util.Collections; import java.util.List; import java.util.ListIterator; +import java.util.Map; import org.alfresco.repo.search.ResultSetRowIterator; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -336,4 +337,10 @@ public class FilteringResultSet extends ACLEntryAfterInvocationProvider implemen { return inclusionMask.cardinality(); } + + @Override + public Map getFacetQueries() + { + return unfiltered.getFacetQueries(); + } }