diff --git a/config/alfresco/model/solrFacetModel.xml b/config/alfresco/model/solrFacetModel.xml
index 7ed3d04200..663f0a1c01 100644
--- a/config/alfresco/model/solrFacetModel.xml
+++ b/config/alfresco/model/solrFacetModel.xml
@@ -18,6 +18,8 @@
+
+
@@ -155,4 +157,18 @@
cm:folder
+
+
+
+
+ Facet Custom Properties
+
+
+ Additional Facet Information
+ d:text
+
+
+
+
+
\ 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 4c630e6571..050a1dcd5e 100644
--- a/config/alfresco/subsystems/Search/solr/facet/solr-facets-config.properties
+++ b/config/alfresco/subsystems/Search/solr/facet/solr-facets-config.properties
@@ -12,7 +12,7 @@ default.cm\:content.mimetype.hitThreshold=1
default.cm\:content.mimetype.minFilterValueLength=4
default.cm\:content.mimetype.sortBy=DESCENDING
default.cm\:content.mimetype.scope=SCOPED_SITES
-default.cm\:content.mimetype.scopedSites=site1,site2,site3
+default.cm\:content.mimetype.scopedSites=
default.cm\:content.mimetype.index=0
default.cm\:content.mimetype.isEnabled=true
@@ -25,7 +25,7 @@ default.cm\:description.__.hitThreshold=1
default.cm\:description.__.minFilterValueLength=4
default.cm\:description.__.sortBy=DESCENDING
default.cm\:description.__.scope=SCOPED_SITES
-default.cm\:description.__.scopedSites=site1,site2,site3
+default.cm\:description.__.scopedSites=
default.cm\:description.__.index=1
default.cm\:description.__.isEnabled=true
@@ -38,7 +38,7 @@ default.cm\:creator.__.u.hitThreshold=1
default.cm\:creator.__.u.minFilterValueLength=4
default.cm\:creator.__.u.sortBy=ALPHABETICALLY
default.cm\:creator.__.u.scope=SCOPED_SITES
-default.cm\:creator.__.u.scopedSites=site1,site2,site3
+default.cm\:creator.__.u.scopedSites=
default.cm\:creator.__.u.index=2
default.cm\:creator.__.u.isEnabled=true
@@ -51,7 +51,7 @@ 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=site1,site2,site3
+default.cm\:modifier.__.u.scopedSites=
default.cm\:modifier.__.u.index=3
default.cm\:modifier.__.u.isEnabled=true
@@ -59,40 +59,40 @@ default.cm\:modifier.__.u.isEnabled=true
default.cm\:created.filterID=filter_created
default.cm\:created.displayName=faceted-search.facet-menu.facet.created
default.cm\:created.displayControl=alfresco/search/FacetFilters
-default.cm\:created.blockIncludeFacetRequest=true
default.cm\:created.maxFilters=5
default.cm\:created.hitThreshold=1
default.cm\:created.minFilterValueLength=4
default.cm\:created.sortBy=ALPHABETICALLY
default.cm\:created.scope=SCOPED_SITES
-default.cm\:created.scopedSites=site1,site2,site3
+default.cm\:created.scopedSites=
default.cm\:created.index=4
default.cm\:created.isEnabled=true
+default.cm\:created.EXTRA-PROP.blockIncludeFacetRequest=true
# Field-Facet-Qname => cm:modified
default.cm\:modified.filterID=filter_modified
default.cm\:modified.displayName=faceted-search.facet-menu.facet.modified
default.cm\:modified.displayControl=alfresco/search/FacetFilters
-default.cm\:modified.blockIncludeFacetRequest=true
default.cm\:modified.maxFilters=5
default.cm\:modified.hitThreshold=1
default.cm\:modified.minFilterValueLength=4
default.cm\:modified.sortBy=ALPHABETICALLY
default.cm\:modified.scope=SCOPED_SITES
-default.cm\:modified.scopedSites=site1,site2,site3
+default.cm\:modified.scopedSites=
default.cm\:modified.index=5
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
default.cm\:content.size.displayName=faceted-search.facet-menu.facet.size
default.cm\:content.size.displayControl=alfresco/search/FacetFilters
-default.cm\:content.size.blockIncludeFacetRequest=true
default.cm\:content.size.maxFilters=5
default.cm\:content.size.hitThreshold=1
default.cm\:content.size.minFilterValueLength=4
default.cm\:content.size.sortBy=ALPHABETICALLY
default.cm\:content.size.scope=SCOPED_SITES
-default.cm\:content.size.scopedSites=site1,site2,site3
+default.cm\:content.size.scopedSites=
default.cm\:content.size.index=6
default.cm\:content.size.isEnabled=true
+default.cm\:content.size.EXTRA-PROP.blockIncludeFacetRequest=true
diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java
index 1c4f1a2a54..73ad83c73c 100644
--- a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java
+++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java
@@ -19,15 +19,19 @@
package org.alfresco.repo.search.impl.solr.facet;
+import java.io.Serializable;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties.CustomProperties;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.PropertyCheck;
@@ -47,6 +51,7 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
*
* - custom.cm\:content.mimetype.filterID=filter_abc
* - custom.cm\:content.mimetype.displayName=faceted-search.facet-menu.facet.formats
+ * - custom.cm\:content.mimetype.displayControl=alfresco/search/FacetFilters
* - custom.cm\:content.mimetype.maxFilters=5
* - custom.cm\:content.mimetype.hitThreshold=1
* - custom.cm\:content.mimetype.minFilterValueLength=4
@@ -56,6 +61,12 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
* - custom.cm\:content.mimetype.index=0
* - custom.cm\:content.mimetype.isEnabled=true
*
+ * Also, if there is a need to add additional properties, the following needs to be
+ * put into a properties file:
+ *
+ * - custom.cm\:content.mimetype.EXTRA-PROP.blockIncludeFacetRequest=true
+ * - custom.cm\:content.mimetype.EXTRA-PROP.moreProp=additionalInfo
+ *
* The inheritance order is strictly defined using property:
* ${solr_facets.inheritanceHierarchy}
* The default inheritance orders are:
@@ -69,6 +80,9 @@ public class SolrFacetConfig extends AbstractLifecycleBean
{
private static final Log logger = LogFactory.getLog(SolrFacetConfig.class);
+ private static final String KEY_EXTRA_INFO = ".EXTRA-PROP.";
+ private static final int KEY_EXTRA_INFO_LENGTH = KEY_EXTRA_INFO.length();
+
private final Properties rawProperties;
private final Set propInheritanceOrder;
@@ -163,15 +177,39 @@ public class SolrFacetConfig extends AbstractLifecycleBean
}
}
- Set facetFields = new HashSet<>();
+ Map> facetFields = new HashMap<>();
for(String key : propValues.keySet())
{
- facetFields.add(key.substring(0, key.lastIndexOf('.')));
+ String facetQName = null;
+ Set extraProp = null;
+ int index = key.indexOf(KEY_EXTRA_INFO);
+ if (index > 0)
+ {
+ String extraInfo = key.substring(index + KEY_EXTRA_INFO_LENGTH);
+ facetQName = key.substring(0, index);
+
+ extraProp = facetFields.get(facetQName);
+ if (extraProp == null)
+ {
+ extraProp = new HashSet<>();
+ }
+ if (extraInfo.length() > 0)
+ {
+ extraProp.add(extraInfo);
+ }
+ }
+ else
+ {
+ index = key.lastIndexOf('.');
+ facetQName = key.substring(0, index);
+ extraProp = facetFields.get(facetQName);
+ }
+ facetFields.put(facetQName, extraProp);
}
// Build the facet config objects
Map facetProperties = new HashMap<>(100);
- for (String field : facetFields)
+ for (String field : facetFields.keySet())
{
// FacetProperty attributes
// Resolve facet field into QName
@@ -186,7 +224,12 @@ public class SolrFacetConfig extends AbstractLifecycleBean
String scope = propValues.get(ValueName.PROP_SCOPE.getPropValueName(field));
Set scopedSites = getScopedSites(propValues.get(ValueName.PROP_SCOPED_SITES.getPropValueName(field)));
int index = getIntegerValue(propValues.get(ValueName.PROP_INDEX.getPropValueName(field)));
+ if(index < 0)
+ {
+ throw new SolrFacetConfigException("Index must be greater than or equal to 0");
+ }
boolean isEnabled = Boolean.valueOf(propValues.get(ValueName.PROP_IS_ENABLED.getPropValueName(field)));
+ Set customProps = getCustomProps(facetFields.get(field), field, propValues);
// Construct the FacetProperty object
SolrFacetProperties fp = new SolrFacetProperties.Builder()
@@ -202,8 +245,9 @@ public class SolrFacetConfig extends AbstractLifecycleBean
.index(index)
.isEnabled(isEnabled)
.isDefault(true)
- .scopedSites(scopedSites).build();
-
+ .scopedSites(scopedSites)
+ .customProperties(customProps).build();
+
facetProperties.put(filterID, fp);
}
@@ -283,6 +327,35 @@ public class SolrFacetConfig extends AbstractLifecycleBean
}
return set;
}
+
+ private static Set getCustomProps(Set additionalProps, String field, Map propValues)
+ {
+ if (additionalProps == null)
+ {
+ return Collections.emptySet();
+ }
+
+ Set customProps = new HashSet<>();
+ for (String extraInfo : additionalProps)
+ {
+ String value = propValues.get(field + KEY_EXTRA_INFO + extraInfo);
+ if (value != null)
+ {
+ QName qName = QName.createQName(SolrFacetModel.SOLR_FACET_CUSTOM_PROPERTY_URL, extraInfo);
+ String[] extra = value.split(",");
+ if (extra.length == 1)
+ {
+ customProps.add(new CustomProperties(qName, null, null, extra[0]));
+ }
+ else
+ {
+ List list = Arrays.asList(extra);
+ customProps.add(new CustomProperties(qName, null, null, (Serializable) list));
+ }
+ }
+ }
+ return customProps;
+ }
private static int getIntegerValue(String propValue)
{
diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetModel.java b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetModel.java
index 1e0d597262..2b3f27a4ff 100644
--- a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetModel.java
+++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetModel.java
@@ -31,8 +31,13 @@ public interface SolrFacetModel
public static final String SOLR_FACET_MODEL_URL = "http://www.alfresco.org/model/solrfacet/1.0";
public static final String PREFIX = "srft";
+ public static final String SOLR_FACET_CUSTOM_PROPERTY_URL = "http://www.alfresco.org/model/solrfacetcustomproperty/1.0";
+ public static final String SOLR_FACET_CUSTOM_PROPERTY_PREFIX = "srftcustom";
+
public static final QName TYPE_FACET_FIELD = QName.createQName(SOLR_FACET_MODEL_URL, "facetField");
+ public static final QName ASPECT_CUSTOM_PROPERTIES = QName.createQName(SOLR_FACET_MODEL_URL, "customProperties");
+
public static final QName PROP_FIELD_TYPE = QName.createQName(SOLR_FACET_MODEL_URL, "fieldType");
public static final QName PROP_FIELD_LABEL = QName.createQName(SOLR_FACET_MODEL_URL, "fieldLabel");
@@ -56,4 +61,6 @@ public interface SolrFacetModel
public static final QName PROP_IS_ENABLED = QName.createQName(SOLR_FACET_MODEL_URL, "isEnabled");
public static final QName PROP_IS_DEFAULT = QName.createQName(SOLR_FACET_MODEL_URL, "isDefault");
+
+ public static final QName PROP_EXTRA_INFORMATION = QName.createQName(SOLR_FACET_CUSTOM_PROPERTY_URL, "extraInformation");
}
diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetProperties.java b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetProperties.java
index b2f03fc997..49938973c6 100644
--- a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetProperties.java
+++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetProperties.java
@@ -19,20 +19,23 @@
package org.alfresco.repo.search.impl.solr.facet;
+import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.alfresco.service.namespace.QName;
+import org.alfresco.util.EqualsHelper;
/**
- * Domain-Specific Language (DSL) style builder class for encapsulating the
- * facet properties.
+ * Domain-Specific Language (DSL) style builder class for encapsulating the facet properties.
*
* @author Jamal Kaabi-Mofrad
*/
-public class SolrFacetProperties implements Comparable
+public class SolrFacetProperties implements Serializable
{
+ private static final long serialVersionUID = 2991173095752087202L;
+
private final String filterID;
private final QName facetQName;
private final String displayName;
@@ -46,6 +49,7 @@ public class SolrFacetProperties implements Comparable
private final int index;
private final boolean isEnabled;
private final boolean isDefault; // is loaded from properties files?
+ private final Set customProperties;
/**
* Initialises a newly created SolrFacetProperty
object
@@ -66,7 +70,8 @@ public class SolrFacetProperties implements Comparable
this.index = builder.index;
this.isEnabled = builder.isEnabled;
this.isDefault = builder.isDefault;
- this.scopedSites = (builder.scopedSites == null) ? null :Collections.unmodifiableSet(new HashSet(builder.scopedSites));
+ this.scopedSites = Collections.unmodifiableSet(new HashSet(builder.scopedSites));
+ this.customProperties = Collections.unmodifiableSet(new HashSet(builder.customProperties));
}
/**
@@ -142,16 +147,12 @@ public class SolrFacetProperties implements Comparable
}
/**
- * Returns an unmodifiable view of the Scoped Sites set or null
+ * Returns an unmodifiable view of the Scoped Sites set. Never null.
*
* @return the scopedSites
*/
public Set getScopedSites()
{
- if (this.scopedSites == null)
- {
- return null;
- }
return Collections.unmodifiableSet(new HashSet(this.scopedSites));
}
@@ -181,6 +182,17 @@ public class SolrFacetProperties implements Comparable
return this.isDefault;
}
+ /**
+ * Returns an unmodifiable view of the custom properties set. Never null.
+ *
+ * @return the customProperties
+ */
+ public Set getCustomProperties()
+ {
+ return Collections.unmodifiableSet(new HashSet(this.customProperties));
+ }
+
+
/*
* @see java.lang.Object#hashCode()
*/
@@ -226,22 +238,13 @@ public class SolrFacetProperties implements Comparable
return true;
}
- /*
- * @see java.lang.Comparable#compareTo(T)
- */
- @Override
- public int compareTo(SolrFacetProperties that)
- {
- return Integer.compare(this.index, that.index);
- }
-
/*
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
- StringBuilder sb = new StringBuilder(320);
+ StringBuilder sb = new StringBuilder(400);
sb.append("FacetProperty [filterID=").append(this.filterID).append(", facetQName=")
.append(this.facetQName).append(", displayName=").append(this.displayName)
.append(", displayControl=").append(this.displayControl).append(", maxFilters=")
@@ -249,7 +252,8 @@ public class SolrFacetProperties implements Comparable
.append(", minFilterValueLength=").append(this.minFilterValueLength).append(", sortBy=")
.append(this.sortBy).append(", scope=").append(this.scope).append(", scopedSites=")
.append(this.scopedSites).append(", index=").append(this.index).append(", isEnabled=").append(this.isEnabled)
- .append(", isDefault=").append(this.isDefault).append("]");
+ .append(", isDefault=").append(this.isDefault).append(", customProperties=").append(this.customProperties)
+ .append("]");
return sb.toString();
}
@@ -264,10 +268,38 @@ public class SolrFacetProperties implements Comparable
private int minFilterValueLength;
private String sortBy;
private String scope;
- private Set scopedSites;
+ private Set scopedSites = Collections.emptySet();
private int index;
private boolean isEnabled;
private boolean isDefault;
+ private Set customProperties = Collections.emptySet();
+
+ public Builder()
+ {
+ }
+
+ /**
+ * Copy builder
+ *
+ * @param that existing {@code SolrFacetProperties} object
+ */
+ public Builder(SolrFacetProperties that)
+ {
+ this.filterID = that.filterID;
+ this.facetQName = that.facetQName;
+ this.displayName = that.displayName;
+ this.displayControl = that.displayControl;
+ this.maxFilters = that.maxFilters;
+ this.hitThreshold = that.hitThreshold;
+ this.minFilterValueLength = that.minFilterValueLength;
+ this.sortBy = that.sortBy;
+ this.scope = that.scope;
+ this.scopedSites = that.scopedSites;
+ this.index = that.index;
+ this.isEnabled = that.isEnabled;
+ this.isDefault = that.isDefault;
+ this.customProperties = that.customProperties;
+ }
public Builder filterID(String filterID)
{
@@ -325,7 +357,10 @@ public class SolrFacetProperties implements Comparable
public Builder scopedSites(Set scopedSites)
{
- this.scopedSites = scopedSites;
+ if (scopedSites != null)
+ {
+ this.scopedSites = scopedSites;
+ }
return this;
}
@@ -347,9 +382,106 @@ public class SolrFacetProperties implements Comparable
return this;
}
+ public Builder customProperties(Set customProperties)
+ {
+ if (customProperties != null)
+ {
+ this.customProperties = customProperties;
+ }
+ return this;
+ }
+
public SolrFacetProperties build()
{
return new SolrFacetProperties(this);
}
}
+
+ public static class CustomProperties implements Serializable
+ {
+ private static final long serialVersionUID = 2250062300454166258L;
+
+ private final QName name;
+ private final String title;
+ private final String type;
+ private final Serializable value;
+
+ public CustomProperties(QName name, String title, String type, Serializable value)
+ {
+ this.name = name;
+ this.title = title;
+ this.type = type;
+ this.value = value;
+ }
+
+ public QName getName()
+ {
+ return this.name;
+ }
+
+ /**
+ * @return the title
+ */
+ public String getTitle()
+ {
+ return this.title;
+ }
+
+ /**
+ * @return the type
+ */
+ public String getType()
+ {
+ return this.type;
+ }
+
+ public Serializable getValue()
+ {
+ return this.value;
+ }
+
+ /*
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((this.name == null) ? 0 : this.name.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 || !(obj instanceof CustomProperties))
+ {
+ return false;
+ }
+ CustomProperties other = (CustomProperties) obj;
+ return EqualsHelper.nullSafeEquals(this.name, other.name);
+ }
+
+ /*
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder(100);
+ builder.append("CustomProperties [name=").append(this.name).append(", title=")
+ .append(this.title).append(", type=").append(this.type).append(", value=")
+ .append(this.value).append("]");
+ return builder.toString();
+ }
+
+ }
}
diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetService.java b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetService.java
index a751aab5e6..c26f23d943 100644
--- a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetService.java
+++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetService.java
@@ -19,7 +19,7 @@
package org.alfresco.repo.search.impl.solr.facet;
-import java.util.Map;
+import java.util.List;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -34,10 +34,9 @@ public interface SolrFacetService
/**
* Gets all the available facets.
*
- * @return Map of {@code SolrFacetProperties} with the
- * {@code SolrFacetProperties.filterID} as the key or an empty map if none exists
+ * @return List of {@code SolrFacetProperties} or an empty list if none exists
*/
- public Map getFacets();
+ public List getFacets();
/**
* Gets the facet by filter Id.
@@ -86,4 +85,6 @@ public interface SolrFacetService
* @param filterID the filter Id
*/
public void deleteFacet(String filterID);
+
+ public int getNextIndex();
}
diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetServiceImpl.java b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetServiceImpl.java
index dfa1d514a7..366f84e6e6 100644
--- a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetServiceImpl.java
+++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetServiceImpl.java
@@ -21,24 +21,30 @@ package org.alfresco.repo.search.impl.solr.facet;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.NavigableMap;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+
import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
+import org.alfresco.repo.node.NodeServicePolicies.BeforeUpdateNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnUpdateNodePolicy;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties.CustomProperties;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -60,14 +66,13 @@ import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
/**
- *
- *
* @author Jamal Kaabi-Mofrad
*/
public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrFacetService,
NodeServicePolicies.OnCreateNodePolicy,
NodeServicePolicies.OnUpdateNodePolicy,
- NodeServicePolicies.BeforeDeleteNodePolicy
+ NodeServicePolicies.BeforeDeleteNodePolicy,
+ NodeServicePolicies.BeforeUpdateNodePolicy
{
private static final Log logger = LogFactory.getLog(SolrFacetServiceImpl.class);
/**
@@ -76,7 +81,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
private static final String ALFRESCO_SEARCH_ADMINISTRATORS_AUTHORITY = "ALFRESCO_SEARCH_ADMINISTRATORS";
private static final String GROUP_ALFRESCO_SEARCH_ADMINISTRATORS_AUTHORITY = PermissionService.GROUP_PREFIX
+ ALFRESCO_SEARCH_ADMINISTRATORS_AUTHORITY;
-
+
/** The store where facets are kept */
private static final StoreRef FACET_STORE = new StoreRef("workspace://SpacesStore");
@@ -87,12 +92,13 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
private RetryingTransactionHelper retryingTransactionHelper;
private BehaviourFilter behaviourFilter;
private PolicyComponent policyComponent;
- private SolrFacetConfig facetConfig;
+ private SolrFacetConfig facetConfig;
private String facetsRootXPath;
private SimpleCache singletonCache; // eg. for facetsHomeNodeRef
private final String KEY_FACETS_HOME_NODEREF = "key.facetshome.noderef";
private SimpleCache facetNodeRefCache; // for filterID to nodeRef lookup
- private ConcurrentMap facetsMap = new ConcurrentHashMap<>();
+ private NavigableMap facetsMap = new ConcurrentSkipListMap<>();
+ private int maxAllowedFilters = 100;
/**
* @param authorityService the authorityService to set
@@ -182,6 +188,14 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
this.facetNodeRefCache = facetNodeRefCache;
}
+ /**
+ * @param maxAllowedFilters the maxAllowedFilters to set
+ */
+ public void setMaxAllowedFilters(int maxAllowedFilters)
+ {
+ this.maxAllowedFilters = maxAllowedFilters;
+ }
+
@Override
public boolean isSearchAdmin(String userName)
{
@@ -194,16 +208,26 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
}
@Override
- public Map getFacets()
+ public List getFacets()
{
- Map sortedMap = CollectionUtils.sortMapByValue(facetsMap);
- return sortedMap;
+ return new ArrayList<>(facetsMap.values());
}
@Override
public SolrFacetProperties getFacet(String filterID)
{
- return facetsMap.get(filterID);
+ /*
+ * Note: There is no need to worry about the state of the SolrFacetProperties returned from
+ * facetConfig (getDefaultLoadedFacet), as if the FP has been modified, then we'll get it from
+ * the nodeService.
+ */
+ NodeRef nodeRef = getFacetNodeRef(filterID);
+ return (nodeRef == null) ? getDefaultLoadedFacet(filterID) : getFacetProperties(nodeRef);
+ }
+
+ private SolrFacetProperties getDefaultLoadedFacet(String filterID)
+ {
+ return facetConfig.getDefaultFacets().get(filterID);
}
@Override
@@ -268,6 +292,12 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
List scSites = (List) properties.get(SolrFacetModel.PROP_SCOPED_SITES);
Set scopedSites = (scSites == null) ? null : new HashSet<>(scSites);
+ Map customProperties = getFacetCustomProperties(properties);
+ Set extraProps = new HashSet<>(customProperties.size());
+ for(Entry cp : customProperties.entrySet())
+ {
+ extraProps.add(new CustomProperties(cp.getKey(), (String) properties.get(ContentModel.PROP_TITLE), null, cp.getValue()));
+ }
// Construct the FacetProperty object
SolrFacetProperties fp = new SolrFacetProperties.Builder()
.filterID(filterID)
@@ -282,7 +312,8 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
.index(index)
.isEnabled(isEnabled)
.isDefault(isDefault)
- .scopedSites(scopedSites).build();
+ .scopedSites(scopedSites)
+ .customProperties(extraProps).build();
return fp;
}
@@ -298,9 +329,9 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
final String filterID = facetProperties.getFilterID();
NodeRef facetNodeRef = getFacetNodeRef(filterID);
- // We need to check the bootstrapped Facet properties as well, in order
- // to not allow the user to create a new facet with the same filterID as the bootstrapped FP.
- if (facetNodeRef != null || (checkDefaultFP && getFacet(filterID) != null))
+ // We need to check the bootstrapped Facet properties (i.e loaded from properties file(s)) as well,
+ // in order to not allow the user to create a new facet with the same filterID as the bootstrapped FP.
+ if (facetNodeRef != null || (checkDefaultFP && getDefaultLoadedFacet(filterID) != null))
{
throw new SolrFacetConfigException("Unable to create facet because the filterID [" + filterID + "] is already in use.");
}
@@ -312,7 +343,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
throw new SolrFacetConfigException("Facets root folder does not exist.");
}
- return facetNodeRef = AuthenticationUtil.runAs(new RunAsWork()
+ return facetNodeRef = AuthenticationUtil.runAs(new RunAsWork()
{
@Override
public NodeRef doWork() throws Exception
@@ -324,7 +355,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
behaviourFilter.disableBehaviour(facetRoot, ContentModel.ASPECT_AUDITABLE);
try
{
- Map properties = createNodeProperties(facetProperties, true);
+ Map properties = createNodeProperties(facetProperties);
// We don't want the node to be indexed
properties.put(ContentModel.PROP_IS_INDEXED, false);
NodeRef ref = nodeService.createNode(facetRoot, ContentModel.ASSOC_CONTAINS,
@@ -355,12 +386,12 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
NodeRef facetNodeRef = getFacetNodeRef(filterID);
if (facetNodeRef == null)
{
- SolrFacetProperties fp = getFacet(filterID);
+ SolrFacetProperties fp = getDefaultLoadedFacet(filterID);
if (fp != null)
{
// As we don't create nodes for the bootstrapped FP on server
// startup, we need to create a node here, when a user tries to
- // update the default properties for the first time.
+ // update the default properties for the first time.
createFacetNodeImpl(facetProperties, false);
}
else
@@ -370,7 +401,12 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
}
else
{
- Map properties = createNodeProperties(facetProperties, false);
+ String name = (String) nodeService.getProperty(facetNodeRef, ContentModel.PROP_NAME);
+ if (!filterID.equals(name))
+ {
+ throw new SolrFacetConfigException("The filterID cannot be renamed.");
+ }
+ Map properties = createNodeProperties(facetProperties);
// Set the updated properties back onto the facet node reference
this.nodeService.setProperties(facetNodeRef, properties);
}
@@ -384,17 +420,16 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
public void deleteFacet(String filterID)
{
NodeRef facetNodeRef = getFacetNodeRef(filterID);
- SolrFacetProperties defaultFP = facetConfig.getDefaultFacets().get(filterID);
- if(defaultFP != null)
- {
- throw new SolrFacetConfigException("The default [" + filterID + "] facet cannot be deleted. It can only be disabled.");
- }
-
- if(facetNodeRef == null)
+ if (facetNodeRef == null)
{
throw new SolrFacetConfigException("The [" + filterID + "] facet cannot be found.");
}
-
+
+ SolrFacetProperties defaultFP = getDefaultLoadedFacet(filterID);
+ if (defaultFP != null)
+ {
+ throw new SolrFacetConfigException("The default [" + filterID + "] facet cannot be deleted. It can only be disabled.");
+ }
nodeService.deleteNode(facetNodeRef);
if (logger.isDebugEnabled())
{
@@ -402,19 +437,17 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
}
}
- private Map createNodeProperties(SolrFacetProperties facetProperties, boolean withFilterId)
+ private Map createNodeProperties(SolrFacetProperties facetProperties)
{
if (facetProperties.getFilterID() == null)
{
throw new SolrFacetConfigException("Filter Id cannot be null.");
}
- Map properties = new HashMap(14);
+ Set customProperties = facetProperties.getCustomProperties();
+ Map properties = new HashMap(14 + customProperties.size());
- if (withFilterId)
- {
- properties.put(ContentModel.PROP_NAME, facetProperties.getFilterID());
- }
+ properties.put(ContentModel.PROP_NAME, facetProperties.getFilterID());
properties.put(SolrFacetModel.PROP_FIELD_TYPE, facetProperties.getFacetQName());
properties.put(SolrFacetModel.PROP_FIELD_LABEL, facetProperties.getDisplayName());
properties.put(SolrFacetModel.PROP_DISPLAY_CONTROL, facetProperties.getDisplayControl());
@@ -427,9 +460,14 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
properties.put(SolrFacetModel.PROP_INDEX, facetProperties.getIndex());
properties.put(SolrFacetModel.PROP_IS_ENABLED, facetProperties.isEnabled());
- SolrFacetProperties fp = facetConfig.getDefaultFacets().get(facetProperties.getFilterID());
+ SolrFacetProperties fp = getDefaultLoadedFacet(facetProperties.getFilterID());
properties.put(SolrFacetModel.PROP_IS_DEFAULT, (fp == null) ? false : fp.isDefault());
+ for (CustomProperties cp : customProperties)
+ {
+ properties.put(cp.getName(), cp.getValue());
+ }
+
return properties;
}
@@ -467,7 +505,6 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
}
return facetHomeRef;
}
-
@Override
protected void onBootstrap(ApplicationEvent event)
@@ -478,46 +515,89 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
SolrFacetModel.TYPE_FACET_FIELD,
new JavaBehaviour(this, "onCreateNode"));
+ // Filter before update
+ this.policyComponent.bindClassBehaviour(
+ BeforeUpdateNodePolicy.QNAME,
+ SolrFacetModel.TYPE_FACET_FIELD,
+ new JavaBehaviour(this, "beforeUpdateNode"));
+
// Filter update
this.policyComponent.bindClassBehaviour(
OnUpdateNodePolicy.QNAME,
SolrFacetModel.TYPE_FACET_FIELD,
new JavaBehaviour(this, "onUpdateNode"));
- // Filter deletion
+ // Filter before deletion
this.policyComponent.bindClassBehaviour(
BeforeDeleteNodePolicy.QNAME,
SolrFacetModel.TYPE_FACET_FIELD,
new JavaBehaviour(this, "beforeDeleteNode"));
+ Map mergedMap = new HashMap<>(100);
+ // Loaded facets
Map defaultFP = facetConfig.getDefaultFacets();
- for(Entry fpEntry : defaultFP.entrySet())
- {
- facetsMap.put(fpEntry.getKey(), fpEntry.getValue());
- }
-
- List persistedProperties = getPersistedFacetProperties();
+ mergedMap.putAll(defaultFP);
+
+ // Persisted facets
+ Map persistedProperties = getPersistedFacetProperties();
// The persisted facets will override the default facets
- for(SolrFacetProperties fp : persistedProperties)
+ mergedMap.putAll(persistedProperties);
+
+ // Sort the merged maps
+ Map sortedMap = CollectionUtils.sortMapByValue(mergedMap, getIndextComparator());
+ LinkedList orderedFacets = new LinkedList<>(sortedMap.values());
+
+ // Get the last index, as the map is sorted by the FP's index value
+ int maxIndex = orderedFacets.getLast().getIndex();
+ int previousIndex = -1;
+ SolrFacetProperties previousFP = null;
+ for (SolrFacetProperties facet : orderedFacets)
{
- facetsMap.put(fp.getFilterID(), fp);
+ String filterID = facet.getFilterID();
+ int index = facet.getIndex();
+ if (index == previousIndex)
+ {
+ // we can be sure that previousFP is never null, as we don't
+ // allow the index to be -1;
+ if (defaultFP.get(previousFP.getFilterID()) != null && persistedProperties.get(filterID) != null)
+ {
+ SolrFacetProperties updatedPreviousFacet = new SolrFacetProperties.Builder(previousFP).index(++maxIndex).build();
+ mergedMap.put(previousFP.getFilterID(), updatedPreviousFacet);
+ mergedMap.put(filterID, facet);
+ }
+ else
+ {
+ SolrFacetProperties updatedCurrentFacet = new SolrFacetProperties.Builder(facet).index(++maxIndex).build();
+ mergedMap.put(updatedCurrentFacet.getFilterID(), updatedCurrentFacet);
+ }
+ }
+ else
+ {
+ mergedMap.put(filterID, facet);
+ }
+ previousIndex = index;
+ previousFP = facet;
}
+ for (SolrFacetProperties fp : mergedMap.values())
+ {
+ facetsMap.put(fp.getIndex(), fp);
+ }
if (logger.isDebugEnabled() && persistedProperties.size() > 0)
{
logger.debug("The facets [" + persistedProperties + "] have overridden their matched default facets.");
}
}
-
- private List getPersistedFacetProperties()
+
+ private Map getPersistedFacetProperties()
{
List list = nodeService.getChildAssocs(getFacetsRoot());
- List facets = new ArrayList<>(list.size());
+ Map facets = new HashMap<>(list.size());
for (ChildAssociationRef associationRef : list)
{
SolrFacetProperties fp = getFacetProperties(associationRef.getChildRef());
- facets.add(fp);
+ facets.put(fp.getFilterID(), fp);
}
return facets;
}
@@ -528,18 +608,26 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
// nothing to do
}
+ @Override
+ public void beforeUpdateNode(NodeRef nodeRef)
+ {
+ // Remove the facet, in order to not end up with duplicate facets but different index
+ SolrFacetProperties fp = getFacetProperties(nodeRef);
+ this.facetsMap.remove(fp.getIndex());
+ }
+
@Override
public void onUpdateNode(NodeRef nodeRef)
{
SolrFacetProperties fp = getFacetProperties(nodeRef);
- this.facetsMap.put(fp.getFilterID(), fp);
+ this.facetsMap.put(fp.getIndex(), fp);
}
@Override
public void onCreateNode(ChildAssociationRef childAssocRef)
{
SolrFacetProperties fp = getFacetProperties(childAssocRef.getChildRef());
- this.facetsMap.put(fp.getFilterID(), fp);
+ this.facetsMap.put(fp.getIndex(), fp);
this.facetNodeRefCache.put(fp.getFilterID(), childAssocRef.getChildRef());
}
@@ -547,7 +635,106 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF
public void beforeDeleteNode(NodeRef nodeRef)
{
String filterID = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
- this.facetsMap.remove(filterID);
+ int index = (Integer) nodeService.getProperty(nodeRef, SolrFacetModel.PROP_INDEX);
+
+ this.facetsMap.remove(index);
this.facetNodeRefCache.remove(filterID);
}
+
+ /**
+ * Note: this comparator imposes orderings that are inconsistent with equals
+ * method of the {@link SolrFacetProperties}."
+ *
+ * @return
+ */
+ private Comparator> getIndextComparator()
+ {
+ return new Comparator>()
+ {
+ public int compare(Entry facet1,
+ Entry facet2)
+ {
+ return Integer.compare(facet1.getValue().getIndex(), facet2.getValue().getIndex());
+ }
+ };
+ }
+
+ @Override
+ public int getNextIndex()
+ {
+ synchronized (facetsMap)
+ {
+ if (facetsMap.size() >= maxAllowedFilters)
+ {
+ throw new SolrFacetConfigException("You have reached the maximum number of allowed filters. Please delete an existing filter in order to make a new one!");
+ }
+ int max = facetsMap.lastKey();
+ if (max >= Integer.MAX_VALUE)
+ {
+ reorder();
+ max = facetsMap.lastKey();
+ }
+
+ return max + 1;
+ }
+ }
+
+ /**
+ * Gets a map containing the facet's custom properties
+ *
+ * @return Map map containing the custom properties of the facet
+ */
+ private Map getFacetCustomProperties(Map properties)
+ {
+ Map customProperties = new HashMap(5);
+
+ for (Map.Entry entry : properties.entrySet())
+ {
+ if (SolrFacetModel.SOLR_FACET_CUSTOM_PROPERTY_URL.equals(entry.getKey().getNamespaceURI()))
+ {
+ customProperties.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return customProperties;
+ }
+
+ /**
+ * This will reorder the facetsMap, hence, the invoker needs to use an
+ * appropriate locking mechanism
+ */
+ private void reorder()
+ {
+ boolean order = false;
+ int previous = 0;
+ for (int i : facetsMap.keySet())
+ {
+ if (i != previous)
+ {
+ order = true;
+ break;
+ }
+ previous++;
+ }
+
+ if (order)
+ {
+ Map tempMap = new LinkedHashMap<>();
+ int index = 0;
+ for (SolrFacetProperties fp : facetsMap.values())
+ {
+ if (fp.getIndex() != index)
+ {
+ fp = new SolrFacetProperties.Builder(fp).index(index).build();
+ }
+ tempMap.put(index, fp);
+ index++;
+ }
+ facetsMap.clear();
+
+ for (SolrFacetProperties fp : tempMap.values())
+ {
+ updateFacet(fp);
+ }
+ }
+ }
}