mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-07 18:25:23 +00:00
42174: ALF-14721: Merged PATCHES/V4.0.2 to V4.1-BUG-FIX 41782: ALF-15751: Merged DEV to V4.0.2 (4.0.2.14) 41704: ALF-15751: CLONE - Version History presents versions in wrong order 'VersionHistoryImpl' now sorts versions by node DB id because version with greater version number can't have id which is lesser than id of version with lesser version number. Additionally, this approach should be quicker than sorting by 'Modification date' and 'Version number' label. << Did not merge unit test, which was doing things with version branches that we don't normally support >> 42179: ALF-16149: Merged PATCHES/V4.0.1 to V4.1-BUG-FIX 41995: 41911: ALF-14127 User search retrieves all users from the DB regardless of search criteria - PeopleServiceImpl.getPeople(...) now calls a new method nonCannedGetPeopleQuery(...) rather than using the canned query which is slow with large numbers of users. 42011: 41911: ALF-14127 User search retrieves all users from the DB regardless of search criteria - Avoid NPE on params 42059: 41911: ALF-14127 User search retrieves all users from the DB regardless of search criteria - Ignore case broke one of the unit tests (now excluded from nonCanned version) 42188: French installer corrections from Gloria 42192: ALF-15906 - Share UI does not show the 'edit online' button for Visio documents 42195: Refactor of imapSpacesTemplates.acp into imapSpacesTemplates.xml and exploded content. This work is a necessary precursor to the fix for ALF-15803, which will add new localisations. 42220: Fix for ALF-16138. AbstractLinksWebScript doesn't cope with Links from deleted users. 42233: Fix for ALF-16164 Cloud monitoring of SOLR is CPU intensive due to its repeated use of the SOLR stats page and related CLOUD-760 Cloud monitoring of SOLR is CPU intensive due to its repeated use of the SOLR stats page 42259: Fix to issue where multiple concurrent writes to same user preferences would cause exception to appear in Share when changing between old document library views and new views provided by a module. 42266: ALF-16154 - IE9: script error when click on workflow from document details page 42268: Fix for ALF-11152 - License Usage information always shows 0 users 42269: Fix for ALF-15211 - TinyMCE corrupting hyperlinks 42275: ALF-15993: alfresco log not removed if uninstalled on a different day - Fix from Bitrock - Also fixed for awe and share logs 42289: Merged DEV to V4.1-BUG-FIX 42276: ALF-1907: Check out rule is active for spaces - Unit test for checkout via action executer Fixed line endings and split asserts 42292: ALF-15937: updated the Javadoc of the checkin method to be in sync with what's in doc.alfresco.com 42307: Fix handling of syncmodeconfig=OFF when running 4.1.X locally without doing full enterprise build. 42308: Fix ALF-13968: Share DocLib sorting mixes files and folders - implicitly sort folders before files (~ pre 4.x) then selected sort option, such as name - also allow Alf-specific option with CMIS getChildren (eg. "orderBy=cmis:baseTypeId DESC,cmis:name ASC") 42310: Merged BRANCHES/DEV/BELARUS/V4.1-BUG-FIX-2012_09_24 to BRANCHES/DEV/V4.1-BUG-FIX: 42309: ALF-15707 (ALF-14691) - Any custom aspect or type (including ootb workflow) is not available for API calls like api/classes/<type or aspect> 42338: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.1-BUG-FIX 42337: Fix for ALF-14764 - Moving a folder removes non-site Group permissions set, resets 'Inherit permissions flag' 42339: Fix for ALF-15151 - Selected group is illegible(black) in Admin console if High contrast theme is selected 42342: ALF-10362: Activities fail to log "name" changes with more than 1024 chars (eg. via Share "Create Content" form) - part I - fix Share config so that default "Create Content" form restricts to 255 chars as per other form config (eg. Edit Properties, inline rename, ...) 42353: Merged V3.4-BUG-FIX to V4.1-BUG-FIX (RECORD ONLY) 42281: Fix for ALF-9946 Need a supported solution for switching off content indexing (FTS) -> merge only to 4.1-BUG-FIX - remove references to isIndexed property which was removed in the back port 42360: ALF-16146: Fixed QName of the data list item type. 42361: ALF-10362: Activities fail to log "name" changes with more than 1024 chars (eg. via Share "Create Content" form) - part II - belts-and-braces (with unit test) 42362: Merged DEV to V4.1-BUG-FIX 42336: ALF-16160: office 2010 doesn't notify users of files being locked when using sharepoint protocol MS Office (if we enabled notification about document unlocking) periodically sends PROPFIND requests to get info about active locks. This code makes PROPFIND be able to send an info about locks for the MS Office 2010 client if a document was locked for edit offline. 42363: ALF-16213: renaming versioned file results in file being deleted. 42368: Record only merge V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.2) 42367: Merge V3.4 (3.4.11) to V3.4-BUG-FIX (3.4.12) 42366: ALF-16137: Merge V4.1 (4.1.1) to V3.4 (3.4.11) 42048: ALF-16005 Could not transform file size of 0 kb - Turns out that it was only doc ppt and xls zero byte files that had the problem. - Reverting part of revision 6473 (release 2.1 2007) AR-1251 (Version error when saving new content via CIFS) Dave W tells me that this is no longer an issue due to other changes 42381: Fixed ALF-16218: Solr GetNodes return status is 500 for Postgresql - Read-only webscript was calling through to "qnameDAO.getOrCreateQName", which could fail if the QName does not exist. Issue is not critical because it will start working once the QName gets created. 42384: ALF-15868 RepoTransferReceiverImplTest failing on MySQL Checked in a refactor of the transaction handling in the test to remove the suspicion that the current failure iis somehow a test error. (Still fails on MySQL) 42395: ALF-14353: Deploy pom files with dependencies to the Maven repo 42405: ALF-15986: Upgrade to Bitrock 8.5.0 in order to improve font scaling and adaptive layout with Gtk - Helps I18N 42407: Fixed 'state leak' from ActivityServiceImplTest 42408: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/V4.1-BUG-FIX: 42389: CLOUD-796: handle unknown custom content/folder type 42396: CLOUD-796: handle unknown custom content/folder type 42397: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 41858: ALF-14444 - CloudSync: Ensure unknown properties when synced to Cloud are ignored properly 42406: CLOUD-796: handle unknown custom content/folder type 42409: CloudSync: CLOUD-796 / ALF-16226 - hide sync props in forms (eg. edit props) for sync'ed custom content type 42419: Fixes: ALF-11096 - Ensures event edit button is disabled if the event came from Outlook (this is because VTI connector is one directional & changes can't be pushed back). 42420: Fix for ALF-16003 - Sync mode is incorrectly cached as off if repo hasn't started when the check is made. 42430: More refactoring of RepoTransferReceiverImplTest. 42441: Merged V4.1-BUG-FIX to HEAD 42440: ALF-16247: Thumbnails not rendering for PDFs with standard fonts - Because GS_LIB wasn't set on Linux and OSX 42452: Fix for ALF-15450 Share Lucene tool in admin console works incorrectly 42457: ALF-14347: Document workspace is incorrectly deleted - Check returned status code from delete method before continuing to delete components. 42458: ALF-15700: 'Imap Attachments' folder is not localized. - Added spaces.imap_attachments.childname property that allows the attachments folder to be localized 42459: ALF-16103: No easy way to specify a timeout for LDAP connections - Added ldap-authentication/ldap-ad-authentication property (ldap.authentication.java.naming.read.timeout) to configure the com.sun.jndi.ldap.read.timeout for the initialDirContextEnvironment. - ldap.authentication.java.naming.read.timeout property is configured in milliseconds. Defaults to zero (infinite) which is the current behavior. 42467: Fix for ALF-16275 SOLR include configuration to avoid indexing content - done and fixed all configuration to be treated as Java properties 42472: ALF-16175: Merged PATCHES/V4.0.1 to V4.1-BUG-FIX (Record Only) 42448: ALF-16096: Repo corruption in MT - clean-up assistance requested - Changed RepositoryAuthenticationDAO.getUserFolderLocation() to use getCurrentUserDomain() for its cache key. 42473: ALF-14838 ALF-14839 Deploy Maven artifacts containing the config and the test-resources, using these as classifiers 42475: ALF-14180 - CIFS - Cluster - doc and docx files are opened in read-only mode via MS Office 2003 and 2010 appropriately missed from check in 34544 42477: ALF-5051: Define ThumbnailDefinition Beans Outside of ThumbnailRegistry Bean - Reverted imgpreview to enterprise 4.1 size of 480 42504: Reverse Merge 42458 ALF-15700: 'Imap Attachments' folder is not localized. Causes unit test failures. 42517: ALF-15700: Restoring duff revision 42458 so that we can finish the job and fix it 42518: ALF-15700: Corrected internationalization of IMAP Attachments folder - RepositoryFolderConfigBean must look up paths by QName to be immune to localization and backward compatible - Must throw an error rather than using the store root if the path contains unresolved placeholders! - QName of attachments folder must remain "cm:Imap Attachments" because that's what it always was! 42528: ALF-16282: Hybrid Sync: folder unsync - sub-children still have sync indicators - fix typo fallout from ALF-15420 (r40782) + add unit/regress test 42529: ALF-16231: Corrected Imap Attachments English string 42530: ALF-14838 ALF-14839 Fix enterprise artifacts + deploy jars instead of zips 42531: ALF-14770 Cut / Paste triggers folder rules - Needed to disable rules on nodes being MOVED. - Added extra check to RuleTypeImpl when working out if a rule was disabled so that debug would not be misleading. No impact on logic, as RuleService does the same check later and discards the rules. 42546: ALF-15737 Audit trail does not show user login events - Also does not show any failed login events 42568: ALF-16077 CLONE: Incorrect activities if you try to add/edit/remove comment for document (if this document contains any title) The original activity feed comment code would include the title of a document, folder or blog rather than its name if it was available. - name is a mandatory field for a document and folder. - title is a mandatory field for a blog entry and its name may not be set via Share. Changing activity feed comment code so that the: - name is always used for documents and folders - title is always used for blogs 42571: ALF-14838 ALF-14839 Deploy config and test-resoruces artifacts in the same batch as the main artifact, otherwise they get different snapshot versions 42582: ALF-16255: CopiedFromAspectPatch fails on rules copied with a folder - Checked to make sure that cm:copiedfrom target is a cm:object before attempting a cm:original association. - Remove cm:copiedfrom aspect from source if cm:copiedfrom target is not a cm:object. 42593: ALF-16255: CopiedFromAspectPatch fails on rules copied with a folder - Corrections to log message and formatting. 42605: ALF-16231: Fixed broken IMAP unit tests 42612: Further fix for ALF-16164 Cloud monitoring of SOLR is CPU intensive due to its repeated use of the SOLR stats page - protect from dodgey JSON output 42624: ALF-14353: switch groupId to org.alfresco.enterprise, to be in sync with actual Maven deployment 42657: Fix for ALF-16359 Fix SOLR logging in production and other environments - configure in log4j-solr.properties anywhere on the solr web app classpath ... 42671: ALF-14353: fix facebook api dependency 42679: Merged V3.4-BUG-FIX to V4.1-BUG-FIX 42172: ALF-15262: Correct handling of linked rule deletion - When the last rule is removed from a folder and the ASPECT_RULES aspect is removed from its parent, we must cascade this removal to its secondary parents 42173: ALF-14400: Only site members can Edit Online (sharepoint) although the site is public and permissions allow editing for everybody - Rationalized the fix provided by Alex Malinovsky - Don't bother checking site memberships - let ACLs handle that and just check for permission to read the document 42182: Incremented version revision for 3.4.12 42243: ALF-15262: Further correction by Dmitry: use beforeRemoveAspect because beforeDeleteChildAssociation is not invoked on deletion of primary child associations 42278: ALF-12999: Correction by Alex M 42586: BDE-101: make .MD5 files suitable for easy check with md5sum -c 42627: Merged DEV to V3.4-BUG-FIX 42537: ALF-16139: Impossible to connect to CMIS via AtomPub and Web Services Activation libraries (including all Geronimo versions) have been removed because of a conflict with libraries in JBoss CXF WS installation. Also, 'javax.activation' is part of the JDK 1.6 (http://docs.oracle.com/javase/6/docs/api/javax/activation/DataHandler.html) 42677: Merged V3.4 to V3.4-BUG-FIX 42380: ALF-16220: Merged V4.1-BUG-FIX to V3.4 40590: ALF-15318: It was possible for a user with a disabled / expired account to log in via NTLM/SSO 40663: Merged DEV to V4.1-BUG-FIX 40661: ALF-15318 (part 2): It's possible to log in by disabled user (NTLM with SSO in a clustered env) The onValidateFailed() methods were moved to BaseSSOAuthenticationFilter to response with a 401 for a disabled user. 42556: ALF-15077: Site creation in Share is very very slow with over 15000 sites - Probably knock-on impact from us versioning secondary associations properly - Found old way of locating a leaf document to be ineffective as it would blow the caches (find all documents with the correct ID, then filter out the containers) - Effect was magnified when admin user was previously accessed via the explorer client and thus had an app:configurations child node, thus making admin a container and requiring its paths (e.g. zillions of nested group memberships) to be indexed - Instead, we have a new LEAFID field on leaves that we can use to efficiently locate a node to delete without hitting zillions of containers - Left backward compatible code to avoid requiring a full reindex 42557: ALF-16202: Merged V4.1-BUG-FIX to V3.4 40937: ALF-15702, ALF-15669: mmt-dependencies was messing up the SDK classpath 42566: ALF-15077: Correction to category-handling logic in container generation to fix failing unit tests 42608: Merged DEV to V3.4 42543: ALF-16248 : IE specific: It's impossible to create any event due to script error Correction for the fix for ALF-13623 to support IE8, also added clearing of 'allday' checkbox. 42622: ALF-16339: Group names incorrect in (non-site) "Manage Permissions" page - Site name was being used as the display name of all site groups! 42632: ALF-16354: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 42521: ALF-16231: Corrected LockUtils.isLockedOrReadOnly to properly handle the LOCK_EXPIRED status - Also fixed CheckOutCheckInService.checkout() to respect LOCK_EXPIRED but still disallow overwrite of unexpired WRITE_LOCKS 42522: ALF-16231: Further improvements - Renamed to isLockedAndReadOnly because that's what it means! 42644: ALF-16298: Cannot install RM amps on 4.1.1 - Passed command line arguments from shell script to mmt utility 42656: ALF-16298: Correction to DOS argument concatenation to allow multiple parameters separated by space 42664: ALF-16358: NPE detected during benchmark test. - Guarding against this in LeafScorer 42665: ALF-16360: Merged HEAD to V3.4 42440: ALF-16247: Thumbnails not rendering for PDFs with standard fonts - Because GS_LIB wasn't set on Linux and OSX 42447: ALF-16247: Thumbnails not rendering for PDFs with standard fonts - Fixes by Bitrock 42678: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 42244: Merged V3.4-BUG-FIX to V3.4 42172: ALF-15262: Correct handling of linked rule deletion - When the last rule is removed from a folder and the ASPECT_RULES aspect is removed from its parent, we must cascade this removal to its secondary parents 42243: ALF-15262: Further correction by Dmitry: use beforeRemoveAspect because beforeDeleteChildAssociation is not invoked on deletion of primary child associations 42279: Merged V3.4-BUG-FIX to V3.4 42278: ALF-12999: Correction by Alex M 42282: Merged V3.4-BUG-FIX to V3.4 42281: Fix for ALF-9946 Need a supported solution for switching off content indexing (FTS) -> merge only to 4.1-BUG-FIX - remove references to isIndexed property which was removed in the back port git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@42683 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
813 lines
30 KiB
Java
813 lines
30 KiB
Java
/*
|
|
* Copyright (C) 2005-2012 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.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.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.util.ParameterCheck;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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,
|
|
MethodSecurityBean<NodeRef> methodSecurity,
|
|
CannedQueryParameters params)
|
|
{
|
|
super(params, methodSecurity);
|
|
|
|
this.nodeDAO = nodeDAO;
|
|
this.qnameDAO = qnameDAO;
|
|
this.cannedQueryDAO = cannedQueryDAO;
|
|
this.nodePropertyHelper = nodePropertyHelper;
|
|
this.tenantService = tenantService;
|
|
|
|
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)
|
|
{
|
|
FilterSortChildQueryCallback callback = new DefaultFilterSortChildQueryCallback(children, filterProps);
|
|
return callback;
|
|
}
|
|
|
|
protected UnsortedChildQueryCallback getUnsortedChildQueryCallback(final List<NodeRef> rawResult, final int requestedCount)
|
|
{
|
|
UnsortedChildQueryCallback callback = new DefaultUnsortedChildQueryCallback(rawResult, requestedCount);
|
|
return callback;
|
|
}
|
|
|
|
@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);
|
|
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);
|
|
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;
|
|
this.collator = Collator.getInstance(); // note: currently default locale
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
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;
|
|
|
|
public DefaultFilterSortChildQueryCallback(final List<FilterSortNode> children, final List<FilterProp> filterProps)
|
|
{
|
|
this.children = children;
|
|
this.filterProps = filterProps;
|
|
this.applyFilter = (filterProps.size() > 0);
|
|
}
|
|
|
|
@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));
|
|
}
|
|
}
|
|
|
|
protected class DefaultUnsortedChildQueryCallback implements UnsortedChildQueryCallback
|
|
{
|
|
private List<NodeRef> rawResult;
|
|
private int requestedCount;
|
|
|
|
public DefaultUnsortedChildQueryCallback(final List<NodeRef> rawResult, final int requestedCount)
|
|
{
|
|
this.rawResult = rawResult;
|
|
this.requestedCount = requestedCount;
|
|
}
|
|
|
|
@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 true;
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
} |