diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index 6a77f1e014..b6e104e713 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -338,7 +338,8 @@ - + + @@ -350,6 +351,14 @@ + + + + + + + + diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml index b7d31a23a0..b2cd233f20 100644 --- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -72,13 +72,16 @@ Inbound settings from iBatis - + + + + @@ -223,6 +226,7 @@ Inbound settings from iBatis + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-people-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-people-common-SqlMap.xml new file mode 100644 index 0000000000..5946c497f5 --- /dev/null +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-people-common-SqlMap.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java index 870f18bdba..6ac430299a 100644 --- a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java +++ b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -64,8 +64,6 @@ public abstract class AbstractFeedGenerator implements FeedGenerator private RepoCtx ctx = null; - private volatile boolean busy; - public void setActivityPostServiceImpl(ActivityPostServiceImpl activityPostServiceImpl) { this.activityPostServiceImpl = activityPostServiceImpl; @@ -135,8 +133,6 @@ public abstract class AbstractFeedGenerator implements FeedGenerator { ctx = new RepoCtx(sysAdminParams, repoEndPoint); ctx.setUserNamesAreCaseSensitive(userNamesAreCaseSensitive); - - busy = false; } /** @@ -151,11 +147,6 @@ public abstract class AbstractFeedGenerator implements FeedGenerator abstract public int getEstimatedGridSize(); - protected boolean isActive() - { - return busy; - } - public void execute() throws JobExecutionException { checkProperties(); @@ -171,10 +162,11 @@ public abstract class AbstractFeedGenerator implements FeedGenerator } String lockToken = null; + LockCallback lockCallback = null; try { - JobLockRefreshCallback lockCallback = new LockCallback(); + lockCallback = new LockCallback(); lockToken = acquireLock(lockCallback); @@ -213,7 +205,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator } finally { - releaseLock(lockToken); + releaseLock(lockCallback, lockToken); } } @@ -221,6 +213,8 @@ public abstract class AbstractFeedGenerator implements FeedGenerator private class LockCallback implements JobLockRefreshCallback { + private volatile boolean busy = false; + @Override public boolean isActive() { @@ -251,7 +245,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator // Got the lock - now register the refresh callback which will keep the lock alive jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL, lockCallback); - busy = true; + ((LockCallback)lockCallback).busy = true; if (logger.isDebugEnabled()) { @@ -261,11 +255,11 @@ public abstract class AbstractFeedGenerator implements FeedGenerator return lockToken; } - private void releaseLock(String lockToken) + private void releaseLock(LockCallback lockCallback, String lockToken) { - if (lockToken != null) + if (lockCallback != null && lockToken != null) { - busy = false; + ((LockCallback)lockCallback).busy = false; jobLockService.releaseLock(lockToken, LOCK_QNAME); diff --git a/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java index f9e4d0ed49..dc14a650a7 100644 --- a/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java @@ -969,6 +969,10 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac } else { + if(valueStr.indexOf("\u0000") != -1) + { + valueStr = valueStr.replaceAll("\u0000", ""); + } // Keep the trimmed value value = valueStr; } diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index a0525ee504..cd1ba4b97c 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -1831,6 +1831,13 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // The node's version has moved on so no need to invalidate caches } + // ALF-16366: Ensure index impact is accounted for. If the node is being deleted we would expect the + // appropriate events to be fired manually + if (!nodeUpdate.isUpdateTypeQNameId() || !getNodeNotNull(nodeId, false).getDeleted(qnameDAO)) + { + nodeIndexer.indexUpdateNode(oldNode.getNodeRef()); + } + // Done if (isDebugEnabled) { diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index c9b9ca21af..29147d9f0c 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -48,7 +48,6 @@ import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService.PersonInfo; import org.alfresco.service.cmr.usage.ContentUsageService; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.alfresco.util.PropertyMap; @@ -76,7 +75,6 @@ public final class People extends BaseScopableProcessorExtension implements Init private AuthorityDAO authorityDAO; private AuthorityService authorityService; private PersonService personService; - private NamespaceService namespaceService; private MutableAuthenticationService authenticationService; private ContentUsageService contentUsageService; private TenantService tenantService; @@ -85,7 +83,10 @@ public final class People extends BaseScopableProcessorExtension implements Init private StoreRef storeRef; private ValueDerivingMapFactory valueDerivingMapFactory; private int numRetries = 10; - + + private int defaultListMaxResults = 5000; + + private static final String HINT_CQ_SUFFIX = " [hint:useCQ]"; public void afterPropertiesSet() throws Exception { @@ -192,11 +193,6 @@ public final class People extends BaseScopableProcessorExtension implements Init this.personService = personService; } - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - /** * @param contentUsageService the ContentUsageService to set */ @@ -233,6 +229,11 @@ public final class People extends BaseScopableProcessorExtension implements Init this.userRegistrySynchronizer = userRegistrySynchronizer; } + public void setDefaultListMaxResults(int defaultListMaxResults) + { + this.defaultListMaxResults = defaultListMaxResults; + } + /** * Delete a Person with the given username * @@ -508,23 +509,27 @@ public final class People extends BaseScopableProcessorExtension implements Init */ public Scriptable getPeople(String filter, int maxResults) { - Object[] people = null; - - // TODO - remove open-ended query (eg cutoff at default/configurable max, eg. 5000 people) - if (maxResults <= 0) + boolean useCQ = false; + if (filter != null) { - maxResults = Integer.MAX_VALUE; + if (filter.endsWith(HINT_CQ_SUFFIX)) + { + useCQ = true; + filter = filter.substring(0, filter.length()-HINT_CQ_SUFFIX.length()); + } } - if (filter == null || filter.length() == 0) + Object[] people = null; + + if ((maxResults <= 0) || (maxResults > defaultListMaxResults)) { - PagingRequest pagingRequest = new PagingRequest(maxResults, null); - List persons = personService.getPeople(null, true, null, pagingRequest).getPage(); - people = new Object[persons.size()]; - for (int i=0; i> filterProps = new ArrayList>(3); - filterProps.add(new Pair(ContentModel.PROP_FIRSTNAME, propVal)); - filterProps.add(new Pair(ContentModel.PROP_LASTNAME, propVal)); - filterProps.add(new Pair(ContentModel.PROP_USERNAME, propVal)); - - PagingRequest pagingRequest = new PagingRequest(maxResults, null); - List persons = personService.getPeople(filterProps, true, null, pagingRequest).getPage(); - people = new Object[persons.size()]; - for (int i=0; i 0) - { - query.append('"'); - } - else - { - query.append("*\""); - } - } - else - { - // scan for non-fts-alfresco property search tokens - int nonFtsTokens = 0; - while (t.hasMoreTokens()) - { - if (t.nextToken().indexOf(':') == -1) nonFtsTokens++; - } - t = new StringTokenizer(term, " "); - + } + } + } + + if (people == null) + { + people = new Object[0]; + } + + return Context.getCurrentContext().newArray(getScope(), people); + } + + // canned query + private Object[] getPeopleImplDB(String term, int maxResults) + { + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + Object[] people = null; + + // simple non-FTS filter: firstname or lastname or username starting with term (ignoring case) + + List filterProps = new ArrayList(3); + filterProps.add(ContentModel.PROP_FIRSTNAME); + filterProps.add(ContentModel.PROP_LASTNAME); + filterProps.add(ContentModel.PROP_USERNAME); + + List> sortProps = new ArrayList>(1); + sortProps.add(new Pair(ContentModel.PROP_USERNAME, true)); + + PagingRequest pagingRequest = new PagingRequest(maxResults, null); + List persons = personService.getPeople(term, filterProps, sortProps, pagingRequest).getPage(); + people = new Object[persons.size()]; + for (int i=0; i 0) + { + query.append('"'); + } + else + { + query.append("*\""); + } + } + else + { + // scan for non-fts-alfresco property search tokens + int nonFtsTokens = 0; + while (t.hasMoreTokens()) + { + if (t.nextToken().indexOf(':') == -1) nonFtsTokens++; + } + t = new StringTokenizer(term, " "); + // multiple terms supplied - look for first and second name etc. // assume first term is first name, any more are second i.e. "Fraun van de Wiels" // also allow fts-alfresco property search to reduce results @@ -618,77 +666,77 @@ public final class People extends BaseScopableProcessorExtension implements Init query.append("firstName:\""); query.append(term); query.append("*\" "); - - firstToken = false; - } - else - { - if (tokenSurname) - { - query.append("OR "); - } - query.append("lastName:\""); - query.append(term); - query.append("*\" "); - - tokenSurname = true; - } - } - } - else - { - // fts-alfresco property search i.e. "location:maidenhead" - propIndex = term.lastIndexOf(':'); - query.append(term.substring(0, propIndex+1)) - .append('"') - .append(term.substring(propIndex+1)) - .append('"'); - - propertySearch = true; - } - } - } - query.append(")"); - - // define the search parameters - params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); - params.addStore(this.storeRef); - params.setQuery(query.toString()); - if (maxResults > 0) - { - params.setLimitBy(LimitBy.FINAL_SIZE); - params.setLimit(maxResults); - } - - ResultSet results = null; - try - { - results = services.getSearchService().query(params); - people = results.getNodeRefs().toArray(); - } - catch (Throwable err) - { - // hide query parse error from users - if (logger.isDebugEnabled()) - logger.debug("Failed to execute people search: " + query.toString(), err); - } - finally - { - if (results != null) - { - results.close(); - } - } - } - } - } - - if (people == null) - { - people = new Object[0]; - } - - return Context.getCurrentContext().newArray(getScope(), people); + + firstToken = false; + } + else + { + if (tokenSurname) + { + query.append("OR "); + } + query.append("lastName:\""); + query.append(term); + query.append("*\" "); + + tokenSurname = true; + } + } + } + else + { + // fts-alfresco property search i.e. "location:maidenhead" + propIndex = term.lastIndexOf(':'); + query.append(term.substring(0, propIndex+1)) + .append('"') + .append(term.substring(propIndex+1)) + .append('"'); + + propertySearch = true; + } + } + } + query.append(")"); + + // define the search parameters + params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + params.addStore(this.storeRef); + params.setQuery(query.toString()); + if (maxResults > 0) + { + params.setLimitBy(LimitBy.FINAL_SIZE); + params.setLimit(maxResults); + } + + ResultSet results = null; + try + { + results = services.getSearchService().query(params); + people = results.getNodeRefs().toArray(); + + if (start != null) + { + logger.debug("getPeople: search - "+people.length+" items (in "+(System.currentTimeMillis()-start)+" msecs)"); + } + } + catch (Throwable err) + { + if (logger.isDebugEnabled()) + { + logger.debug("Failed to execute people search: " + query.toString(), err); + } + + throw err; + } + finally + { + if (results != null) + { + results.close(); + } + } + + return people; } /** diff --git a/source/java/org/alfresco/repo/security/person/FilterSortPersonEntity.java b/source/java/org/alfresco/repo/security/person/FilterSortPersonEntity.java new file mode 100644 index 0000000000..591099a57c --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/FilterSortPersonEntity.java @@ -0,0 +1,160 @@ +/* + * 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 . + */ +package org.alfresco.repo.security.person; + + +/** + * Filterable/Sortable Person Entity + * + * Can be optionally filtered/sorted by (up to) three properties - note: sort applied in same order as filter properties (if sort order is not null for given property) + * + * @author janv + * @since 4.1.2 + */ +public class FilterSortPersonEntity +{ + private Long parentNodeId; + + private Long prop1qnameId =null; + private Boolean sort1asc = null; + + private Long prop2qnameId = null; + private Boolean sort2asc = null; + + private Long prop3qnameId = null; + private Boolean sort3asc = null; + + private String pattern; + + + /** + * Default constructor + */ + public FilterSortPersonEntity() + { + } + + public Long getParentNodeId() + { + return parentNodeId; + } + + public void setParentNodeId(Long parentNodeId) + { + this.parentNodeId = parentNodeId; + } + + public String getPattern() + { + return pattern; + } + + protected String escape(String s, char escapeChar) + { + StringBuilder sb = new StringBuilder(); + int idx = -1; + int offset = 0; + do + { + idx = s.indexOf(escapeChar, offset); + if(idx != -1) + { + sb.append(s.substring(offset, idx)); + sb.append("\\"); + sb.append(escapeChar); + offset = idx + 1; + } + } + while(idx != -1); + sb.append(s.substring(offset)); + return sb.toString(); + } + + public void setPattern(String pattern) + { + if (pattern != null) + { + // escape the '%' character with '\' (standard SQL escape character) + //pattern = escape(pattern, '%'); + + // replace the wildcard character '*' with the one used in database queries i.e. '%' + this.pattern = pattern.replace('*', '%'); + } + } + + public Long getProp1qnameId() + { + return prop1qnameId; + } + + public void setProp1qnameId(Long prop1qnameId) + { + this.prop1qnameId = prop1qnameId; + } + + public Boolean getSort1asc() + { + return sort1asc; + } + + public void setSort1asc(Boolean sort1asc) + { + this.sort1asc = sort1asc; + } + + public Long getProp2qnameId() + { + return prop2qnameId; + } + + public void setProp2qnameId(Long prop2qnameId) + { + this.prop2qnameId = prop2qnameId; + } + + public Boolean getSort2asc() + { + return sort2asc; + } + + public void setSort2asc(Boolean sort2asc) + { + this.sort2asc = sort2asc; + } + + public Long getProp3qnameId() + { + return prop3qnameId; + } + + public void setProp3qnameId(Long prop3qnameId) + { + this.prop3qnameId = prop3qnameId; + } + + public Boolean getSort3asc() + { + return sort3asc; + } + + public void setSort3asc(Boolean sort3asc) + { + this.sort3asc = sort3asc; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java new file mode 100644 index 0000000000..4bd7b995bd --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java @@ -0,0 +1,315 @@ +/* + * 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 . + */ +package org.alfresco.repo.security.person; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.query.AbstractCannedQuery; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.query.CannedQuerySortDetails; +import org.alfresco.query.CannedQuerySortDetails.SortOrder; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.domain.query.CannedQueryDAO; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +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; + +/** + * GetPeople 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.1.2 + */ +public class GetPeopleCannedQuery extends AbstractCannedQuery +{ + private Log logger = LogFactory.getLog(getClass()); + + private static final String QUERY_NAMESPACE = "alfresco.query.people"; + private static final String QUERY_SELECT_GET_PEOPLE = "select_GetPeopleCannedQuery"; + + public static final int MAX_FILTER_SORT_PROPS = 3; + + private NodeDAO nodeDAO; + private QNameDAO qnameDAO; + private CannedQueryDAO cannedQueryDAO; + private TenantService tenantService; + + public GetPeopleCannedQuery( + NodeDAO nodeDAO, + QNameDAO qnameDAO, + CannedQueryDAO cannedQueryDAO, + TenantService tenantService, + CannedQueryParameters params) + { + super(params); + + this.nodeDAO = nodeDAO; + this.qnameDAO = qnameDAO; + this.cannedQueryDAO = cannedQueryDAO; + this.tenantService = tenantService; + } + + @Override + protected List queryAndFilter(CannedQueryParameters parameters) + { + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + + // Get parameters + GetPeopleCannedQueryParams paramBean = (GetPeopleCannedQueryParams)parameters.getParameterBean(); + + // Get parent node + NodeRef parentRef = paramBean.getParentRef(); + ParameterCheck.mandatory("nodeRef", parentRef); + Pair 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 + FilterSortPersonEntity params = new FilterSortPersonEntity(); + + // Set parent node id + params.setParentNodeId(parentNodeId); + + // Get filter details + final List filterProps = paramBean.getFilterProps(); + + // Get sort details + CannedQuerySortDetails sortDetails = parameters.getSortDetails(); + @SuppressWarnings({ "unchecked", "rawtypes" }) + final List> sortPairs = (List)sortDetails.getSortPairs(); + + String pattern = paramBean.getPattern(); + if ((sortPairs.size() > 0) && ((pattern == null) || (pattern.equals("")))) + { + // note: although no pattern means no filtering required, we currently need to match all if sort required + pattern = "%"; + } + else if ((! pattern.endsWith("%")) && (! pattern.endsWith("*"))) + { + // implicit startsWith match + pattern = pattern + "%"; + } + + // Set filter pattern + params.setPattern(pattern); + + // Set sort / filter params + // Note - need to keep the sort properties in their requested order + List sortFilterProps = new ArrayList(MAX_FILTER_SORT_PROPS); + Map sortAsc = new HashMap(MAX_FILTER_SORT_PROPS); + + // add sort props first + for (Pair sort : sortPairs) + { + QName sortQName = sort.getFirst(); + if ((filterProps.size() > 0) && (! filterProps.contains(sortQName))) + { + throw new AlfrescoRuntimeException("GetPeople: cannot sort by a non-filter property: "+sortQName+" (filterStringProps="+filterProps+")"); + } + + if (! sortFilterProps.contains(sortQName)) + { + sortFilterProps.add(sortQName); + sortAsc.put(sortQName, sort.getSecond().equals(SortOrder.ASCENDING)); + } + } + + // add any additional filter props (not part of sort) + for (QName filterQName : filterProps) + { + if (! sortFilterProps.contains(filterQName)) + { + sortFilterProps.add(filterQName); + sortAsc.put(filterQName, null); + } + } + + int filterSortPropCnt = sortFilterProps.size(); + + if (filterSortPropCnt > MAX_FILTER_SORT_PROPS) + { + throw new AlfrescoRuntimeException("GetPeople: exceeded maximum number filter/sort properties: (max="+MAX_FILTER_SORT_PROPS+", actual="+filterSortPropCnt); + } + + filterSortPropCnt = setFilterSortParams(sortFilterProps, sortAsc, params); + + // filtered and/or sorted - note: permissions not applicable for getPeople + final List result = new ArrayList(100); + final PersonQueryCallback c = new DefaultPersonQueryCallback(result); + PersonResultHandler resultHandler = new PersonResultHandler(c); + + int offset = parameters.getPageDetails().getSkipResults(); + int limit = parameters.getPageDetails().getPageSize(); + if (limit != Integer.MAX_VALUE) + { + // to enable hasMore flag + limit++; + } + + cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_PEOPLE, params, offset, limit, resultHandler); + resultHandler.done(); + + if (start != null) + { + logger.debug("Base query: "+result.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); + } + + return result; + } + + // Set filter/sort props (between 0 and 3) + private int setFilterSortParams(List filterSortProps, Map sortAsc, FilterSortPersonEntity params) + { + int cnt = 0; + int propCnt = 0; + + for (QName filterSortProp : filterSortProps) + { + Long sortQNameId = getQNameId(filterSortProp); + Boolean sortOrder = sortAsc.get(filterSortProp); // true = ascending, false = descending, null = unsorted + + if (sortQNameId != null) + { + if (propCnt == 0) + { + params.setProp1qnameId(sortQNameId); + params.setSort1asc(sortOrder); + } + else if (propCnt == 1) + { + params.setProp2qnameId(sortQNameId); + params.setSort2asc(sortOrder); + } + else if (propCnt == 2) + { + params.setProp3qnameId(sortQNameId); + params.setSort3asc(sortOrder); + } + else + { + // belts and braces + throw new AlfrescoRuntimeException("GetPeople: 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) + { + Pair qnamePair = qnameDAO.getQName(sortPropQName); + return (qnamePair == null ? null : qnamePair.getFirst()); + } + + @Override + protected boolean isApplyPostQuerySorting() + { + // note: sorted as part of the query impl + return false; + } + + @Override + protected boolean isApplyPostQueryPermissions() + { + return false; + } + + @Override + protected boolean isApplyPostQueryPaging() + { + return false; + } + + @Override + protected Pair getTotalResultCount(List results) + { + return null; + } + + protected interface PersonQueryCallback + { + boolean handle(NodeRef personRef); + } + + protected class DefaultPersonQueryCallback implements PersonQueryCallback + { + private List children; + + public DefaultPersonQueryCallback(final List children) + { + this.children = children; + } + + @Override + public boolean handle(NodeRef personRef) + { + children.add(tenantService.getBaseName(personRef)); + + // More results + return true; + } + } + + protected class PersonResultHandler implements CannedQueryDAO.ResultHandler + { + private final PersonQueryCallback resultsCallback; + + private PersonResultHandler(PersonQueryCallback resultsCallback) + { + this.resultsCallback = resultsCallback; + } + + public boolean handleResult(String uuid) + { + // Call back + return resultsCallback.handle(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, uuid)); + } + + public void done() + { + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryFactory.java b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryFactory.java new file mode 100644 index 0000000000..5e1ab4822e --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryFactory.java @@ -0,0 +1,123 @@ +/* + * 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 . + */ +package org.alfresco.repo.security.person; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.query.AbstractCannedQueryFactory; +import org.alfresco.query.CannedQuery; +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.query.CannedQuerySortDetails; +import org.alfresco.query.CannedQuerySortDetails.SortOrder; +import org.alfresco.query.PagingRequest; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.domain.query.CannedQueryDAO; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; + +/** + * GetPeople canned query factory - to get paged list of people + * + * @author janv + * @since 4.1.2 + */ +public class GetPeopleCannedQueryFactory extends AbstractCannedQueryFactory +{ + protected NodeDAO nodeDAO; + protected QNameDAO qnameDAO; + protected CannedQueryDAO cannedQueryDAO; + protected TenantService tenantService; + + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + public void setCannedQueryDAO(CannedQueryDAO cannedQueryDAO) + { + this.cannedQueryDAO = cannedQueryDAO; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + @Override + public CannedQuery getCannedQuery(CannedQueryParameters parameters) + { + return (CannedQuery) new GetPeopleCannedQuery(nodeDAO, qnameDAO, cannedQueryDAO, tenantService, parameters); + } + + public CannedQuery getCannedQuery(NodeRef parentRef, String pattern, List filterProps, List> sortProps, PagingRequest pagingRequest) + { + ParameterCheck.mandatory("parentRef", parentRef); + ParameterCheck.mandatory("pagingRequest", pagingRequest); + + int requestTotalCountMax = pagingRequest.getRequestTotalCountMax(); + + // specific query params - context (parent) and inclusive filters (property values) + GetPeopleCannedQueryParams paramBean = new GetPeopleCannedQueryParams(tenantService.getName(parentRef), filterProps, pattern); + + // page details + CannedQueryPageDetails cqpd = new CannedQueryPageDetails(pagingRequest.getSkipCount(), pagingRequest.getMaxItems(), CannedQueryPageDetails.DEFAULT_PAGE_NUMBER, CannedQueryPageDetails.DEFAULT_PAGE_COUNT); + + // sort details + CannedQuerySortDetails cqsd = null; + if (sortProps != null) + { + List> sortPairs = new ArrayList>(sortProps.size()); + for (Pair sortProp : sortProps) + { + sortPairs.add(new Pair(sortProp.getFirst(), (sortProp.getSecond() ? SortOrder.ASCENDING : SortOrder.DESCENDING))); + } + + cqsd = new CannedQuerySortDetails(sortPairs); + } + + // create query params holder + CannedQueryParameters params = new CannedQueryParameters(paramBean, cqpd, cqsd, requestTotalCountMax, pagingRequest.getQueryExecutionId()); + + // return canned query instance + return getCannedQuery(params); + } + + @Override + public void afterPropertiesSet() throws Exception + { + super.afterPropertiesSet(); + + PropertyCheck.mandatory(this, "tenantService", tenantService); + PropertyCheck.mandatory(this, "nodeDAO", nodeDAO); + PropertyCheck.mandatory(this, "qnameDAO", qnameDAO); + PropertyCheck.mandatory(this, "cannedQueryDAO", cannedQueryDAO); + } +} diff --git a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryParams.java b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryParams.java new file mode 100644 index 0000000000..ac77150313 --- /dev/null +++ b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQueryParams.java @@ -0,0 +1,64 @@ +/* + * 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 . + */ +package org.alfresco.repo.security.person; + +import java.util.Collections; +import java.util.List; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * GetPeople CQ parameters - for query context and filtering + * + * @author janv + * @since 4.1.2 + */ +public class GetPeopleCannedQueryParams +{ + private NodeRef parentRef; + private List filterProps = Collections.emptyList(); + private String pattern = null; + + public GetPeopleCannedQueryParams( + NodeRef parentRef, + List filterProps, + String pattern) + { + this.parentRef = parentRef; + if (filterProps != null) { this.filterProps = filterProps; } + + this.pattern = pattern; + } + + public NodeRef getParentRef() + { + return parentRef; + } + + public List getFilterProps() + { + return filterProps; + } + + public String getPattern() + { + return pattern; + } +} diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 5d5220f821..6aadca05f0 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -20,7 +20,6 @@ package org.alfresco.repo.security.person; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -29,12 +28,10 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; -import org.alfresco.query.CannedQuery; import org.alfresco.query.CannedQueryFactory; import org.alfresco.query.CannedQueryResults; import org.alfresco.query.PagingRequest; @@ -81,9 +78,6 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; @@ -115,7 +109,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { private static Log logger = LogFactory.getLog(PersonServiceImpl.class); - private static final String CANNED_QUERY_PEOPLE_LIST = "peopleGetChildrenCannedQueryFactory"; + private static final String CANNED_QUERY_PEOPLE_LIST = "getPeopleCannedQueryFactory"; + private static final String CANNED_QUERY_PEOPLE_LIST_DEPRECATED = "peopleGetChildrenCannedQueryFactory"; private static final String DELETE = "DELETE"; private static final String SPLIT = "SPLIT"; @@ -1190,71 +1185,65 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return refs; } + private List getFilterProps(List> stringPropFilters, boolean filterIgnoreCase) + { + List filterProps = null; + if (stringPropFilters != null) + { + filterProps = new ArrayList(stringPropFilters.size()); + for (Pair filterProp : stringPropFilters) + { + String filterStr = filterProp.getSecond(); + if ((filterStr == null) || (filterStr.equals("")) || (filterStr.equals("*"))) + { + // The wildcard means no filtering is needed on this property + continue; + } + else if (filterStr.endsWith("*")) + { + // The trailing * is implicit + filterStr = filterStr.substring(0, filterStr.length()-1); + } + + // Turn this into a canned query filter + filterProps.add(new FilterPropString(filterProp.getFirst(), filterStr, (filterIgnoreCase ? FilterTypeString.STARTSWITH_IGNORECASE : FilterTypeString.STARTSWITH))); + } + } + + return filterProps; + } + /** * {@inheritDoc} */ - public PagingResults getPeople(List> stringPropFilters, boolean filterIgnoreCase, List> sortProps, PagingRequest pagingRequest) + public PagingResults getPeople(String pattern, List filterStringProps, List> sortProps, PagingRequest pagingRequest) { ParameterCheck.mandatory("pagingRequest", pagingRequest); Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); - - // TODO Remove this ALF-14127 hot fix code (calling nonCannedGetPeopleQuery(...) once this canned query does not fetch all rows from the database, - // which is very slow when there are a lot of users. 10,000 user takes about 4 seconds. The customer has 90,000. + CannedQueryResults cqResults = null; - String searchValue = null; - if (filterIgnoreCase && pagingRequest != null && pagingRequest.getQueryExecutionId() == null && pagingRequest.getSkipCount() == 0) - { - searchValue = getSearchOnNameValue(stringPropFilters); - } - if (searchValue != null) - { - cqResults = nonCannedGetPeopleQuery(searchValue, pagingRequest); - } - else - { - NodeRef contextNodeRef = getPeopleContainer(); - Set childTypeQNames = new HashSet(1); - childTypeQNames.add(ContentModel.TYPE_PERSON); + NodeRef contextNodeRef = getPeopleContainer(); - // get canned query - GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject(CANNED_QUERY_PEOPLE_LIST); + // get canned query + GetPeopleCannedQueryFactory getPeopleCannedQueryFactory = (GetPeopleCannedQueryFactory)cannedQueryRegistry.getNamedObject(CANNED_QUERY_PEOPLE_LIST); - List filterProps = null; - if (stringPropFilters != null) - { - filterProps = new ArrayList(stringPropFilters.size()); - for (Pair filterProp : stringPropFilters) - { - String filterStr = filterProp.getSecond(); - if ((filterStr == null) || (filterStr.equals("")) || (filterStr.equals("*"))) - { - // The wildcard means no filtering is needed on this property - continue; - } - else if (filterStr.endsWith("*")) - { - // The trailing * is implicit - filterStr = filterStr.substring(0, filterStr.length()-1); - } - - // Turn this into a canned query filter - filterProps.add(new FilterPropString(filterProp.getFirst(), filterStr, (filterIgnoreCase ? FilterTypeString.STARTSWITH_IGNORECASE : FilterTypeString.STARTSWITH))); - } - } + GetPeopleCannedQuery cq = (GetPeopleCannedQuery)getPeopleCannedQueryFactory.getCannedQuery(contextNodeRef, pattern, filterStringProps, sortProps, pagingRequest); - GetChildrenCannedQuery cq = (GetChildrenCannedQuery)getChildrenCannedQueryFactory.getCannedQuery(contextNodeRef, null, null, childTypeQNames, filterProps, sortProps, pagingRequest); - - // execute canned query - cqResults = cq.execute(); - } + // execute canned query + cqResults = cq.execute(); final CannedQueryResults results = cqResults; - final List nodeRefs; + List nodeRefs; if (results.getPageCount() > 0) { nodeRefs = results.getPages().get(0); + if (nodeRefs.size() > pagingRequest.getMaxItems()) + { + // eg. since hasMoreItems added one (for a pre-paged result) + nodeRefs = nodeRefs.subList(0, pagingRequest.getMaxItems()); + } } else { @@ -1284,9 +1273,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { logger.debug( "getPeople: "+cnt+" items in "+(System.currentTimeMillis()-start)+" msecs " + - "[pageNum="+pageNum+",skip="+skipCount+",max="+maxItems+",hasMorePages="+hasMoreItems+ - ",totalCount="+totalCount+",filters="+stringPropFilters+ - ",filtersIgnoreCase="+filterIgnoreCase+"]"); + "[pageNum="+pageNum+",skip="+skipCount+",max="+maxItems+",hasMorePages="+hasMoreItems+ + ",totalCount="+totalCount+",pattern="+pattern+",filterStringProps="+filterStringProps+ + ",sortProps="+sortProps+"]"); } } @@ -1326,255 +1315,104 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } /** - * If the search is on first, last and user name only with the same value, return that value, - * otherwise return null. + * {@inheritDoc} */ - // TODO Remove this ALF-14127 hot fix code once this canned query does not fetch all rows from the database. - private String getSearchOnNameValue(List> stringPropFilters) - { - String filter = null; - if (stringPropFilters != null && stringPropFilters.size() == 3) - { - // Does not check we don't have duplicates. - for (int i=0; i < 3; i++) - { - Pair pair = stringPropFilters.get(i); - if (i == 0) - { - filter = pair.getSecond().trim(); - if (filter == null || filter.length() == 0) - { - filter = null; - break; - } - } - - if ((i != 0 && !filter.equals(pair.getSecond().trim()) || !NAME_SEARCH_NAMES.contains(pair.getFirst()))) - { - filter = null; - break; - } - } - } - return filter; - } - - // TODO Remove this ALF-14127 hot fix code once this canned query does not fetch all rows from the database. - private static final List NAME_SEARCH_NAMES = Arrays.asList(new QName[] { - ContentModel.PROP_FIRSTNAME, ContentModel.PROP_LASTNAME, ContentModel.PROP_USERNAME}); - - /** - * Use Solr search based on code in org.alfresco.repo.jscript.People.getPeople(String, int) - */ - // TODO Remove this ALF-14127 hot fix code once this canned query does not fetch all rows from the database. - private CannedQueryResults nonCannedGetPeopleQuery(String filter, PagingRequest pagingRequest) + public PagingResults getPeople(List> stringPropFilters, boolean filterIgnoreCase, List> sortProps, PagingRequest pagingRequest) { + ParameterCheck.mandatory("pagingRequest", pagingRequest); + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); - int maxResults = pagingRequest != null ? pagingRequest.getMaxItems() : Integer.MAX_VALUE; - if (maxResults <= 0) + + CannedQueryResults cqResults = null; + + NodeRef contextNodeRef = getPeopleContainer(); + + Set childTypeQNames = new HashSet(1); + childTypeQNames.add(ContentModel.TYPE_PERSON); + + // get canned query + GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject(CANNED_QUERY_PEOPLE_LIST_DEPRECATED); + + List filterProps = getFilterProps(stringPropFilters, filterIgnoreCase); + + GetChildrenCannedQuery cq = (GetChildrenCannedQuery)getChildrenCannedQueryFactory.getCannedQuery(contextNodeRef, null, null, childTypeQNames, filterProps, sortProps, pagingRequest); + + // execute canned query + cqResults = cq.execute(); + + final CannedQueryResults results = cqResults; + final List nodeRefs; + if (results.getPageCount() > 0) { - maxResults = Integer.MAX_VALUE; - } - - String term = filter.replace("\\", "").replace("\"", ""); - StringTokenizer t = new StringTokenizer(term, " "); - int propIndex = term.indexOf(':'); - - SearchParameters params = new SearchParameters(); - params.addQueryTemplate("_PERSON", "|%firstName OR |%lastName OR |%userName"); - params.setDefaultFieldName("_PERSON"); - - StringBuilder query = new StringBuilder(256); - - query.append("TYPE:\"").append(ContentModel.TYPE_PERSON).append("\" AND ("); - - if (t.countTokens() == 1) - { - // single word with no field will go against _PERSON and expand - - // fts-alfresco property search i.e. location:"maidenhead" - query.append(term.substring(0, propIndex + 1)).append('"') - .append(term.substring(propIndex + 1)); - if (propIndex > 0) - { - query.append('"'); - } - else - { - query.append("*\""); - } + nodeRefs = results.getPages().get(0); } else { - // scan for non-fts-alfresco property search tokens - int nonFtsTokens = 0; - while (t.hasMoreTokens()) - { - if (t.nextToken().indexOf(':') == -1) - nonFtsTokens++; - } - t = new StringTokenizer(term, " "); - - // multiple terms supplied - look for first and second name etc. - // assume first term is first name, any more are second i.e. - // "Fraun van de Wiels" - // also allow fts-alfresco property search to reduce results - params.setDefaultOperator(SearchParameters.Operator.AND); - boolean firstToken = true; - boolean tokenSurname = false; - boolean propertySearch = false; - while (t.hasMoreTokens()) - { - term = t.nextToken(); - if (!propertySearch && term.indexOf(':') == -1) - { - if (nonFtsTokens == 1) - { - // simple search: first name, last name and username - // starting with term - query.append("_PERSON:\""); - query.append(term); - query.append("*\" "); - } - else - { - if (firstToken) - { - query.append("firstName:\""); - query.append(term); - query.append("*\" "); - - firstToken = false; - } - else - { - if (tokenSurname) - { - query.append("OR "); - } - query.append("lastName:\""); - query.append(term); - query.append("*\" "); - - tokenSurname = true; - } - } - } - else - { - // fts-alfresco property search i.e. "location:maidenhead" - propIndex = term.indexOf(':'); - query.append(term.substring(0, propIndex + 1)).append('"') - .append(term.substring(propIndex + 1)).append('"'); - - propertySearch = true; - } - } + nodeRefs = Collections.emptyList(); } - query.append(")"); - - // define the search parameters - params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); - params.addStore(this.storeRef); - params.setQuery(query.toString()); - if (maxResults > 0) + + // set total count + final Pair totalCount; + if (pagingRequest.getRequestTotalCountMax() > 0) { - params.setLimitBy(LimitBy.FINAL_SIZE); - params.setLimit(maxResults); + totalCount = results.getTotalResultCount(); } - - ResultSet results = null; - List resultNodeRefs = null; - try + else { - results = searchService.query(params); - resultNodeRefs = results.getNodeRefs(); + totalCount = null; } - catch (Throwable err) + + if (start != null) { - resultNodeRefs = Collections.emptyList(); + int cnt = results.getPagedResultCount(); + int skipCount = pagingRequest.getSkipCount(); + int maxItems = pagingRequest.getMaxItems(); + boolean hasMoreItems = results.hasMoreItems(); + int pageNum = (skipCount / maxItems) + 1; - // hide query parse error from users if (logger.isDebugEnabled()) - logger.debug("Failed to execute people search: " + query.toString(), err); - } - finally - { - if (results != null) { - results.close(); + logger.debug( + "getPeople: "+cnt+" items in "+(System.currentTimeMillis()-start)+" msecs " + + "[pageNum="+pageNum+",skip="+skipCount+",max="+maxItems+",hasMorePages="+hasMoreItems+ + ",totalCount="+totalCount+",filters="+stringPropFilters+ + ",filtersIgnoreCase="+filterIgnoreCase+",sortProps="+sortProps+"]"); } } - - // Turn NodeRefs into a single page of results. - final List nodRefs = resultNodeRefs; - CannedQueryResults cqResults = new CannedQueryResults() + + final List personInfos = new ArrayList(nodeRefs.size()); + for (NodeRef nodeRef : nodeRefs) + { + Map props = nodeService.getProperties(nodeRef); + personInfos.add(new PersonInfo(nodeRef, + (String)props.get(ContentModel.PROP_USERNAME), + (String)props.get(ContentModel.PROP_FIRSTNAME), + (String)props.get(ContentModel.PROP_LASTNAME))); + } + + return new PagingResults() { - @Override - public CannedQuery getOriginatingQuery() - { - return null; - } - @Override public String getQueryExecutionId() { - return null; + return results.getQueryExecutionId(); } - @Override - public Pair getTotalResultCount() + public List getPage() { - int size = nodRefs.size(); - return new Pair(size, size); + return personInfos; } - - @Override - public int getPagedResultCount() - { - return nodRefs.size(); - } - - @Override - public int getPageCount() - { - return 1; - } - - @Override - public NodeRef getSingleResult() - { - if (nodRefs.size() != 1) - { - throw new IllegalStateException( - "There must be exactly one page of one result available."); - } - return nodRefs.get(0); - } - - @Override - public List getPage() - { - return nodRefs; - } - - @Override - public List> getPages() - { - return Collections.singletonList(getPage()); - } - @Override public boolean hasMoreItems() { - return false; + return results.hasMoreItems(); + } + @Override + public Pair getTotalResultCount() + { + return totalCount; } }; - if (logger.isDebugEnabled()) - { - logger.debug("nonCannedGetPeopleQuery(\""+filter+"\", "+maxResults+") "+cqResults.getTotalResultCount()+" in "+(System.currentTimeMillis()-start)+" msecs "); - } - return cqResults; } /** diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java index 692c47cb5b..89791d3758 100644 --- a/source/java/org/alfresco/repo/security/person/PersonTest.java +++ b/source/java/org/alfresco/repo/security/person/PersonTest.java @@ -560,7 +560,6 @@ public class PersonTest extends TestCase personService.deletePerson("Derek"); assertEquals(2, getPeopleCount()); - } public void testPeopleFiltering() @@ -581,6 +580,113 @@ public class PersonTest extends TestCase PagingRequest pr = new PagingRequest(100, null); + List filters = new ArrayList(4); + + filters.clear(); + filters.add(ContentModel.PROP_USERNAME); + assertEquals(2, personService.getPeople("y", filters, null, pr).getPage().size()); + + filters.clear(); + filters.add(ContentModel.PROP_USERNAME); + filters.add(ContentModel.PROP_FIRSTNAME); + filters.add(ContentModel.PROP_LASTNAME); + assertEquals(3, personService.getPeople("b", filters, null, pr).getPage().size()); + + filters.clear(); + filters.add(ContentModel.PROP_USERNAME); + assertEquals(2, personService.getPeople("A", filters, null, pr).getPage().size()); // includes "admin" + + personService.deletePerson("aa"); + + filters.clear(); + filters.add(ContentModel.PROP_USERNAME); + assertEquals(1, personService.getPeople("a", filters, null, pr).getPage().size()); // includes "admin" + + // a* is the same as a + filters.clear(); + filters.add(ContentModel.PROP_USERNAME); + assertEquals(1, personService.getPeople("a*", filters, null, pr).getPage().size()); // includes "admin" + + // * means everyone + filters.clear(); + filters.add(ContentModel.PROP_USERNAME); + assertEquals(5, getPeopleCount()); + assertEquals(5, personService.getPeople("*", filters, null, pr).getPage().size()); + } + + public void testPeopleSortingPaging() + { + personService.setCreateMissingPeople(false); + + assertEquals(2, getPeopleCount()); + + NodeRef p1 = personService.getPerson(AuthenticationUtil.getAdminUserName()); // admin - by default + NodeRef p2 = personService.getPerson(AuthenticationUtil.getGuestUserName()); // guest - by default + + NodeRef p3 = personService.createPerson(createDefaultProperties("aa", "Aa", "Aa", "aa@aa", "alfresco", rootNodeRef)); + NodeRef p4 = personService.createPerson(createDefaultProperties("cc", "Cc", "Cc", "cc@cc", "alfresco", rootNodeRef)); + NodeRef p5 = personService.createPerson(createDefaultProperties("hh", "Hh", "Hh", "hh@hh", "alfresco", rootNodeRef)); + NodeRef p6 = personService.createPerson(createDefaultProperties("bb", "Bb", "Bb", "bb@bb", "alfresco", rootNodeRef)); + NodeRef p7 = personService.createPerson(createDefaultProperties("dd", "Dd", "Dd", "dd@dd", "alfresco", rootNodeRef)); + + + + assertEquals(7, getPeopleCount()); + + List> sort = new ArrayList>(1); + sort.add(new Pair(ContentModel.PROP_USERNAME, true)); + + // page 1 + PagingRequest pr = new PagingRequest(0, 2, null); + PagingResults ppr = personService.getPeople(null, null, sort, pr); + List results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p3, results.get(0).getNodeRef()); + assertEquals(p1, results.get(1).getNodeRef()); + + // page 2 + pr = new PagingRequest(2, 2, null); + ppr = personService.getPeople(null, null, sort, pr); + results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p6, results.get(0).getNodeRef()); + assertEquals(p4, results.get(1).getNodeRef()); + + // page 3 + pr = new PagingRequest(4, 2, null); + ppr = personService.getPeople(null, null, sort, pr); + results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p7, results.get(0).getNodeRef()); + assertEquals(p2, results.get(1).getNodeRef()); + + // page 4 + pr = new PagingRequest(6, 2, null); + ppr = personService.getPeople(null, null, sort, pr); + results = ppr.getPage(); + assertEquals(1, results.size()); + assertEquals(p5, results.get(0).getNodeRef()); + } + + // note: this test can be removed as and when we remove the deprecated "getPeople" impl + public void testPeopleFiltering_deprecatedCQ_via_getChildren() + { + personService.setCreateMissingPeople(false); + + assertEquals(2, getPeopleCount()); + + checkPeopleContain(AuthenticationUtil.getAdminUserName()); + checkPeopleContain(AuthenticationUtil.getGuestUserName()); + + personService.createPerson(createDefaultProperties("aa", "Aa", "Aa", "aa@aa", "alfresco", rootNodeRef)); + personService.createPerson(createDefaultProperties("bc", "c", "C", "bc@bc", "alfresco", rootNodeRef)); + personService.createPerson(createDefaultProperties("yy", "B", "D", "yy@yy", "alfresco", rootNodeRef)); + personService.createPerson(createDefaultProperties("Yz", "yz", "B", "yz@yz", "alfresco", rootNodeRef)); + + assertEquals(6, getPeopleCount()); + + PagingRequest pr = new PagingRequest(100, null); + List> filters = new ArrayList>(4); filters.clear(); @@ -618,7 +724,8 @@ public class PersonTest extends TestCase assertEquals(5, personService.getPeople(filters, true, null, pr).getPage().size()); } - public void testPeopleSortingPaging() + // note: this test can be removed as and when we remove the deprecated "getPeople" impl + public void testPeopleSortingPaging_deprecatedCQ_via_getChildren() { personService.setCreateMissingPeople(false); diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 685d1f4c70..3f683d2c01 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -274,7 +274,25 @@ public interface PersonService /** * Get paged list of people optionally filtered and/or sorted - + * + * Note: the pattern is applied to filter props (0 to 3) as startsWithIgnoreCase, which are OR'ed together, for example: cm:userName or cm:firstName or cm:lastName + * + * @param pattern pattern to apply to filter props - "startsWith" and "ignoreCase" + * @param filterProps list of filter properties (these are OR'ed) + * @param sortProps sort property, eg. cm:username ascending + * @param pagingRequest skip, max + optional query execution id + * @return + * + * @author janv + * @since 4.1.2 + */ + @Auditable(parameters = {"pattern", "filterProps", "sortProps", "pagingRequest"}) + public PagingResults getPeople(String pattern, List filterProps, List> sortProps, PagingRequest pagingRequest); + + + /** + * Get paged list of people optionally filtered and/or sorted + * * @param filterProps list of filter properties (with "startsWith" values), eg. cm:username "al" might match "alex", "alice", ... * @param filterIgnoreCase true to ignore case when filtering, false to be case-sensitive when filtering * @param sortProps sort property, eg. cm:username ascending @@ -282,6 +300,7 @@ public interface PersonService * * @author janv * @since 4.0 + * @deprecated see getPeople(String pattern, List filterProps, List> sortProps, PagingRequest pagingRequest) */ @Auditable(parameters = {"stringPropFilters", "filterIgnoreCase", "sortProps", "pagingRequest"}) public PagingResults getPeople(List> stringPropFilters, boolean filterIgnoreCase, List> sortProps, PagingRequest pagingRequest);