mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	71614: Merged V4.2-BUG-FIX (4.2.3) to HEAD-BUG-FIX (4.3/Cloud)
      71278: Merged V4.1-BUG-FIX (4.1.9) to V4.2-BUG-FIX (4.2.3)
         70686: Merged DEV to V4.1-BUG-FIX (4.1.9)
            70361: MNT-11120: FileFolderService.list does not order properly
             - Check next sort properties if current are null for both nodes.
             - Add unit test 
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@74703 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
	
		
			
				
	
	
		
			898 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			898 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2005-2013 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.node.getchildren;
 | |
| 
 | |
| import java.io.Serializable;
 | |
| import java.text.Collator;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Collections;
 | |
| import java.util.Comparator;
 | |
| import java.util.Date;
 | |
| import java.util.HashMap;
 | |
| import java.util.HashSet;
 | |
| import java.util.LinkedList;
 | |
| import java.util.List;
 | |
| import java.util.Map;
 | |
| import java.util.Set;
 | |
| 
 | |
| import org.alfresco.error.AlfrescoRuntimeException;
 | |
| import org.alfresco.model.ContentModel;
 | |
| import org.alfresco.query.CannedQueryParameters;
 | |
| import org.alfresco.query.CannedQuerySortDetails;
 | |
| import org.alfresco.query.CannedQuerySortDetails.SortOrder;
 | |
| import org.alfresco.repo.domain.node.AuditablePropertiesEntity;
 | |
| import org.alfresco.repo.domain.node.Node;
 | |
| import org.alfresco.repo.domain.node.NodeDAO;
 | |
| import org.alfresco.repo.domain.node.NodeEntity;
 | |
| import org.alfresco.repo.domain.node.NodePropertyEntity;
 | |
| import org.alfresco.repo.domain.node.NodePropertyHelper;
 | |
| import org.alfresco.repo.domain.node.NodePropertyKey;
 | |
| import org.alfresco.repo.domain.node.NodePropertyValue;
 | |
| import org.alfresco.repo.domain.node.ReferenceablePropertiesEntity;
 | |
| import org.alfresco.repo.domain.qname.QNameDAO;
 | |
| import org.alfresco.repo.domain.query.CannedQueryDAO;
 | |
| import org.alfresco.repo.node.getchildren.FilterPropString.FilterTypeString;
 | |
| import org.alfresco.repo.security.permissions.PermissionCheckedValue.PermissionCheckedValueMixin;
 | |
| import org.alfresco.repo.security.permissions.impl.acegi.AbstractCannedQueryPermissions;
 | |
| import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityBean;
 | |
| import org.alfresco.repo.tenant.TenantService;
 | |
| import org.alfresco.service.cmr.repository.ContentData;
 | |
| import org.alfresco.service.cmr.repository.InvalidNodeRefException;
 | |
| import org.alfresco.service.cmr.repository.MLText;
 | |
| import org.alfresco.service.cmr.repository.NodeRef;
 | |
| import org.alfresco.service.cmr.repository.NodeService;
 | |
| import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
 | |
| import org.alfresco.service.namespace.QName;
 | |
| import org.alfresco.util.AlfrescoCollator;
 | |
| import org.alfresco.util.Pair;
 | |
| import org.alfresco.util.ParameterCheck;
 | |
| import org.apache.commons.logging.Log;
 | |
| import org.apache.commons.logging.LogFactory;
 | |
| import org.springframework.extensions.surf.util.I18NUtil;
 | |
| 
 | |
| /**
 | |
|  * GetChidren canned query
 | |
|  * 
 | |
|  * To get paged list of children of a parent node filtered by child type.
 | |
|  * Also optionally filtered and/or sorted by one or more properties (up to three).
 | |
|  *
 | |
|  * @author janv
 | |
|  * @since 4.0
 | |
|  */
 | |
| public class GetChildrenCannedQuery extends AbstractCannedQueryPermissions<NodeRef>
 | |
| {
 | |
|     private Log logger = LogFactory.getLog(getClass());
 | |
|     
 | |
|     private static final String QUERY_NAMESPACE = "alfresco.node";
 | |
|     private static final String QUERY_SELECT_GET_CHILDREN_WITH_PROPS = "select_GetChildrenCannedQueryWithProps";
 | |
|     private static final String QUERY_SELECT_GET_CHILDREN_WITHOUT_PROPS = "select_GetChildrenCannedQueryWithoutProps";
 | |
|     
 | |
|     public static final int MAX_FILTER_SORT_PROPS = 3;
 | |
|     
 | |
|     // note: speical qnames - originally from Share DocLib default config (however, we do not support arbitrary "fts-alfresco" special sortable fields)
 | |
|     public static final QName SORT_QNAME_CONTENT_SIZE = QName.createQName("http://www.alfresco.org/model/content/1.0", "content.size");
 | |
|     public static final QName SORT_QNAME_CONTENT_MIMETYPE = QName.createQName("http://www.alfresco.org/model/content/1.0", "content.mimetype");
 | |
|     public static final QName SORT_QNAME_NODE_TYPE = QName.createQName("", "TYPE");
 | |
|     public static final QName SORT_QNAME_NODE_IS_FOLDER = QName.createQName("", "IS_FOLDER"); // ALF-13968
 | |
|     
 | |
|     
 | |
|     private NodeDAO nodeDAO;
 | |
|     private QNameDAO qnameDAO;
 | |
|     private CannedQueryDAO cannedQueryDAO;
 | |
|     private NodePropertyHelper nodePropertyHelper;
 | |
|     private TenantService tenantService;
 | |
|     protected NodeService nodeService;
 | |
|     
 | |
|     private boolean applyPostQueryPermissions = false; // if true, the permissions will be applied post-query (else should be applied as part of the "queryAndFilter")
 | |
|     
 | |
|     public GetChildrenCannedQuery(
 | |
|             NodeDAO nodeDAO,
 | |
|             QNameDAO qnameDAO,
 | |
|             CannedQueryDAO cannedQueryDAO,
 | |
|             NodePropertyHelper nodePropertyHelper,
 | |
|             TenantService tenantService,
 | |
|             NodeService nodeService,
 | |
|             MethodSecurityBean<NodeRef> methodSecurity,
 | |
|             CannedQueryParameters params)
 | |
|     {
 | |
|         super(params, methodSecurity);
 | |
|         
 | |
|         this.nodeDAO = nodeDAO;
 | |
|         this.qnameDAO = qnameDAO;
 | |
|         this.cannedQueryDAO = cannedQueryDAO;
 | |
|         this.nodePropertyHelper = nodePropertyHelper;
 | |
|         this.tenantService = tenantService;
 | |
|         this.nodeService = nodeService;
 | |
|         
 | |
|         if ((params.getSortDetails() != null) && (params.getSortDetails().getSortPairs().size() > 0))
 | |
|         {
 | |
|             applyPostQueryPermissions = true;
 | |
|         }
 | |
|         
 | |
|         // TODO refactor (only apply post query if sorted - as above)
 | |
|         GetChildrenCannedQueryParams paramBean = (GetChildrenCannedQueryParams)params.getParameterBean();
 | |
|         if ((paramBean.getFilterProps()!= null) && (paramBean.getFilterProps().size() > 0))
 | |
|         {
 | |
|             applyPostQueryPermissions = true;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     protected FilterSortChildQueryCallback getFilterSortChildQuery(final List<FilterSortNode> children, final List<FilterProp> filterProps, GetChildrenCannedQueryParams paramBean)
 | |
|     {
 | |
|         Set<QName> inclusiveAspects = paramBean.getInclusiveAspects();
 | |
|         Set<QName> exclusiveAspects = paramBean.getExclusiveAspects();
 | |
|         
 | |
|         return new DefaultFilterSortChildQueryCallback(children, filterProps, inclusiveAspects, exclusiveAspects);
 | |
|     }
 | |
|     
 | |
|     protected UnsortedChildQueryCallback getUnsortedChildQueryCallback(final List<NodeRef> rawResult, final int requestedCount, GetChildrenCannedQueryParams paramBean)
 | |
|     {
 | |
|         Set<QName> inclusiveAspects = paramBean.getInclusiveAspects();
 | |
|         Set<QName> exclusiveAspects = paramBean.getExclusiveAspects();
 | |
|         return new DefaultUnsortedChildQueryCallback(rawResult, requestedCount, inclusiveAspects, exclusiveAspects);
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     protected List<NodeRef> queryAndFilter(CannedQueryParameters parameters)
 | |
|     {
 | |
|         Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null);
 | |
|         
 | |
|         // Get parameters
 | |
|         GetChildrenCannedQueryParams paramBean = (GetChildrenCannedQueryParams)parameters.getParameterBean();
 | |
|         
 | |
|         // Get parent node
 | |
|         NodeRef parentRef = paramBean.getParentRef();
 | |
|         ParameterCheck.mandatory("nodeRef", parentRef);
 | |
|         Pair<Long, NodeRef> nodePair = nodeDAO.getNodePair(parentRef);
 | |
|         if (nodePair == null)
 | |
|         {
 | |
|             throw new InvalidNodeRefException("Parent node does not exist: " + parentRef, parentRef);
 | |
|         }
 | |
|         Long parentNodeId = nodePair.getFirst();
 | |
|         
 | |
|         // Set query params - note: currently using SortableChildEntity to hold (supplemental-) query params
 | |
|         FilterSortNodeEntity params = new FilterSortNodeEntity();
 | |
|         
 | |
|         // Set parent node id
 | |
|         params.setParentNodeId(parentNodeId);
 | |
|         
 | |
|         // Get filter details
 | |
|         Set<QName> childNodeTypeQNames = paramBean.getChildTypeQNames();
 | |
|         Set<QName> assocTypeQNames = paramBean.getAssocTypeQNames();
 | |
|         final List<FilterProp> filterProps = paramBean.getFilterProps();
 | |
|         String pattern = paramBean.getPattern();
 | |
|         
 | |
|         // Get sort details
 | |
|         CannedQuerySortDetails sortDetails = parameters.getSortDetails();
 | |
|         @SuppressWarnings({ "unchecked", "rawtypes" })
 | |
|         final List<Pair<QName, SortOrder>> sortPairs = (List)sortDetails.getSortPairs();
 | |
| 
 | |
|         // Set sort / filter params
 | |
|         // Note - need to keep the sort properties in their requested order
 | |
|         List<QName> sortFilterProps = new ArrayList<QName>(filterProps.size() + sortPairs.size());
 | |
|         for (Pair<QName, SortOrder> sort : sortPairs)
 | |
|         {
 | |
|             QName sortQName = sort.getFirst();
 | |
|             if(! sortFilterProps.contains(sortQName))
 | |
|             {
 | |
|                sortFilterProps.add(sortQName);
 | |
|             }
 | |
|         }
 | |
|         for (FilterProp filter : filterProps)
 | |
|         {
 | |
|             QName filterQName = filter.getPropName();
 | |
|             if(! sortFilterProps.contains(filterQName))
 | |
|             {
 | |
|                sortFilterProps.add(filterQName);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         int filterSortPropCnt = sortFilterProps.size();
 | |
|         
 | |
|         if (filterSortPropCnt > MAX_FILTER_SORT_PROPS)
 | |
|         {
 | |
|             throw new AlfrescoRuntimeException("GetChildren: exceeded maximum number filter/sort properties: (max="+MAX_FILTER_SORT_PROPS+", actual="+filterSortPropCnt);
 | |
|         }
 | |
|         
 | |
|         filterSortPropCnt = setFilterSortParams(sortFilterProps, params);
 | |
|         
 | |
|         // Set child node type qnames (additional filter - performed by DB query)
 | |
|         
 | |
|         if (childNodeTypeQNames != null)
 | |
|         {
 | |
|             Set<Long> childNodeTypeQNameIds = qnameDAO.convertQNamesToIds(childNodeTypeQNames, false);
 | |
|             if (childNodeTypeQNameIds.size() > 0)
 | |
|             {
 | |
|                 params.setChildNodeTypeQNameIds(new ArrayList<Long>(childNodeTypeQNameIds));
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         if(assocTypeQNames != null)
 | |
|         {
 | |
|             Set<Long> assocTypeQNameIds = qnameDAO.convertQNamesToIds(assocTypeQNames, false);
 | |
|             if (assocTypeQNameIds.size() > 0)
 | |
|             {
 | |
|                 params.setAssocTypeQNameIds(assocTypeQNameIds);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (pattern != null)
 | |
|         {
 | |
|             // TODO, check that we should be tied to the content model in this way. Perhaps a configurable property
 | |
|             // name against which compare the pattern?
 | |
|             Pair<Long, QName> nameQName = qnameDAO.getQName(ContentModel.PROP_NAME);
 | |
|             if(nameQName == null)
 | |
|             {
 | |
|                 throw new AlfrescoRuntimeException("Unable to determine qname id of name property");
 | |
|             }
 | |
|             params.setNamePropertyQNameId(nameQName.getFirst());
 | |
|             params.setPattern(pattern);
 | |
|         }
 | |
|         
 | |
|         final List<NodeRef> result;
 | |
|         
 | |
|         if (filterSortPropCnt > 0)
 | |
|         {
 | |
|             // filtered and/or sorted - note: permissions will be applied post query
 | |
|             final List<FilterSortNode> children = new ArrayList<FilterSortNode>(100);
 | |
|             final FilterSortChildQueryCallback c = getFilterSortChildQuery(children, filterProps, paramBean);
 | |
|             FilterSortResultHandler resultHandler = new FilterSortResultHandler(c);
 | |
|             cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_CHILDREN_WITH_PROPS, params, 0, Integer.MAX_VALUE, resultHandler);
 | |
|             resultHandler.done();
 | |
|             
 | |
|             if (sortPairs.size() > 0)
 | |
|             {
 | |
|                 // sort
 | |
|                 Collections.sort(children, new PropComparatorAsc(sortPairs));
 | |
|             }
 | |
|             
 | |
|             result = new ArrayList<NodeRef>(children.size());
 | |
|             for (FilterSortNode child : children)
 | |
|             {
 | |
|                 result.add(tenantService.getBaseName(child.getNodeRef()));
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // unsorted (apart from any implicit order) - note: permissions are applied during result handling to allow early cutoff
 | |
|             
 | |
|             final int requestedCount = parameters.getResultsRequired();
 | |
|             
 | |
|             final List<NodeRef> rawResult = new ArrayList<NodeRef>(Math.min(1000, requestedCount));
 | |
|             UnsortedChildQueryCallback callback = getUnsortedChildQueryCallback(rawResult, requestedCount, paramBean);
 | |
|             UnsortedResultHandler resultHandler = new UnsortedResultHandler(callback);
 | |
|             cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_CHILDREN_WITHOUT_PROPS, params, 0, Integer.MAX_VALUE, resultHandler);
 | |
|             resultHandler.done();
 | |
|             
 | |
|             // permissions have been applied
 | |
|             result = PermissionCheckedValueMixin.create(rawResult);
 | |
|         }
 | |
|         
 | |
|         if (start != null)
 | |
|         {
 | |
|             logger.debug("Base query "+(filterSortPropCnt > 0 ? "(sort=y, perms=n)" : "(sort=n, perms=y)")+": "+result.size()+" in "+(System.currentTimeMillis()-start)+" msecs");
 | |
|         }
 | |
|         
 | |
|         return result;
 | |
|     }
 | |
|     
 | |
|     // Set filter/sort props (between 0 and 3)
 | |
|     private int setFilterSortParams(List<QName> filterSortProps, FilterSortNodeEntity params)
 | |
|     {
 | |
|         int cnt = 0;
 | |
|         int propCnt = 0;
 | |
|         
 | |
|         for (QName filterSortProp : filterSortProps)
 | |
|         {
 | |
|             if (AuditablePropertiesEntity.getAuditablePropertyQNames().contains(filterSortProp))
 | |
|             {
 | |
|                 params.setAuditableProps(true);
 | |
|             }
 | |
|             else if (filterSortProp.equals(SORT_QNAME_NODE_TYPE) || filterSortProp.equals(SORT_QNAME_NODE_IS_FOLDER))
 | |
|             {
 | |
|                 params.setNodeType(true);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 Long sortQNameId = getQNameId(filterSortProp);
 | |
|                 if (sortQNameId != null)
 | |
|                 {
 | |
|                     if (propCnt == 0)
 | |
|                     {
 | |
|                         params.setProp1qnameId(sortQNameId);
 | |
|                     }
 | |
|                     else if (propCnt == 1)
 | |
|                     {
 | |
|                         params.setProp2qnameId(sortQNameId);
 | |
|                     }
 | |
|                     else if (propCnt == 2)
 | |
|                     {
 | |
|                         params.setProp3qnameId(sortQNameId);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         // belts and braces
 | |
|                         throw new AlfrescoRuntimeException("GetChildren: unexpected - cannot set sort parameter: "+cnt);
 | |
|                     }
 | |
|                     
 | |
|                     propCnt++;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     logger.warn("Skipping filter/sort param - cannot find: "+filterSortProp);
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             cnt++;
 | |
|         }
 | |
|         
 | |
|         return cnt;
 | |
|     }
 | |
|     
 | |
|     private Long getQNameId(QName sortPropQName)
 | |
|     {
 | |
|         if (sortPropQName.equals(SORT_QNAME_CONTENT_SIZE) || sortPropQName.equals(SORT_QNAME_CONTENT_MIMETYPE))
 | |
|         {
 | |
|             sortPropQName = ContentModel.PROP_CONTENT;
 | |
|         }
 | |
|         
 | |
|         Pair<Long, QName> qnamePair = qnameDAO.getQName(sortPropQName);
 | |
|         return (qnamePair == null ? null : qnamePair.getFirst());
 | |
|     }
 | |
|     
 | |
|     @Override
 | |
|     protected boolean isApplyPostQuerySorting()
 | |
|     {
 | |
|         // note: sorted as part of the query impl (using SortableNode results)
 | |
|         return false;
 | |
|     }
 | |
|     
 | |
|     private class PropComparatorAsc implements Comparator<FilterSortNode>
 | |
|     {
 | |
|         private List<Pair<QName, SortOrder>> sortProps;
 | |
|         private Collator collator;
 | |
|         
 | |
|         public PropComparatorAsc(List<Pair<QName, SortOrder>> sortProps)
 | |
|         {
 | |
|             this.sortProps = sortProps;
 | |
|             // try to overrider collator comparison rules
 | |
|             this.collator = AlfrescoCollator.getInstance(I18NUtil.getContentLocale()); 
 | |
|         }
 | |
|         
 | |
|         public int compare(FilterSortNode n1, FilterSortNode n2)
 | |
|         {
 | |
|             return compareImpl(n1, n2, sortProps);
 | |
|         }
 | |
|         
 | |
|         private int compareImpl(FilterSortNode node1In, FilterSortNode node2In, List<Pair<QName, SortOrder>> sortProps)
 | |
|         {
 | |
|             Object pv1 = null;
 | |
|             Object pv2 = null;
 | |
|             
 | |
|             QName sortPropQName = (QName)sortProps.get(0).getFirst();
 | |
|             boolean sortAscending = (sortProps.get(0).getSecond() == SortOrder.ASCENDING);
 | |
|             
 | |
|             FilterSortNode node1 = node1In;
 | |
|             FilterSortNode node2 = node2In; 
 | |
|             
 | |
|             if (sortAscending == false)
 | |
|             {
 | |
|                 node1 = node2In;
 | |
|                 node2 = node1In;
 | |
|             }
 | |
|             
 | |
|             int result = 0;
 | |
|             
 | |
|             pv1 = node1.getVal(sortPropQName);
 | |
|             pv2 = node2.getVal(sortPropQName);
 | |
|             
 | |
|             if (pv1 == null)
 | |
|             {
 | |
|                 if(pv2 == null && sortProps.size() > 1)
 | |
|                 {
 | |
|                     return compareImpl(node1In, node2In, sortProps.subList(1, sortProps.size()));
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     return (pv2 == null ? 0 : -1);
 | |
|                 }
 | |
|             }
 | |
|             else if (pv2 == null)
 | |
|             {
 | |
|                 return 1;
 | |
|             }
 | |
|             
 | |
|             if (pv1 instanceof String)
 | |
|             {
 | |
|                 result = collator.compare((String)pv1, (String)pv2); // TODO use collation keys (re: performance)
 | |
|             }
 | |
|             else if (pv1 instanceof Date)
 | |
|             {
 | |
|                 result = (((Date)pv1).compareTo((Date)pv2));
 | |
|             }
 | |
|             else if (pv1 instanceof Long)
 | |
|             {
 | |
|                 result = (((Long)pv1).compareTo((Long)pv2));
 | |
|             }
 | |
|             else if (pv1 instanceof Integer)
 | |
|             {
 | |
|                 result = (((Integer)pv1).compareTo((Integer)pv2));
 | |
|             }
 | |
|             else if (pv1 instanceof QName)
 | |
|             {
 | |
|                 result = (((QName)pv1).compareTo((QName)pv2));
 | |
|             }
 | |
|             else if (pv1 instanceof Boolean)
 | |
|             {
 | |
|                 result = (((Boolean)pv1).compareTo((Boolean)pv2));
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // TODO other comparisons
 | |
|                 throw new RuntimeException("Unsupported sort type: "+pv1.getClass().getName());
 | |
|             }
 | |
|             
 | |
|             if ((result == 0) && (sortProps.size() > 1))
 | |
|             {
 | |
|                 return compareImpl(node1In, node2In, sortProps.subList(1, sortProps.size()));
 | |
|             }
 | |
|             
 | |
|             return result;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private boolean includeAspects(NodeRef nodeRef, Set<QName> inclusiveAspects, Set<QName> exclusiveAspects)
 | |
|     {
 | |
|         if (inclusiveAspects == null && exclusiveAspects == null)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         Set<QName> nodeAspects = nodeService.getAspects(nodeRef);
 | |
|         if (inclusiveAspects != null) 
 | |
|         {
 | |
|             Set<QName> includedIntersect = new HashSet<QName>(nodeAspects);
 | |
|             includedIntersect.retainAll(inclusiveAspects);
 | |
|             if (includedIntersect.isEmpty()) 
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|         if (exclusiveAspects != null)
 | |
|         {
 | |
|             Set<QName> excludedIntersect = new HashSet<QName>(nodeAspects);
 | |
|             excludedIntersect.retainAll(exclusiveAspects);
 | |
|             if (excludedIntersect.isEmpty() == false)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|         return true;
 | |
|         
 | |
|     }
 | |
|     
 | |
|     // note: currently inclusive and OR-based
 | |
|     private boolean includeFilter(Map<QName, Serializable> propVals, List<FilterProp> filterProps)
 | |
|     {
 | |
|         for (FilterProp filterProp : filterProps)
 | |
|         {
 | |
|             Serializable propVal = propVals.get(filterProp.getPropName());
 | |
|             if (propVal != null)
 | |
|             {
 | |
|                 if ((filterProp instanceof FilterPropString) && (propVal instanceof String))
 | |
|                 {
 | |
|                     String val = (String)propVal;
 | |
|                     String filter = (String)filterProp.getPropVal();
 | |
|                     
 | |
|                     switch ((FilterTypeString)filterProp.getFilterType())
 | |
|                     {
 | |
|                         case STARTSWITH:
 | |
|                             if (val.startsWith(filter))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                         break;
 | |
|                         case STARTSWITH_IGNORECASE:
 | |
|                             if (val.toLowerCase().startsWith(filter.toLowerCase()))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                             break;
 | |
|                         case EQUALS:
 | |
|                             if (val.equals(filter))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                         break;
 | |
|                         case EQUALS_IGNORECASE:
 | |
|                             if (val.equalsIgnoreCase(filter))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                             break;
 | |
|                         case ENDSWITH:
 | |
|                             if (val.endsWith(filter))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                             break;
 | |
|                         case ENDSWITH_IGNORECASE:
 | |
|                             if (val.toLowerCase().endsWith(filter.toLowerCase()))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                             break;
 | |
|                         case MATCHES:
 | |
|                             if (val.matches(filter))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                             break;
 | |
|                         case MATCHES_IGNORECASE:
 | |
|                             if (val.toLowerCase().matches(filter.toLowerCase()))
 | |
|                             {
 | |
|                                 return true;
 | |
|                             }
 | |
|                             break;
 | |
|                         default:
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             if ((filterProp instanceof FilterPropBoolean) && (propVal instanceof Boolean))
 | |
|             {
 | |
|                 Boolean val = (Boolean)propVal;
 | |
|                 Boolean filter = (Boolean)filterProp.getPropVal();
 | |
|                 
 | |
|                 return (val == filter);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return false;
 | |
|     }
 | |
|     
 | |
|     @Override
 | |
|     protected boolean isApplyPostQueryPermissions()
 | |
|     {
 | |
|         return applyPostQueryPermissions; // true if sorted (if unsorted then permissions are applied as part of the query impl)
 | |
|     }
 | |
|     
 | |
|     @Override
 | |
|     protected List<NodeRef> applyPostQueryPermissions(List<NodeRef> results, int requestedCount)
 | |
|     {
 | |
|         Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null);
 | |
|         
 | |
|         int requestTotalCountMax = getParameters().getTotalResultCountMax();
 | |
|         int maxChecks = (((requestTotalCountMax > 0) && (requestTotalCountMax > requestedCount)) ? requestTotalCountMax : requestedCount);
 | |
|         int cnt = results.size();
 | |
|         
 | |
|         int toIdx = (maxChecks > cnt ? cnt : maxChecks);
 | |
|         
 | |
|         // note: assume user has read access to most/majority of the items hence pre-load up to max checks
 | |
|         preload(results.subList(0, toIdx));
 | |
|         
 | |
|         List<NodeRef> ret = super.applyPostQueryPermissions(results, requestedCount);
 | |
|         
 | |
|         if (start != null)
 | |
|         {
 | |
|             logger.debug("Post-query perms: "+ret.size()+" in "+(System.currentTimeMillis()-start)+" msecs");
 | |
|         }
 | |
|         
 | |
|         return ret;
 | |
|     }
 | |
|     
 | |
|     private void preload(List<NodeRef> nodeRefs)
 | |
|     {
 | |
|         Long start = (logger.isTraceEnabled() ? System.currentTimeMillis() : null);
 | |
|         
 | |
|         nodeDAO.cacheNodes(nodeRefs);
 | |
|         
 | |
|         if (start != null)
 | |
|         {
 | |
|             logger.trace("Pre-load: "+nodeRefs.size()+" in "+(System.currentTimeMillis()-start)+" msecs");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     protected interface FilterSortChildQueryCallback
 | |
|     {
 | |
|         boolean handle(FilterSortNode node);
 | |
|     }
 | |
|     
 | |
|     protected class DefaultFilterSortChildQueryCallback implements FilterSortChildQueryCallback
 | |
|     {
 | |
|         private List<FilterSortNode> children;
 | |
|         private List<FilterProp> filterProps;
 | |
|         private boolean applyFilter;
 | |
|         private Set<QName> inclusiveAspects;
 | |
|         private Set<QName> exclusiveAspects;
 | |
| 
 | |
|         public DefaultFilterSortChildQueryCallback(final List<FilterSortNode> children, final List<FilterProp> filterProps)
 | |
|         {
 | |
|     	    this(children, filterProps, null, null);
 | |
|     	}
 | |
| 
 | |
|     	public DefaultFilterSortChildQueryCallback(final List<FilterSortNode> children, final List<FilterProp> filterProps, Set<QName> inclusiveAspects, Set<QName> exclusiveAspects)
 | |
|     	{
 | |
|             this.children = children;
 | |
|             this.filterProps = filterProps;
 | |
|             this.applyFilter = (filterProps.size() > 0);
 | |
|             this.inclusiveAspects = inclusiveAspects;
 | |
|             this.exclusiveAspects = exclusiveAspects;
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public boolean handle(FilterSortNode node)
 | |
|         {
 | |
|             if(include(node))
 | |
|             {
 | |
|                 children.add(node);
 | |
|             }
 | |
| 
 | |
|             // More results
 | |
|             return true;
 | |
|         }
 | |
|         
 | |
|         protected boolean include(FilterSortNode node)
 | |
|         {
 | |
|             // filter, if needed
 | |
|         	return(!applyFilter || includeFilter(node.getPropVals(), filterProps)) && includeAspects(node.getNodeRef(), inclusiveAspects, exclusiveAspects);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     protected class DefaultUnsortedChildQueryCallback implements UnsortedChildQueryCallback
 | |
|     {
 | |
|         private List<NodeRef> rawResult;
 | |
|         private int requestedCount;
 | |
|         private Set<QName> inclusiveAspects;
 | |
|         private Set<QName> exclusiveAspects;
 | |
|         
 | |
|     	public DefaultUnsortedChildQueryCallback(final List<NodeRef> rawResult, final int requestedCount, Set<QName> inclusiveAspects, Set<QName> exclusiveAspects)
 | |
|         {
 | |
|             this.rawResult = rawResult;
 | |
|             this.requestedCount = requestedCount;
 | |
|     		this.inclusiveAspects = inclusiveAspects;
 | |
|     		this.exclusiveAspects = exclusiveAspects;
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public boolean handle(NodeRef nodeRef)
 | |
|         {
 | |
|             if(include(nodeRef))
 | |
|             {
 | |
|                 rawResult.add(tenantService.getBaseName(nodeRef));
 | |
|             }
 | |
| 
 | |
|             // More results ?
 | |
|             return (rawResult.size() < requestedCount);
 | |
|         }
 | |
| 
 | |
|         protected boolean include(NodeRef nodeRef)
 | |
|         {
 | |
|             return includeAspects(nodeRef, inclusiveAspects, exclusiveAspects);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     protected interface UnsortedChildQueryCallback
 | |
|     {
 | |
|         boolean handle(NodeRef nodeRef);
 | |
|     }
 | |
|     
 | |
|     protected class FilterSortResultHandler implements CannedQueryDAO.ResultHandler<FilterSortNodeEntity>
 | |
|     {
 | |
|         private final FilterSortChildQueryCallback resultsCallback;
 | |
|         private boolean more = true;
 | |
|         
 | |
|         private FilterSortResultHandler(FilterSortChildQueryCallback resultsCallback)
 | |
|         {
 | |
|             this.resultsCallback = resultsCallback;
 | |
|         }
 | |
|         
 | |
|         public boolean handleResult(FilterSortNodeEntity result)
 | |
|         {
 | |
|             // Do nothing if no further results are required
 | |
|             if (!more)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|             
 | |
|             Node node = result.getNode();
 | |
|             NodeRef nodeRef = node.getNodeRef();
 | |
|             
 | |
|             Map<NodePropertyKey, NodePropertyValue> propertyValues = new HashMap<NodePropertyKey, NodePropertyValue>(3);
 | |
|             
 | |
|             NodePropertyEntity prop1 = result.getProp1();
 | |
|             if (prop1 != null)
 | |
|             {
 | |
|                 propertyValues.put(prop1.getKey(), prop1.getValue());
 | |
|             }
 | |
|             
 | |
|             NodePropertyEntity prop2 = result.getProp2();
 | |
|             if (prop2 != null)
 | |
|             {
 | |
|                 propertyValues.put(prop2.getKey(), prop2.getValue());
 | |
|             }
 | |
|             
 | |
|             NodePropertyEntity prop3 = result.getProp3();
 | |
|             if (prop3 != null)
 | |
|             {
 | |
|                 propertyValues.put(prop3.getKey(), prop3.getValue());
 | |
|             }
 | |
|             
 | |
|             Map<QName, Serializable> propVals = nodePropertyHelper.convertToPublicProperties(propertyValues);
 | |
|             
 | |
|             // Add referenceable / spoofed properties (including spoofed name if null)
 | |
|             ReferenceablePropertiesEntity.addReferenceableProperties(node, propVals);
 | |
|             
 | |
|             // special cases
 | |
|             
 | |
|             // MLText (eg. cm:title, cm:description, ...)
 | |
|             for (Map.Entry<QName, Serializable> entry : propVals.entrySet())
 | |
|             {
 | |
|                 if (entry.getValue() instanceof MLText)
 | |
|                 {
 | |
|                     propVals.put(entry.getKey(), DefaultTypeConverter.INSTANCE.convert(String.class, (MLText)entry.getValue()));
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             // ContentData (eg. cm:content.size, cm:content.mimetype)
 | |
|             ContentData contentData = (ContentData)propVals.get(ContentModel.PROP_CONTENT);
 | |
|             if (contentData != null)
 | |
|             {
 | |
|                 propVals.put(SORT_QNAME_CONTENT_SIZE, contentData.getSize());
 | |
|                 propVals.put(SORT_QNAME_CONTENT_MIMETYPE, contentData.getMimetype());
 | |
|             }
 | |
|             
 | |
|             // Auditable props (eg. cm:creator, cm:created, cm:modifier, cm:modified, ...)
 | |
|             AuditablePropertiesEntity auditableProps = node.getAuditableProperties();
 | |
|             if (auditableProps != null)
 | |
|             {
 | |
|                 for (Map.Entry<QName, Serializable> entry : auditableProps.getAuditableProperties().entrySet())
 | |
|                 {
 | |
|                     propVals.put(entry.getKey(), entry.getValue());
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             // Node type
 | |
|             Long nodeTypeQNameId = node.getTypeQNameId();
 | |
|             if (nodeTypeQNameId != null)
 | |
|             {
 | |
|                 Pair<Long, QName> pair = qnameDAO.getQName(nodeTypeQNameId);
 | |
|                 if (pair != null)
 | |
|                 {
 | |
|                     propVals.put(SORT_QNAME_NODE_TYPE, pair.getSecond());
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             // Call back
 | |
|             boolean more = resultsCallback.handle(new FilterSortNode(nodeRef, propVals));
 | |
|             if (!more)
 | |
|             {
 | |
|                 this.more = false;
 | |
|             }
 | |
|             
 | |
|             return more;
 | |
|         }
 | |
|         
 | |
|         public void done()
 | |
|         {
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     protected class FilterSortNode
 | |
|     {
 | |
|         private NodeRef nodeRef;
 | |
|         private Map<QName, Serializable> propVals; // subset of nodes properties - used for filtering and/or sorting
 | |
|         
 | |
|         public FilterSortNode(NodeRef nodeRef, Map<QName, Serializable> propVals)
 | |
|         {
 | |
|             this.nodeRef = nodeRef;
 | |
|             this.propVals = propVals;
 | |
|         }
 | |
|         
 | |
|         @Override
 | |
|         public String toString()
 | |
|         {
 | |
|             return "FilterSortNode [nodeRef=" + nodeRef + ", propVals=" + propVals + "]";
 | |
|         }
 | |
| 
 | |
|         public NodeRef getNodeRef()
 | |
|         {
 | |
|             return nodeRef;
 | |
|         }
 | |
|         
 | |
|         public Serializable getVal(QName prop)
 | |
|         {
 | |
|             return propVals.get(prop);
 | |
|         }
 | |
|         
 | |
|         public Map<QName, Serializable> getPropVals()
 | |
|         {
 | |
|             return propVals;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private class UnsortedResultHandler implements CannedQueryDAO.ResultHandler<NodeEntity>
 | |
|     {
 | |
|         private final UnsortedChildQueryCallback resultsCallback;
 | |
|         
 | |
|         private boolean more = true;
 | |
|         
 | |
|         private static final int BATCH_SIZE = 256 * 4;
 | |
|         private final List<NodeRef> nodeRefs;
 | |
|         
 | |
|         private UnsortedResultHandler(UnsortedChildQueryCallback resultsCallback)
 | |
|         {
 | |
|             this.resultsCallback = resultsCallback;
 | |
|             
 | |
|             nodeRefs = new LinkedList<NodeRef>(); 
 | |
|         }
 | |
|         
 | |
|         public boolean handleResult(NodeEntity result)
 | |
|         {
 | |
|             // Do nothing if no further results are required
 | |
|             if (!more)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|             
 | |
|             NodeRef nodeRef = result.getNodeRef();
 | |
|             
 | |
|             if (nodeRefs.size() >= BATCH_SIZE)
 | |
|             {
 | |
|                 // batch
 | |
|                 preloadAndApplyPermissions();
 | |
|             }
 | |
|             
 | |
|             nodeRefs.add(nodeRef);
 | |
|             
 | |
|             return more;
 | |
|         }
 | |
|         
 | |
|         private void preloadAndApplyPermissions()
 | |
|         {
 | |
|             preload(nodeRefs);
 | |
|             
 | |
|             // TODO track total time for incremental permission checks ... and cutoff (eg. based on some config)
 | |
|             List<NodeRef> results = applyPostQueryPermissions(nodeRefs, nodeRefs.size());
 | |
|             
 | |
|             for (NodeRef nodeRef : results)
 | |
|             {
 | |
|                 // Call back
 | |
|                 boolean more = resultsCallback.handle(nodeRef);
 | |
|                 if (!more)
 | |
|                 {
 | |
|                     this.more = false;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             nodeRefs.clear();
 | |
|         }
 | |
|         
 | |
|         public void done()
 | |
|         {
 | |
|             if (nodeRefs.size() >= 0)
 | |
|             {
 | |
|                 // finish batch
 | |
|                 preloadAndApplyPermissions();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| } |