alfresco-community-repo/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java
Derek Hulley ae765f91d9 Merged DEV/SWIFT to HEAD
27168: Added generics for cannedQueryRegistry
   27169: Fixed formatting
   27170: SOLR queries
          - ALF-7072 RSOLR 013: Remote API to get ACLs and readers
            - Queries for ACL ChangeSets and ACLs
            - WebScripts for AclChangeSetsGet
          - ALF-7071 and ALF-7072: Fix naming conventions
   27171: Fixed MySQL create script (ALF-8336: RSOLR 013: DB upgrade scripts for ACL changes)
   27337: Initial draft of the publishing API
   27516: Get the basic WCM ML tests working, covering the simpler use cases
   27517: More on the WCM-QS multi lingual support, further on the aspect and the unit tests for it
   27518: Begin the WCM-QS webscript to return the translation details on a node
   27519: Push some WCM-QS ML search logic into SiteHelper, and add a unit test for it
   27542: - Initial skeleton of the channel API.
          - Initial draft of the publishing content model.
   27546: ALF-7481: RSOLR 018: Execute query against SOLR
          - pass authority filter in json body to avoid possible issue over-running the max URL length
   27559: Created a Web Site Generator tool which randomly generates WCM QS websites.
   27561: Created ChannelServiceImpl and implemented the ChannelType registry.
   27577: Start to pull across the WCM-QS ML custom action
   27579: More pulling across the WCM-QS ML custom action
   27580: More WCM-QS ML ui porting
   27588: ALF-8421: RSOLR 037: Encapsulate the CMIS Query Parser
   27589: Created a PublishingModel to hold Web Publishing constants.
   27610: ALF-7874 Add iWorks mimetype entries
   27624: Restructure index tracking
   27630: ALF-8182: SVC 03: Object Picker needs to use new NodeLocatorService to resolve startLocation parameter
          The picker now uses the NodeLocatorService to look up some start locations and most importantly allows
		  custom 'locators' to be used. All the current start location tokens are still supported
		  i.e. {companyhome}, {userhome}, {siteshome}, {doclib}, {self} and {parent}. A new one has been added called {ancestor},
		  this will allow an ancestor node to be located, the node can be selected by type or aspect, see example below.
          Some node locators can take parameters, a "startLocationParams" has therefore been added to allow these to be specified.

		  The example below shows how to configure a picker to start in the root folder of the site the node being edited is located within.

		  <field id="fdk:contentMultiple">
             <control>
                <control-param name="startLocation">{ancestor}</control-param>
                <control-param name="startLocationParams">type=st:site</control-param>
             </control>
          </field>
   27631: ALF-8182: SVC 03: Object Picker needs to use new NodeLocatorService to resolve startLocation parameter
          Centralised node locator classes to repo.nodelocator package
		  (moved out of repo.node package as that area is reserved for low level node processing)
		  and made all naming consistent i.e. nodelocator rather than nodelocation.
   27633: Fixed cmis:objectTypeId property definition (required = true, see CMIS 1.0 errata 1)
   27635: CMIS compliance fixes
   27638: - Initial operational publishing context with model bootstrapped.
          - First implementation of ChannelService.getChannels and ChannelService.createChannel. Test cases to follow imminently...

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28301 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2011-06-09 12:08:12 +00:00

545 lines
20 KiB
Java

/*
* Copyright (C) 2005-2011 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.solr;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.domain.node.Node;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback;
import org.alfresco.repo.domain.qname.QNameDAO;
import org.alfresco.repo.domain.solr.SOLRDAO;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
/**
* Component providing data for SOLR tracking
*
* @since 4.0
*/
public class SOLRTrackingComponentImpl implements SOLRTrackingComponent
{
private NodeDAO nodeDAO;
private QNameDAO qnameDAO;
private SOLRDAO solrDAO;
private OwnableService ownableService;
private TenantService tenantService;
private DictionaryService dictionaryService;
public void setSolrDAO(SOLRDAO solrDAO)
{
this.solrDAO = solrDAO;
}
public void setNodeDAO(NodeDAO nodeDAO)
{
this.nodeDAO = nodeDAO;
}
public void setQnameDAO(QNameDAO qnameDAO)
{
this.qnameDAO = qnameDAO;
}
public void setOwnableService(OwnableService ownableService)
{
this.ownableService = ownableService;
}
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* Initialize
*/
public void init()
{
PropertyCheck.mandatory(this, "solrDAO", solrDAO);
PropertyCheck.mandatory(this, "nodeDAO", nodeDAO);
PropertyCheck.mandatory(this, "qnameDAO", qnameDAO);
PropertyCheck.mandatory(this, "ownableService", ownableService);
PropertyCheck.mandatory(this, "tenantService", tenantService);
PropertyCheck.mandatory(this, "dictionaryService", dictionaryService);
}
@Override
public List<AclChangeSet> getAclChangeSets(Long minAclChangeSetId, Long fromCommitTime, int maxResults)
{
List<AclChangeSet> changesets = solrDAO.getAclChangeSets(minAclChangeSetId, fromCommitTime, maxResults);
return changesets;
}
@Override
public List<Transaction> getTransactions(Long minTxnId, Long fromCommitTime, int maxResults)
{
List<Transaction> txns = solrDAO.getTransactions(minTxnId, fromCommitTime, maxResults);
return txns;
}
/**
* {@inheritDoc}
*/
public void getNodes(NodeParameters nodeParameters, NodeQueryCallback callback)
{
List<Node> nodes = solrDAO.getNodes(nodeParameters);
for (Node node : nodes)
{
callback.handleNode(node);
}
}
/**
* A dumb iterator that iterates over longs in sequence.
*/
private static class SequenceIterator implements Iterable<Long>, Iterator<Long>
{
private long fromId;
private long toId;
private long counter;
private int maxResults;
private boolean inUse = false;
SequenceIterator(Long fromId, Long toId, int maxResults)
{
this.fromId = (fromId == null ? 1 : fromId.longValue());
this.toId = (toId == null ? Long.MAX_VALUE : toId.longValue());
this.maxResults = maxResults;
this.counter = this.fromId;
}
@Override
public Iterator<Long> iterator()
{
if(inUse)
{
throw new IllegalStateException("Already in use");
}
this.counter = this.fromId;
this.inUse = true;
return this;
}
@Override
public boolean hasNext()
{
return ((counter - this.fromId) < maxResults) && counter <= toId;
}
@Override
public Long next()
{
return counter++;
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
private boolean isCategorised(AspectDefinition aspDef)
{
if(aspDef == null)
{
return false;
}
AspectDefinition current = aspDef;
while (current != null)
{
if (current.getName().equals(ContentModel.ASPECT_CLASSIFIABLE))
{
return true;
}
else
{
QName parentName = current.getParentName();
if (parentName == null)
{
break;
}
current = dictionaryService.getAspect(parentName);
}
}
return false;
}
private Collection<Pair<Path, QName>> getCategoryPaths(NodeRef nodeRef, Set<QName> aspects, Map<QName, Serializable> properties)
{
ArrayList<Pair<Path, QName>> categoryPaths = new ArrayList<Pair<Path, QName>>();
for (QName classRef : aspects)
{
AspectDefinition aspDef = dictionaryService.getAspect(classRef);
if (!isCategorised(aspDef))
{
continue;
}
LinkedList<Pair<Path, QName>> aspectPaths = new LinkedList<Pair<Path, QName>>();
for (PropertyDefinition propDef : aspDef.getProperties().values())
{
if (propDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY))
{
for (NodeRef catRef : DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, properties.get(propDef.getName())))
{
if (catRef == null)
{
continue;
}
// can be running in context of System user, hence use input nodeRef
catRef = tenantService.getName(nodeRef, catRef);
try
{
Pair<Long, NodeRef> pair = nodeDAO.getNodePair(catRef);
for (Path path : nodeDAO.getPaths(pair, false))
{
if ((path.size() > 1) && (path.get(1) instanceof Path.ChildAssocElement))
{
Path.ChildAssocElement cae = (Path.ChildAssocElement) path.get(1);
boolean isFakeRoot = true;
final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(10);
// We have a callback handler to filter results
ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback()
{
public boolean preLoadNodes()
{
return false;
}
public boolean handle(
Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair,
Pair<Long, NodeRef> childNodePair)
{
results.add(childAssocPair.getSecond());
return true;
}
public void done()
{
}
};
Pair<Long, NodeRef> caePair = nodeDAO.getNodePair(cae.getRef().getChildRef());
nodeDAO.getParentAssocs(caePair.getFirst(), null, null, false, callback);
for (ChildAssociationRef car : results)
{
if (cae.getRef().equals(car))
{
isFakeRoot = false;
break;
}
}
if (isFakeRoot)
{
if (path.toString().indexOf(aspDef.getName().toString()) != -1)
{
aspectPaths.add(new Pair<Path, QName>(path, aspDef.getName()));
}
}
}
}
}
catch (InvalidNodeRefException e)
{
// If the category does not exists we move on the next
}
}
}
}
categoryPaths.addAll(aspectPaths);
}
// Add member final element
for (Pair<Path, QName> pair : categoryPaths)
{
if (pair.getFirst().last() instanceof Path.ChildAssocElement)
{
Path.ChildAssocElement cae = (Path.ChildAssocElement) pair.getFirst().last();
ChildAssociationRef assocRef = cae.getRef();
pair.getFirst().append(new Path.ChildAssocElement(new ChildAssociationRef(assocRef.getTypeQName(), assocRef.getChildRef(), QName.createQName("member"), nodeRef)));
}
}
return categoryPaths;
}
private List<Long> preCacheNodes(NodeMetaDataParameters nodeMetaDataParameters)
{
int maxResults = nodeMetaDataParameters.getMaxResults();
boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE);
List<Long> nodeIds = null;
Iterable<Long> iterable = null;
List<Long> allNodeIds = nodeMetaDataParameters.getNodeIds();
if(allNodeIds != null)
{
int toIndex = (maxResults > allNodeIds.size() ? allNodeIds.size() : maxResults);
nodeIds = isLimitSet ? allNodeIds.subList(0, toIndex) : nodeMetaDataParameters.getNodeIds();
iterable = nodeMetaDataParameters.getNodeIds();
}
else
{
Long fromNodeId = nodeMetaDataParameters.getFromNodeId();
Long toNodeId = nodeMetaDataParameters.getToNodeId();
nodeIds = new ArrayList<Long>(isLimitSet ? maxResults : 100); // TODO better default here?
iterable = new SequenceIterator(fromNodeId, toNodeId, maxResults);
int counter = 1;
for(Long nodeId : iterable)
{
if(isLimitSet && counter++ > maxResults)
{
break;
}
nodeIds.add(nodeId);
}
}
// pre-cache nodes
nodeDAO.cacheNodesById(nodeIds);
return nodeIds;
}
/**
* {@inheritDoc}
*/
public void getNodesMetadata(
NodeMetaDataParameters nodeMetaDataParameters,
MetaDataResultsFilter resultFilter,
NodeMetaDataQueryCallback callback)
{
NodeMetaDataQueryRowHandler rowHandler = new NodeMetaDataQueryRowHandler(callback);
boolean includeType = (resultFilter == null ? true : resultFilter.getIncludeType());
boolean includeProperties = (resultFilter == null ? true : resultFilter.getIncludeProperties());
boolean includeAspects = (resultFilter == null ? true : resultFilter.getIncludeAspects());
boolean includePaths = (resultFilter == null ? true : resultFilter.getIncludePaths());
boolean includeNodeRef = (resultFilter == null ? true : resultFilter.getIncludeNodeRef());
boolean includeAssociations = (resultFilter == null ? true : resultFilter.getIncludeAssociations());
boolean includeChildAssociations = (resultFilter == null ? true : resultFilter.getIncludeChildAssociations());
boolean includeOwner = (resultFilter == null ? true : resultFilter.getIncludeOwner());
List<Long> nodeIds = preCacheNodes(nodeMetaDataParameters);
for(Long nodeId : nodeIds)
{
Map<QName, Serializable> props = null;
Set<QName> aspects = null;
if (!nodeDAO.exists(nodeId))
{
// Deleted nodes have no metadata
continue;
}
NodeMetaData nodeMetaData = new NodeMetaData();
nodeMetaData.setNodeId(nodeId);
Pair<Long, NodeRef> pair = nodeDAO.getNodePair(nodeId);
nodeMetaData.setAclId(nodeDAO.getNodeAclId(nodeId));
if(includeType)
{
QName nodeType = nodeDAO.getNodeType(nodeId);
nodeMetaData.setNodeType(nodeType);
}
if(includePaths || includeProperties)
{
props = nodeDAO.getNodeProperties(nodeId);
}
nodeMetaData.setProperties(props);
if(includePaths || includeAspects)
{
aspects = nodeDAO.getNodeAspects(nodeId);
}
nodeMetaData.setAspects(aspects);
// TODO paths may change during get i.e. node moved around in the graph
if(includePaths)
{
Collection<Pair<Path, QName>> categoryPaths = getCategoryPaths(pair.getSecond(), aspects, props);
List<Path> directPaths = nodeDAO.getPaths(pair, false);
Collection<Pair<Path, QName>> paths = new ArrayList<Pair<Path, QName>>(directPaths.size() + categoryPaths.size());
for (Path path : directPaths)
{
paths.add(new Pair<Path, QName>(path, null));
}
paths.addAll(categoryPaths);
nodeMetaData.setPaths(paths);
}
if(includeNodeRef)
{
nodeMetaData.setNodeRef(pair.getSecond());
}
if(includeChildAssociations)
{
final List<ChildAssociationRef> childAssocs = new ArrayList<ChildAssociationRef>(100);
nodeDAO.getChildAssocs(nodeId, null, null, null, null, null, new ChildAssocRefQueryCallback()
{
@Override
public boolean preLoadNodes()
{
return false;
}
@Override
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, Pair<Long, NodeRef> parentNodePair,
Pair<Long, NodeRef> childNodePair)
{
childAssocs.add(childAssocPair.getSecond());
return true;
}
@Override
public void done()
{
}
});
nodeMetaData.setChildAssocs(childAssocs);
}
if(includeAssociations)
{
final List<ChildAssociationRef> parentAssocs = new ArrayList<ChildAssociationRef>(100);
nodeDAO.getParentAssocs(nodeId, null, null, null, new ChildAssocRefQueryCallback()
{
@Override
public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair)
{
parentAssocs.add(childAssocPair.getSecond());
return true;
}
@Override
public boolean preLoadNodes()
{
return false;
}
@Override
public void done()
{
}
});
// TODO non-child associations
// Collection<Pair<Long, AssociationRef>> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId);
// Collection<Pair<Long, AssociationRef>> targetAssocs = nodeDAO.getTargetNodeAssocs(nodeId);
//
// nodeMetaData.setAssocs();
}
if(includeOwner)
{
// cached in OwnableService
nodeMetaData.setOwner(ownableService.getOwner(pair.getSecond()));
}
rowHandler.processResult(nodeMetaData);
}
}
/**
* Class that passes results from a result entity into the client callback
*/
protected class NodeQueryRowHandler
{
private final NodeQueryCallback callback;
private boolean more;
private NodeQueryRowHandler(NodeQueryCallback callback)
{
this.callback = callback;
this.more = true;
}
public void processResult(Node row)
{
if (!more)
{
// No more results required
return;
}
more = callback.handleNode(row);
}
}
/**
* Class that passes results from a result entity into the client callback
*/
protected class NodeMetaDataQueryRowHandler
{
private final NodeMetaDataQueryCallback callback;
private boolean more;
private NodeMetaDataQueryRowHandler(NodeMetaDataQueryCallback callback)
{
this.callback = callback;
this.more = true;
}
public void processResult(NodeMetaData row)
{
if (!more)
{
// No more results required
return;
}
more = callback.handleNodeMetaData(row);
}
}
}