Merged HEAD-BUG-FIX (5.0/Cloud) to HEAD (5.0/Cloud)

80520: Merged WAT1 (5.0/Cloud) to HEAD-BUG-FIX (5.0/Cloud)
      74961: ACE-1582: Added index control and custom data support for the facet configs.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@82817 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Will Abson
2014-09-03 15:53:15 +00:00
parent b0d07f4c04
commit 26880952a0
7 changed files with 510 additions and 94 deletions

View File

@@ -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;
* <ul>
* <li>custom.cm\:content.mimetype.filterID=filter_abc</li>
* <li>custom.cm\:content.mimetype.displayName=faceted-search.facet-menu.facet.formats</li>
* <li>custom.cm\:content.mimetype.displayControl=alfresco/search/FacetFilters</li>
* <li>custom.cm\:content.mimetype.maxFilters=5</li>
* <li>custom.cm\:content.mimetype.hitThreshold=1</li>
* <li>custom.cm\:content.mimetype.minFilterValueLength=4</li>
@@ -56,6 +61,12 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
* <li>custom.cm\:content.mimetype.index=0</li>
* <li>custom.cm\:content.mimetype.isEnabled=true</li>
* </ul>
* Also, if there is a need to add additional properties, the following needs to be
* put into a properties file:
* <ul>
* <li>custom.cm\:content.mimetype<b>.EXTRA-PROP.</b>blockIncludeFacetRequest=true</li>
* <li>custom.cm\:content.mimetype<b>.EXTRA-PROP.</b>moreProp=additionalInfo</li>
* </ul>
* The inheritance order is strictly defined using property:<br/>
* <b>${solr_facets.inheritanceHierarchy}</b><br/>
* The default inheritance orders are:<br/>
@@ -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<String> propInheritanceOrder;
@@ -163,15 +177,39 @@ public class SolrFacetConfig extends AbstractLifecycleBean
}
}
Set<String> facetFields = new HashSet<>();
Map<String, Set<String>> facetFields = new HashMap<>();
for(String key : propValues.keySet())
{
facetFields.add(key.substring(0, key.lastIndexOf('.')));
String facetQName = null;
Set<String> 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<String, SolrFacetProperties> 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<String> 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<CustomProperties> 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<CustomProperties> getCustomProps(Set<String> additionalProps, String field, Map<String, String> propValues)
{
if (additionalProps == null)
{
return Collections.emptySet();
}
Set<CustomProperties> 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<String> list = Arrays.asList(extra);
customProps.add(new CustomProperties(qName, null, null, (Serializable) list));
}
}
}
return customProps;
}
private static int getIntegerValue(String propValue)
{

View File

@@ -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");
}

View File

@@ -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<SolrFacetProperties>
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<SolrFacetProperties>
private final int index;
private final boolean isEnabled;
private final boolean isDefault; // is loaded from properties files?
private final Set<CustomProperties> customProperties;
/**
* Initialises a newly created <code>SolrFacetProperty</code> object
@@ -66,7 +70,8 @@ public class SolrFacetProperties implements Comparable<SolrFacetProperties>
this.index = builder.index;
this.isEnabled = builder.isEnabled;
this.isDefault = builder.isDefault;
this.scopedSites = (builder.scopedSites == null) ? null :Collections.unmodifiableSet(new HashSet<String>(builder.scopedSites));
this.scopedSites = Collections.unmodifiableSet(new HashSet<String>(builder.scopedSites));
this.customProperties = Collections.unmodifiableSet(new HashSet<CustomProperties>(builder.customProperties));
}
/**
@@ -142,16 +147,12 @@ public class SolrFacetProperties implements Comparable<SolrFacetProperties>
}
/**
* 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<String> getScopedSites()
{
if (this.scopedSites == null)
{
return null;
}
return Collections.unmodifiableSet(new HashSet<String>(this.scopedSites));
}
@@ -181,6 +182,17 @@ public class SolrFacetProperties implements Comparable<SolrFacetProperties>
return this.isDefault;
}
/**
* Returns an unmodifiable view of the custom properties set. Never null.
*
* @return the customProperties
*/
public Set<CustomProperties> getCustomProperties()
{
return Collections.unmodifiableSet(new HashSet<CustomProperties>(this.customProperties));
}
/*
* @see java.lang.Object#hashCode()
*/
@@ -226,22 +238,13 @@ public class SolrFacetProperties implements Comparable<SolrFacetProperties>
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<SolrFacetProperties>
.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<SolrFacetProperties>
private int minFilterValueLength;
private String sortBy;
private String scope;
private Set<String> scopedSites;
private Set<String> scopedSites = Collections.emptySet();
private int index;
private boolean isEnabled;
private boolean isDefault;
private Set<CustomProperties> 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<SolrFacetProperties>
public Builder scopedSites(Set<String> scopedSites)
{
this.scopedSites = scopedSites;
if (scopedSites != null)
{
this.scopedSites = scopedSites;
}
return this;
}
@@ -347,9 +382,106 @@ public class SolrFacetProperties implements Comparable<SolrFacetProperties>
return this;
}
public Builder customProperties(Set<CustomProperties> 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();
}
}
}

View File

@@ -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<String, SolrFacetProperties> getFacets();
public List<SolrFacetProperties> 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();
}

View File

@@ -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<String, Object> singletonCache; // eg. for facetsHomeNodeRef
private final String KEY_FACETS_HOME_NODEREF = "key.facetshome.noderef";
private SimpleCache<String, NodeRef> facetNodeRefCache; // for filterID to nodeRef lookup
private ConcurrentMap<String, SolrFacetProperties> facetsMap = new ConcurrentHashMap<>();
private NavigableMap<Integer, SolrFacetProperties> 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<String, SolrFacetProperties> getFacets()
public List<SolrFacetProperties> getFacets()
{
Map<String, SolrFacetProperties> 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<String> scSites = (List<String>) properties.get(SolrFacetModel.PROP_SCOPED_SITES);
Set<String> scopedSites = (scSites == null) ? null : new HashSet<>(scSites);
Map<QName, Serializable> customProperties = getFacetCustomProperties(properties);
Set<CustomProperties> extraProps = new HashSet<>(customProperties.size());
for(Entry<QName, Serializable> 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<NodeRef>()
return facetNodeRef = AuthenticationUtil.runAs(new RunAsWork<NodeRef>()
{
@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<QName, Serializable> properties = createNodeProperties(facetProperties, true);
Map<QName, Serializable> 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<QName, Serializable> 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<QName, Serializable> 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<QName, Serializable> createNodeProperties(SolrFacetProperties facetProperties, boolean withFilterId)
private Map<QName, Serializable> createNodeProperties(SolrFacetProperties facetProperties)
{
if (facetProperties.getFilterID() == null)
{
throw new SolrFacetConfigException("Filter Id cannot be null.");
}
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(14);
Set<CustomProperties> customProperties = facetProperties.getCustomProperties();
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(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<String, SolrFacetProperties> mergedMap = new HashMap<>(100);
// Loaded facets
Map<String, SolrFacetProperties> defaultFP = facetConfig.getDefaultFacets();
for(Entry<String, SolrFacetProperties> fpEntry : defaultFP.entrySet())
{
facetsMap.put(fpEntry.getKey(), fpEntry.getValue());
}
List<SolrFacetProperties> persistedProperties = getPersistedFacetProperties();
mergedMap.putAll(defaultFP);
// Persisted facets
Map<String, SolrFacetProperties> persistedProperties = getPersistedFacetProperties();
// The persisted facets will override the default facets
for(SolrFacetProperties fp : persistedProperties)
mergedMap.putAll(persistedProperties);
// Sort the merged maps
Map<String, SolrFacetProperties> sortedMap = CollectionUtils.sortMapByValue(mergedMap, getIndextComparator());
LinkedList<SolrFacetProperties> 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<SolrFacetProperties> getPersistedFacetProperties()
private Map<String, SolrFacetProperties> getPersistedFacetProperties()
{
List<ChildAssociationRef> list = nodeService.getChildAssocs(getFacetsRoot());
List<SolrFacetProperties> facets = new ArrayList<>(list.size());
Map<String, SolrFacetProperties> 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<Entry<String, SolrFacetProperties>> getIndextComparator()
{
return new Comparator<Entry<String, SolrFacetProperties>>()
{
public int compare(Entry<String, SolrFacetProperties> facet1,
Entry<String, SolrFacetProperties> 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<QName, Serializable> map containing the custom properties of the facet
*/
private Map<QName, Serializable> getFacetCustomProperties(Map<QName, Serializable> properties)
{
Map<QName, Serializable> customProperties = new HashMap<QName, Serializable>(5);
for (Map.Entry<QName, Serializable> 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<Integer, SolrFacetProperties> 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);
}
}
}
}