diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 992ec8616b..3f80e4bb93 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -669,12 +669,6 @@ alfresco/bootstrap/downloadsSpace.xml alfresco/messages/bootstrap-spaces - - - ${solr_facets.root.path} - alfresco/bootstrap/solrFacetsRootFolder.xml - alfresco/messages/bootstrap-spaces - diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index c23d382a29..94f7d4aabf 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1020,33 +1020,7 @@ - - - patch.solrFacets.root - patch.solrFacets.root.description - 0 - 8002 - 8003 - - - - - - - - - - ${solr_facets.root} - - - - ${solr_facets.root.path} - alfresco/bootstrap/solrFacetsRootFolder.xml - alfresco/messages/bootstrap-spaces - - - - + diff --git a/config/alfresco/solr-facets-context.xml b/config/alfresco/solr-facets-context.xml index 8b99db418c..a1304381d5 100644 --- a/config/alfresco/solr-facets-context.xml +++ b/config/alfresco/solr-facets-context.xml @@ -29,11 +29,21 @@ + - - ${solr_facets.root} + + + + + + + + ${solr_facets.root.path} + alfresco/bootstrap/solrFacetsRootFolder.xml + alfresco/messages/bootstrap-spaces + diff --git a/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java index 4e83d7a262..fcd561dfa8 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java @@ -108,7 +108,7 @@ public class GenericBootstrapPatch extends AbstractPatch } else if (results.size() == 1) { - // nothing to do - it exsists + // nothing to do - it exists return I18NUtil.getMessage(MSG_EXISTS, checkPath); } 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 1d35632a5c..b933074d36 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 @@ -19,6 +19,8 @@ package org.alfresco.repo.search.impl.solr.facet; +import static org.alfresco.repo.security.authentication.AuthenticationUtil.getSystemUserName; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -30,6 +32,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -39,6 +42,8 @@ import java.util.concurrent.ConcurrentMap; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.dictionary.Facetable; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; @@ -65,6 +70,7 @@ import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.ParameterCheck; import org.alfresco.util.collections.CollectionUtils; import org.apache.commons.logging.Log; @@ -78,9 +84,10 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; * @author Jamal Kaabi-Mofrad * @since 5.0 */ -public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrFacetService, - NodeServicePolicies.OnCreateNodePolicy, - NodeServicePolicies.BeforeDeleteNodePolicy +public class SolrFacetServiceImpl extends AbstractLifecycleBean + implements SolrFacetService, + NodeServicePolicies.OnCreateNodePolicy, + NodeServicePolicies.BeforeDeleteNodePolicy { private static final Log logger = LogFactory.getLog(SolrFacetServiceImpl.class); /** @@ -102,11 +109,17 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF private BehaviourFilter behaviourFilter; private PolicyComponent policyComponent; private SolrFacetConfig facetConfig; + private Repository repositoryHelper; private String facetsRootXPath; + private String facetsRootChildName; + + private ImporterBootstrap importerBootstrap; + private Properties bootstrapView; + 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 defaultFacetsMap = new ConcurrentHashMap(10); + private ConcurrentMap defaultFacetsMap = new ConcurrentHashMap<>(10); /** * @param authorityService the authorityService to set @@ -172,6 +185,11 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF this.policyComponent = policyComponent; } + public void setRepositoryHelper(Repository repository) + { + this.repositoryHelper = repository; + } + /** * @param facetConfig the facetConfig to set */ @@ -188,6 +206,18 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF this.facetsRootXPath = facetsRootXPath; } + public void setFacetsRootChildName(String facetsRootChildName) + { + this.facetsRootChildName = facetsRootChildName; + } + + public void setImporterBootstrap(ImporterBootstrap importer) { this.importerBootstrap = importer; } + + public void setBootstrapView(Properties bootstrapView) + { + this.bootstrapView = bootstrapView; + } + /** * @param singletonCache the singletonCache to set */ @@ -222,25 +252,30 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF final SolrFacetComparator comparator = new SolrFacetComparator(getFacetOrder()); SortedSet result = new TreeSet<>(comparator); - List children = nodeService.getChildAssocs(getFacetsRoot()); - for (ChildAssociationRef ref : children) + final NodeRef facetsRoot = getFacetsRoot(); + if (facetsRoot != null) { - result.add(getFacetProperties(ref.getChildRef())); + for (ChildAssociationRef ref : nodeService.getChildAssocs(facetsRoot)) + { + result.add(getFacetProperties(ref.getChildRef())); + } } + // add the default filters result.addAll(defaultFacetsMap.values()); return new ArrayList<>(result); } + /** Gets the filter IDs in display order. Will not return {@code null}. */ public List getFacetOrder() { - final NodeRef facetContainer = getFacetsRoot(); + final NodeRef facetsRoot = getFacetsRoot(); - @SuppressWarnings("unchecked") - final List facetOrder = (List) nodeService.getProperty(facetContainer, SolrFacetModel.PROP_FACET_ORDER); - return facetOrder; + return facetsRoot == null ? + new ArrayList<>(facetConfig.getDefaultFacets().keySet()) : + (List)nodeService.getProperty(facetsRoot, SolrFacetModel.PROP_FACET_ORDER); } @Override @@ -274,25 +309,33 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF { // not in cache - find and store final NodeRef facetRoot = getFacetsRoot(); - facetNodeRef = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + + if (facetRoot != null) { - public NodeRef doWork() throws Exception + facetNodeRef = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - // the filterID directly maps to the cm:name property - NodeRef nodeRef = nodeService.getChildByName(facetRoot, ContentModel.ASSOC_CONTAINS, filterID); - // cache the result if found - if (nodeRef != null) + public NodeRef doWork() throws Exception { - facetNodeRefCache.put(filterID, nodeRef); + // the filterID directly maps to the cm:name property + NodeRef nodeRef = nodeService.getChildByName(facetRoot, ContentModel.ASSOC_CONTAINS, filterID); + // cache the result if found + if (nodeRef != null) + { + facetNodeRefCache.put(filterID, nodeRef); + } + return nodeRef; } - return nodeRef; - } - }, AuthenticationUtil.getSystemUserName()); + }, AuthenticationUtil.getSystemUserName()); + } } return facetNodeRef; } + /** + * Gets the {@link SolrFacetProperties} stored on the specified {@link NodeRef}. + * @throws org.alfresco.service.cmr.repository.InvalidNodeRefException if the nodeRef does not exist. + */ private SolrFacetProperties getFacetProperties(NodeRef nodeRef) { Map properties = nodeService.getProperties(nodeRef); @@ -383,10 +426,10 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF final NodeRef facetRoot = getFacetsRoot(); if (facetRoot == null) { - throw new SolrFacetConfigException("Facets root folder does not exist."); + createFacetsRootFolder(); } - return facetNodeRef = AuthenticationUtil.runAs(new RunAsWork() + return AuthenticationUtil.runAs(new RunAsWork() { @Override public NodeRef doWork() throws Exception @@ -430,17 +473,13 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF if (facetNodeRef == null) { SolrFacetProperties fp = defaultFacetsMap.get(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. - createFacetNodeImpl(facetProperties, false); - } - else - { - throw new SolrFacetConfigException("Cannot update facet [" + filterID + "] as it does not exist."); + if (fp == null) { + createFacetsRootFolder(); } + // 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. + createFacetNodeImpl(facetProperties, false); } else { @@ -486,7 +525,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF } boolean isDefaultFP = defaultFacetsMap.containsKey(facetProperties.getFilterID()); - Map properties = new HashMap(15); + Map properties = new HashMap<>(15); properties.put(ContentModel.PROP_NAME, facetProperties.getFilterID()); properties.put(SolrFacetModel.PROP_IS_DEFAULT, isDefaultFP); @@ -529,6 +568,10 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF properties.put(qname, propValue); } + /** + * Gets the {@link NodeRef} of the {@code srft:facets} folder, if it exists. + * @return the {@link NodeRef} if it exists, else {@code null}. + */ public NodeRef getFacetsRoot() { NodeRef facetHomeRef = (NodeRef) singletonCache.get(KEY_FACETS_HOME_NODEREF); @@ -559,7 +602,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF } }, AuthenticationUtil.getSystemUserName()); - singletonCache.put(KEY_FACETS_HOME_NODEREF, facetHomeRef); + if (facetHomeRef != null) { singletonCache.put(KEY_FACETS_HOME_NODEREF, facetHomeRef); } } return facetHomeRef; } @@ -626,7 +669,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF } } - List facetOrder = getFacetOrder(); + final List facetOrder = getFacetOrder(); // Sort the merged maps Comparator> entryComparator = CollectionUtils.toEntryComparator(new SolrFacetComparator(facetOrder)); Map sortedMap = CollectionUtils.sortMapByValue(mergedMap, entryComparator); @@ -636,7 +679,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF logger.debug("The facets [" + persistedProperties + "] have overridden their matched default facets."); } - final Set newFacetOrder = (facetOrder == null) ? new LinkedHashSet(sortedMap.size()) : new LinkedHashSet(facetOrder); + final Set newFacetOrder = (facetOrder == null) ? new LinkedHashSet(sortedMap.size()) : new LinkedHashSet<>(facetOrder); for (SolrFacetProperties fp : sortedMap.values()) { @@ -652,7 +695,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF { public Void execute() throws Exception { - reorderFacets(new ArrayList(newFacetOrder)); + reorderFacets(new ArrayList<>(newFacetOrder)); return null; } }, false); @@ -665,11 +708,16 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF } } + /** Gets the persisted {@link SolrFacetProperties} if there are any, else an empty map. */ private Map getPersistedFacetProperties() { - List list = nodeService.getChildAssocs(getFacetsRoot()); + final NodeRef facetsRoot = getFacetsRoot(); + + Map facets = new HashMap<>(); + + final List list = facetsRoot == null ? + new ArrayList() : nodeService.getChildAssocs(facetsRoot); - Map facets = new HashMap<>(list.size()); for (ChildAssociationRef associationRef : list) { SolrFacetProperties fp = getFacetProperties(associationRef.getChildRef()); @@ -735,7 +783,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF */ private Map getFacetCustomProperties(Map properties) { - Map customProperties = new HashMap(5); + Map customProperties = new HashMap<>(5); for (Map.Entry entry : properties.entrySet()) { @@ -774,7 +822,7 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF final List removedFacetIds = new ArrayList<>(); for (String facetId : facetIds) { - SolrFacetProperties facet = getFacet(facetId); + final SolrFacetProperties facet = getFacet(facetId); if (facet == null) { @@ -811,9 +859,84 @@ public class SolrFacetServiceImpl extends AbstractLifecycleBean implements SolrF logger.info("Removed " + removedFacetIds + " from the facets' ordering list."); } } - nodeService.setProperty(getFacetsRoot(), SolrFacetModel.PROP_FACET_ORDER, serializableProp); + NodeRef facetsRoot = getFacetsRoot(); + + if (facetsRoot == null) + { + facetsRoot = createFacetsRootFolder(); + } + nodeService.setProperty(facetsRoot, SolrFacetModel.PROP_FACET_ORDER, serializableProp); } } + + private NodeRef createFacetsRootFolder() + { + return AuthenticationUtil.runAs(new RunAsWork() + { + @Override public NodeRef doWork() throws Exception + { + final NodeRef companyHome = repositoryHelper.getCompanyHome(); + final QName appModel = QName.createQName("http://www.alfresco.org/model/application/1.0", "dictionary"); + final NodeRef dataDict = getSingleChildNodeRef(companyHome, appModel); + + // The name of the child-assoc to the facets root folder. + final QName facetsRootAssocQName = QName.createQName(facetsRootChildName, namespaceService); + + NodeRef result = getSingleChildNodeRef(dataDict, facetsRootAssocQName); + + if (result == null) + { + List singletonList = new ArrayList<>(); + singletonList.add(bootstrapView); + importerBootstrap.setBootstrapViews(singletonList); + importerBootstrap.setUseExistingStore(true); + importerBootstrap.bootstrap(); + + // Now to get the NodeRef we just imported. (Not using SOLR to avoid consistency effects.) + result = getSingleChildNodeRef(dataDict, facetsRootAssocQName); + } + + return result; + } + }, getSystemUserName()); + } + + /** + * Gets a child NodeRef under the specified parent NodeRef linked by a child-assoc of the specified name. + * + * @param parent the parent whose child is sought. + * @param assocName the name of the child-association. + * @return the NodeRef of the requested child, if it exists. null if there is no match. + */ + private NodeRef getSingleChildNodeRef(NodeRef parent, QName assocName) + { + final List assocs = nodeService.getChildAssocs(parent, + RegexQNamePattern.MATCH_ALL, + assocName, true); + final NodeRef result; + + if (assocs == null || assocs.isEmpty()) + { + result = null; + } + else if (assocs.size() > 1) + { + final StringBuilder msg = new StringBuilder(); + msg.append("Expected exactly one child node at: ") + .append(parent).append("/").append(assocName) + .append(" but found ") + .append(assocs == null ? "" : assocs.size()); + if (logger.isErrorEnabled()) { logger.error(msg.toString()); } + + result = assocs.get(0).getChildRef(); + } + else + { + result = assocs.get(0).getChildRef(); + } + + return result; + } @Override public List getFacetableProperties() { diff --git a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java index 32f7a6b21a..cd41570580 100644 --- a/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java +++ b/source/test-java/org/alfresco/repo/search/impl/solr/facet/SolrFacetTestSuite.java @@ -40,6 +40,7 @@ public class SolrFacetTestSuite extends TestSuite suite.addTest(new JUnit4TestAdapter(SolrFacetQueriesDisplayHandlersTest.class)); suite.addTest(new JUnit4TestAdapter(SolrFacetServiceImplTest.class)); suite.addTest(new JUnit4TestAdapter(SolrFacetConfigTest.class)); + suite.addTest(new JUnit4TestAdapter(SolrFacetComparatorTest.class)); return suite; }