From efbd951a10cb7d7b85b3ccd151477468ddaff9bf Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Wed, 8 Jun 2011 17:29:54 +0000 Subject: [PATCH] Merged DEV/SWIFT to HEAD 27125: Subtasks of ALF-7072: RSOLR 013: Remote API to get ACLs and readers - ALF-8334: RSOLR 013: Modify ACL schema to record change times - ALF-8336: RSOLR 013: DB upgrade scripts for ACL changes - TODO: Query APIs 27128: Added TooManyResultsException as a concurrency detection trigger - Usually too many results indicates that the DB table key is not as specific as it should be, but it's AVM that showed this up. 27132: Clean up: javadocs; non-javadocs; uncommented fields; @since tags; etc. 27134: Removed empty directory 27135: Fix for ALF-8333: CMIS query: JOIN on an aspect results in CmisInvalidArgumentException - incorrect scope used when building orderings 27139: Fixed SORL transaction tracking queries - Queries were using incompatible boolean comparisons - Added SOLRDAO to test suite - Cleaned up code and reformatted code 27141: Minor additions to CannedQuery interface - get parameter bean - construct sort details from a list - ALF-7167: Canned queries 27146: RINF 09 / RINF 10: DB-based paged query for get children (DocLib & CMIS) - milestone check-in for sprint demo & review (WIP) - added new FileFolderService (paged) list query (public API is subject to change) - moved temp JavaScript sorting to Java - example usage by DocLib (via ScriptNode) and CMIS (via AlfrescoCmisService) - implemented as demo "canned query" including embedded use of "list" permission interceptor - ALF-7402 / ALF-7168 27150: RINF 09 / RINF 10: DB-based paged query for get children (DocLib & CMIS) - missed file (follow-on to r27146) 27158: ALF-7070, ALF-7072: SOLR tracking (node and changeset) - Pulled non-DAO code into SOLRTrackingComponent - DAO code and related tests just test basic CRUD - SOLRTrackingComponent does complex cross-schema manipulation 27159: Fixed line ending and removed svn:eol-style 27160: ALF-8334: RSOLR 013: Fixed SQL Server syntax 27165: RINF 09 / RINF 10: DB-based paged query for get children (DocLib & CMIS) - fix listDeepFolders (causing Imap*Test to fail) - all private methods now order files followed by folders (consistent with existing public APIs such as FileFolderService.search & ScriptNode.childFileFolders*) - follow-on to r27146 28271: Consolidate diagnostic logging for max perm checks (ALF-8388 + ALF-8419) - note: this should be a trivial merge to HEAD git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28292 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/bootstrap-context.xml | 1 + config/alfresco/core-services-context.xml | 11 + config/alfresco/dao/dao-context.xml | 4 - .../AlfrescoCreate-RepoTables.sql | 3 +- .../AlfrescoCreate-RepoTables.sql | 3 +- .../AclChangeSet-Tracking.sql | 36 + .../AclChangeSet-Tracking.sql | 38 + .../alfresco/ibatis/alfresco-SqlMapConfig.xml | 6 +- .../permissions-common-SqlMap.xml | 18 +- .../solr-common-SqlMap.xml | 21 +- .../model-specific-services-context.xml | 17 +- .../alfresco/patch/patch-services-context.xml | 34 +- config/alfresco/version.properties | 4 +- .../opencmis/AlfrescoCmisService.java | 82 +- .../org/alfresco/opencmis/CMISConnector.java | 83 +- .../opencmis/search/CMISQueryParser.java | 5 +- .../ResetWCMToGroupBasedPermissionsPatch.java | 2 +- .../alfresco/repo/domain/DomainTestSuite.java | 2 + .../permissions/AbstractAclCrudDAOImpl.java | 30 +- .../permissions/AclChangeSetEntity.java | 17 +- .../repo/domain/permissions/AclCrudDAO.java | 7 +- .../domain/permissions/AclCrudDAOTest.java | 20 +- .../repo/domain/permissions/AclDAOImpl.java | 33 +- .../permissions/ibatis/AclCrudDAOImpl.java | 21 +- .../repo/domain/solr/AclChangeSet.java | 32 + .../repo/domain/solr/AclChangeSetEntity.java | 57 + .../domain/solr/NodeParametersEntity.java | 139 ++ .../alfresco/repo/domain/solr/SOLRDAO.java | 177 +-- .../repo/domain/solr/SOLRDAOTest.java | 1349 ++--------------- .../domain/solr/SOLRTrackingParameters.java | 124 ++ .../solr/SOLRTransactionParameters.java | 94 -- .../repo/domain/solr/Transaction.java | 64 +- .../repo/domain/solr/TransactionEntity.java | 186 +-- .../repo/domain/solr/ibatis/SOLRDAOImpl.java | 1209 +++++++-------- .../org/alfresco/repo/jscript/ScriptNode.java | 104 +- .../repo/jscript/ScriptPagingNodes.java | 59 + .../filefolder/FileFolderServiceImpl.java | 187 ++- .../repo/model/filefolder/FileInfoImpl.java | 11 +- .../filefolder/GetChildrenCannedQuery.java | 318 ++++ .../GetChildrenCannedQueryFactory.java | 55 + .../GetChildrenCannedQueryParams.java} | 96 +- .../filefolder/PagingFileInfoResultsImpl.java | 57 + .../ACLEntryAfterInvocationProvider.java | 24 +- .../solr/MetaDataResultsFilter.java | 2 +- .../NodeMetaData.java} | 5 +- .../solr/NodeMetaDataParameters.java | 2 +- .../{domain => }/solr/NodeParameters.java | 402 +++-- .../repo/solr/SOLRTrackingComponent.java | 99 ++ .../repo/solr/SOLRTrackingComponentImpl.java | 546 +++++++ .../repo/solr/SOLRTrackingComponentTest.java | 1033 +++++++++++++ .../RetryingTransactionHelper.java | 2 + .../service/cmr/model/FileFolderService.java | 12 + .../alfresco/service/cmr/model/FileInfo.java | 5 + .../cmr/model/PagingFileInfoResults.java | 30 + .../service/cmr/model/PagingSortRequest.java | 65 + 55 files changed, 4434 insertions(+), 2609 deletions(-) create mode 100644 config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/AclChangeSet-Tracking.sql create mode 100644 config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/AclChangeSet-Tracking.sql create mode 100644 source/java/org/alfresco/repo/domain/solr/AclChangeSet.java create mode 100644 source/java/org/alfresco/repo/domain/solr/AclChangeSetEntity.java create mode 100644 source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java create mode 100644 source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java delete mode 100644 source/java/org/alfresco/repo/domain/solr/SOLRTransactionParameters.java create mode 100644 source/java/org/alfresco/repo/jscript/ScriptPagingNodes.java create mode 100644 source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java create mode 100644 source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryFactory.java rename source/java/org/alfresco/repo/{domain/solr/NodeMetaData.java => model/filefolder/GetChildrenCannedQueryParams.java} (53%) create mode 100644 source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java rename source/java/org/alfresco/repo/{domain => }/solr/MetaDataResultsFilter.java (98%) rename source/java/org/alfresco/repo/{domain/solr/NodeMetaDataEntity.java => solr/NodeMetaData.java} (96%) rename source/java/org/alfresco/repo/{domain => }/solr/NodeMetaDataParameters.java (98%) rename source/java/org/alfresco/repo/{domain => }/solr/NodeParameters.java (74%) create mode 100644 source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java create mode 100644 source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java create mode 100644 source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java create mode 100644 source/java/org/alfresco/service/cmr/model/PagingFileInfoResults.java create mode 100644 source/java/org/alfresco/service/cmr/model/PagingSortRequest.java diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 75f88ee3cf..9b94e9dfda 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -139,6 +139,7 @@ + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index ff489c1561..f3015416de 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1186,6 +1186,17 @@ + + + + + + + + + + + diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 493eaf5df7..2410c8a763 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -275,11 +275,7 @@ - - - - diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql index 52ead45470..cc0e4d6b0a 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql @@ -99,7 +99,8 @@ CREATE TABLE alf_access_control_entry CREATE TABLE alf_acl_change_set ( id BIGINT NOT NULL AUTO_INCREMENT, - version BIGINT NOT NULL, + commit_time_ms BIGINT, + KEY idx_alf_acs_ctms (commit_time_ms) PRIMARY KEY (id) ) ENGINE=InnoDB; diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql index 7422996a08..cbcebf639d 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql @@ -106,9 +106,10 @@ CREATE SEQUENCE alf_acl_change_set_seq START WITH 1 INCREMENT BY 1; CREATE TABLE alf_acl_change_set ( id INT8 NOT NULL, - version INT8 NOT NULL, + commit_time_ms INT8, PRIMARY KEY (id) ); +CREATE INDEX idx_alf_acs_ctms ON alf_acl_change_set (commit_time_ms); CREATE SEQUENCE alf_access_control_list_seq START WITH 1 INCREMENT BY 1; CREATE TABLE alf_access_control_list diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/AclChangeSet-Tracking.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/AclChangeSet-Tracking.sql new file mode 100644 index 0000000000..d7c823979b --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/AclChangeSet-Tracking.sql @@ -0,0 +1,36 @@ +-- +-- Title: Update ACL Change Set for Change Tracking +-- Database: MySQL +-- Since: V4.0 Schema 5008 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Rename redundant 'version' to indexed 'commit_time_ms' +ALTER TABLE alf_acl_change_set + CHANGE COLUMN version commit_time_ms BIGINT(20) NULL; + +-- Fill with data +--FOREACH alf_acl_change_set.id system.upgrade.alf_acl_change_set.batchsize +UPDATE alf_acl_change_set + SET + commit_time_ms = id + WHERE + id >= ${LOWERBOUND} AND id <= ${UPPERBOUND} +; + +-- Add index on new data +CREATE INDEX idx_alf_acs_ctms ON alf_acl_change_set (commit_time_ms); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-AclChangeSet'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-AclChangeSet', 'Manually executed script upgrade V4.0: Update ACL Change Set for Change Tracking', + 0, 5007, -1, 5008, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/AclChangeSet-Tracking.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/AclChangeSet-Tracking.sql new file mode 100644 index 0000000000..28db644a26 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/AclChangeSet-Tracking.sql @@ -0,0 +1,38 @@ +-- +-- Title: Update ACL Change Set for Change Tracking +-- Database: PostgreSQL +-- Since: V4.0 Schema 5008 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Rename redundant 'version' to indexed 'commit_time_ms' +ALTER TABLE alf_acl_change_set + RENAME "version" TO commit_time_ms; +ALTER TABLE alf_acl_change_set + ALTER COLUMN commit_time_ms DROP NOT NULL; + +-- Fill with data +--FOREACH alf_acl_change_set.id system.upgrade.alf_acl_change_set.batchsize +UPDATE alf_acl_change_set + SET + commit_time_ms = id + WHERE + id >= ${LOWERBOUND} AND id <= ${UPPERBOUND} +; + +-- Add index on new data +CREATE INDEX idx_alf_acs_ctms ON alf_acl_change_set (commit_time_ms); + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-AclChangeSet'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-AclChangeSet', 'Manually executed script upgrade V4.0: Update ACL Change Set for Change Tracking', + 0, 5007, -1, 5008, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml index 4b526d63d4..537bfa59f4 100644 --- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -107,9 +107,9 @@ Inbound settings from iBatis - - - + + + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml index 78144f533d..ad07c5e162 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/permissions-common-SqlMap.xml @@ -159,16 +159,16 @@ insert into alf_acl_change_set - (version) + (commit_time_ms) values - (#{version}) + (null) insert into alf_acl_change_set - (id, version) + (id, commit_time_ms) values - (#{id}, #{version}) + (#{id}, null) @@ -270,6 +270,16 @@ + + + update + alf_acl_change_set + set + commit_time_ms = #{commitTimeMs,jdbcType=BIGINT} + where + id = #{id} + + update diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/solr-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/solr-common-SqlMap.xml index c572a62e75..046951ebc3 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/solr-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/solr-common-SqlMap.xml @@ -4,7 +4,7 @@ - + @@ -20,7 +20,7 @@ - select txn.id as id, txn.commit_time_ms as commit_time_ms, @@ -30,7 +30,7 @@ alf_node node where txn.id = node.transaction_id and - node.node_deleted = 0 + node.node_deleted = #{false} ) as updates, (select count(node.id) @@ -38,25 +38,22 @@ alf_node node where txn.id = node.transaction_id and - node.node_deleted = 1 + node.node_deleted = #{true} ) as deletes from alf_transaction txn - - = #{txnFromCommitTime}]]> + + = #{fromCommitTimeInclusive}]]> - - = #{minTxnId}]]> + + = #{fromIdInclusive}]]> order by txn.commit_time_ms ASC, txn.id ASC - - select node.id as id, node.node_deleted as node_deleted, diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml index 6bb9bb0de7..d7af4663d2 100644 --- a/config/alfresco/model-specific-services-context.xml +++ b/config/alfresco/model-specific-services-context.xml @@ -13,7 +13,8 @@ - + + http://www.alfresco.org/model/application/1.0 @@ -134,6 +135,16 @@ org.alfresco.repo.imap.ImapService - - + + + + + + + + + + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 9965dce8fc..21378dc101 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -2525,8 +2525,8 @@ - - + + classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/link-validation-metadata-removing.sql @@ -2535,10 +2535,9 @@ patch.mtFixAdminExistingTenants patch.mtFixAdminExistingTenants.description - 0 - 4114 - 4115 - + + + @@ -2559,9 +2558,9 @@ patch.fixUserQNames patch.fixUserQNames.description - 0 - 4115 - 4116 + + + @@ -2787,7 +2786,7 @@ - + patch.db-V3.4-JBPM-FK-indexes patch.schemaUpgradeScript.description @@ -2809,7 +2808,7 @@ - + patch.imap.clear.old.messages patch.imap.clear.old.messages.description @@ -2838,5 +2837,16 @@ - + + + + + + + + + classpath:alfresco/dbscripts/upgrade/4.0/${db.script.dialect}/AclChangeSet-Tracking.sql + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 0f500c0478..5db20260fb 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -7,7 +7,7 @@ version.major=4 version.minor=0 version.revision=0 -version.label=unstable +version.label=a # Edition label @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=5007 +version.schema=5008 diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java index 3d1199d743..f6bea0a3cd 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java @@ -42,6 +42,7 @@ import org.alfresco.cmis.CMISInvalidArgumentException; import org.alfresco.model.ContentModel; import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.PropertyDefintionWrapper; import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; import org.alfresco.repo.content.encoding.ContentCharsetFinder; import org.alfresco.repo.node.integrity.IntegrityException; @@ -57,12 +58,15 @@ import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.model.PagingFileInfoResults; +import org.alfresco.service.cmr.model.PagingSortRequest; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.EntityRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.PagingSortProp; import org.alfresco.service.cmr.search.QueryParameterDefinition; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; @@ -126,6 +130,8 @@ import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.chemistry.opencmis.commons.server.ObjectInfo; import org.apache.chemistry.opencmis.commons.server.RenditionInfo; import org.apache.chemistry.opencmis.commons.spi.Holder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * OpenCMIS service object. @@ -134,6 +140,8 @@ import org.apache.chemistry.opencmis.commons.spi.Holder; */ public class AlfrescoCmisService extends AbstractCmisService { + private static Log logger = LogFactory.getLog(AlfrescoCmisService.class); + private CMISConnector connector; private CallContext context; private UserTransaction txn; @@ -501,58 +509,102 @@ public class AlfrescoCmisService extends AbstractCmisService Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) { + long start = System.currentTimeMillis(); + checkRepositoryId(repositoryId); - + // convert BigIntegers to int int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); - + ObjectInFolderListImpl result = new ObjectInFolderListImpl(); List list = new ArrayList(); result.setObjects(list); - + // get the children references NodeRef folderNodeRef = connector.getFolderNodeRef("Folder", folderId); - List childrenList = connector.getNodeService().getChildAssocs(folderNodeRef); - + + // TEMP - impl subject to change + + // convert orderBy to sortProps + List sortProps = null; + if (orderBy != null) + { + sortProps = new ArrayList(1); + + String[] parts = orderBy.split(","); + if (parts.length > 0) + { + for (int i = 0; i < parts.length; i++) + { + String[] sort = parts[i].split(" "); // TODO support multiple spaces + + if (sort.length > 0) + { + PropertyDefintionWrapper propDef = connector.getOpenCMISDictionaryService().findPropertyByQueryName(sort[0]); + if (propDef != null) + { + QName sortProp = propDef.getPropertyAccessor().getMappedProperty(); + if (sortProp != null) + { + boolean sortAsc = ((sort.length == 1) || (sortAsc = (sort[1].equalsIgnoreCase("asc")))); + sortProps.add(new PagingSortProp(sortProp, sortAsc)); + } + } + } + } + } + } + + PagingSortRequest pageRequest = new PagingSortRequest(skipCount.intValue(), maxItems.intValue(), true, sortProps); + PagingFileInfoResults pageOfNodeInfos = connector.getFileFolderService().list(folderNodeRef, true, true, null, pageRequest); + List childrenList = pageOfNodeInfos.getResultsForPage(); + if (max > 0) { int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1; for (int i = skip; i <= lastIndex; i++) { - ChildAssociationRef child = childrenList.get(i); - + FileInfo child = childrenList.get(i); + try { // create a child CMIS object - ObjectData object = connector.createCMISObject(child.getChildRef(), filter, - includeAllowableActions, includeRelationships, renditionFilter, false, false); + ObjectData object = connector.createCMISObject(child, filter, + includeAllowableActions, includeRelationships, renditionFilter, false, false); + if (context.isObjectInfoRequired()) { getObjectInfo(repositoryId, object.getId()); } - + ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl(); childData.setObject(object); - + // include path segment if (includePathSegment) { - childData.setPathSegment(connector.getName(child.getChildRef())); + childData.setPathSegment(child.getName()); } - + // add it list.add(childData); + } catch (InvalidNodeRefException e) { // ignore invalid children } } } - + result.setHasMoreItems(childrenList.size() - skip > result.getObjects().size()); result.setNumItems(BigInteger.valueOf(childrenList.size())); - + + if (logger.isDebugEnabled()) + { + logger.debug("getChildren: "+childrenList.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); + } + return result; } diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index e0e76ced71..5e753fa279 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -32,10 +32,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import javax.xml.datatype.DatatypeConfigurationException; @@ -45,16 +45,18 @@ import org.alfresco.model.ContentModel; import org.alfresco.opencmis.dictionary.CMISActionEvaluator; import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum; import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.CMISPropertyAccessor; import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; import org.alfresco.opencmis.dictionary.PropertyDefintionWrapper; import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.opencmis.mapping.DirectProperty; import org.alfresco.opencmis.search.CMISQueryOptions; -import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; import org.alfresco.opencmis.search.CMISQueryService; import org.alfresco.opencmis.search.CMISResultSet; import org.alfresco.opencmis.search.CMISResultSetColumn; import org.alfresco.opencmis.search.CMISResultSetRow; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -74,6 +76,7 @@ import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidAspectException; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.AspectMissingException; @@ -87,8 +90,8 @@ import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.cmr.repository.Path.ChildAssocElement; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.Path.ChildAssocElement; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AccessPermission; @@ -1015,6 +1018,11 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen public TypeDefinitionWrapper getType(NodeRef nodeRef) { QName typeQName = nodeService.getType(nodeRef); + return getType(typeQName); + } + + private TypeDefinitionWrapper getType(QName typeQName) + { return cmisDictionaryService.findNodeType(typeQName); } @@ -1142,6 +1150,23 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen /** * Creates the CMIS object for a node. */ + public ObjectData createCMISObject(FileInfo node, String filter, boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, + boolean includeAcl) + { + NodeRef nodeRef = node.getNodeRef(); + TypeDefinitionWrapper type = getType(node.getType()); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + Properties nodeProps = getNodeProperties(node, filter, type); + + return createCMISObjectImpl(nodeRef, type, nodeProps, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + } + public ObjectData createCMISObject(NodeRef nodeRef, String filter, boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, boolean includeAcl) @@ -1151,7 +1176,17 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); } - + + Properties nodeProps = getNodeProperties(nodeRef, filter, type); + + return createCMISObjectImpl(nodeRef, type, nodeProps, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePolicyIds, includeAcl); + } + + private ObjectData createCMISObjectImpl(NodeRef nodeRef, TypeDefinitionWrapper type, Properties nodeProps, String filter, boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, + boolean includeAcl) + { // get the current version NodeRef currentVersionNodeRef = nodeRef; if (type instanceof DocumentTypeDefinitionWrapper) @@ -1178,7 +1213,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen ObjectDataImpl result = new ObjectDataImpl(); // set properties - result.setProperties(getNodeProperties(nodeRef, filter, type)); + result.setProperties(nodeProps); // set allowable actions if (includeAllowableActions) @@ -1427,6 +1462,44 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen return result; } + + public Properties getNodeProperties(FileInfo node, String filter, TypeDefinitionWrapper type) + { + PropertiesImpl result = new PropertiesImpl(); + + Set filterSet = splitFilter(filter); + + Map nodeProps = node.getProperties(); + + for (PropertyDefintionWrapper propDef : type.getProperties()) + { + if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID)) + { + // don't filter the object id + if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + } + + Serializable value = null; + + CMISPropertyAccessor accessor = propDef.getPropertyAccessor(); + if (accessor instanceof DirectProperty) + { + value = nodeProps.get(accessor.getMappedProperty()); + } + else + { + value = propDef.getPropertyAccessor().getValue(node.getNodeRef()); + } + + result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value)); + } + + return result; + } public Properties getAssocProperties(AssociationRef assocRef, String filter, TypeDefinitionWrapper type) { diff --git a/source/java/org/alfresco/opencmis/search/CMISQueryParser.java b/source/java/org/alfresco/opencmis/search/CMISQueryParser.java index 3e7cc25ff4..a94134b1f0 100644 --- a/source/java/org/alfresco/opencmis/search/CMISQueryParser.java +++ b/source/java/org/alfresco/opencmis/search/CMISQueryParser.java @@ -784,7 +784,7 @@ public class CMISQueryParser } TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), - BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER); + validScopes); if (typeDef == null) { throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " @@ -859,8 +859,7 @@ public class CMISQueryParser } } - TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), - BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER); + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), validScopes); if (typeDef == null) { throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ResetWCMToGroupBasedPermissionsPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ResetWCMToGroupBasedPermissionsPatch.java index 3f5cefbc71..ccda25be23 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/ResetWCMToGroupBasedPermissionsPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ResetWCMToGroupBasedPermissionsPatch.java @@ -524,7 +524,7 @@ public class ResetWCMToGroupBasedPermissionsPatch extends MoveWCMToGroupBasedPer } // create acl change set, defining acl and shared acl - long aclChangeSet = aclCrudDAO.createAclChangeSet(); + Long aclChangeSet = aclCrudDAO.createAclChangeSet(); long definingAclId = helper.createWCMGroupBasedAcl(stagingStoreName, aclChangeSet, ACLType.DEFINING, false); long sharedAclId = helper.createWCMGroupBasedAcl(stagingStoreName, aclChangeSet, ACLType.SHARED, false); diff --git a/source/java/org/alfresco/repo/domain/DomainTestSuite.java b/source/java/org/alfresco/repo/domain/DomainTestSuite.java index d0e1cb5f98..3c0ec62411 100644 --- a/source/java/org/alfresco/repo/domain/DomainTestSuite.java +++ b/source/java/org/alfresco/repo/domain/DomainTestSuite.java @@ -33,6 +33,7 @@ import org.alfresco.repo.domain.permissions.AclCrudDAOTest; import org.alfresco.repo.domain.propval.PropertyValueDAOTest; import org.alfresco.repo.domain.qname.QNameDAOTest; import org.alfresco.repo.domain.query.CannedQueryDAOTest; +import org.alfresco.repo.domain.solr.SOLRDAOTest; import org.alfresco.repo.domain.usage.UsageDAOTest; /** @@ -60,6 +61,7 @@ public class DomainTestSuite extends TestSuite suite.addTestSuite(AclCrudDAOTest.class); suite.addTestSuite(UsageDAOTest.class); suite.addTestSuite(CannedQueryDAOTest.class); + suite.addTestSuite(SOLRDAOTest.class); return suite; } diff --git a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java index 9c43985a45..93bd9d8dbc 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AbstractAclCrudDAOImpl.java @@ -489,25 +489,39 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO // ACL Change Set // - public long createAclChangeSet() + public Long createAclChangeSet() { return createAclChangeSetEntity(); } - public AclChangeSetEntity getAclChangeSet(long changeSetId) + @Override + public void updateAclChangeSet(Long aclChangeSetEntityId, long commitTimeMs) + { + int updated = updateChangeSetEntity(aclChangeSetEntityId, commitTimeMs); + if (updated != 1) + { + throw new ConcurrencyFailureException("Update by ID should delete exactly 1: " + aclChangeSetEntityId); + } + } + + public AclChangeSetEntity getAclChangeSet(Long changeSetId) { return getAclChangeSetEntity(changeSetId); } - public void deleteAclChangeSet(long changeSetId) + public void deleteAclChangeSet(Long changeSetId) { - deleteAclChangeSetEntity(changeSetId); + int deleted = deleteAclChangeSetEntity(changeSetId); + if (deleted != 1) + { + throw new ConcurrencyFailureException("Deleted by ID should delete exactly 1: " + changeSetId); + } } - protected abstract long createAclChangeSetEntity(); - protected abstract AclChangeSetEntity getAclChangeSetEntity(long changeSetId); - protected abstract int deleteAclChangeSetEntity(long id); - + protected abstract Long createAclChangeSetEntity(); + protected abstract AclChangeSetEntity getAclChangeSetEntity(Long changeSetId); + protected abstract int deleteAclChangeSetEntity(Long id); + protected abstract int updateChangeSetEntity(Long id, long commitTimeMs); // // Access Control Entry (ACE) diff --git a/source/java/org/alfresco/repo/domain/permissions/AclChangeSetEntity.java b/source/java/org/alfresco/repo/domain/permissions/AclChangeSetEntity.java index d37111066f..62b30bda9e 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclChangeSetEntity.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclChangeSetEntity.java @@ -20,7 +20,6 @@ package org.alfresco.repo.domain.permissions; import org.alfresco.util.EqualsHelper; - /** * Entity for alf_acl_change_set persistence. * @@ -30,7 +29,7 @@ import org.alfresco.util.EqualsHelper; public class AclChangeSetEntity implements AclChangeSet { private Long id; - private Long version; + private Long commitTimeMs; /** * Default constructor @@ -49,16 +48,16 @@ public class AclChangeSetEntity implements AclChangeSet this.id = id; } - public Long getVersion() + public Long getCommitTimeMs() { - return version; + return commitTimeMs; } - - public void setVersion(Long version) + + public void setCommitTimeMs(Long commitTimeMs) { - this.version = version; + this.commitTimeMs = commitTimeMs; } - + @Override public int hashCode() { @@ -89,7 +88,7 @@ public class AclChangeSetEntity implements AclChangeSet StringBuilder sb = new StringBuilder(512); sb.append("AclChangeSetEntity") .append("[ ID=").append(id) - .append(", version=").append(version) + .append(", commitTimeMs=").append(commitTimeMs) .append("]"); return sb.toString(); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java index 39fd9f2f32..d57018ce5b 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAO.java @@ -74,9 +74,10 @@ public interface AclCrudDAO // ACL Change Set // - public long createAclChangeSet(); - public AclChangeSet getAclChangeSet(long aclChangeSetEntityId); - public void deleteAclChangeSet(long aclChangeSetEntityId); + public Long createAclChangeSet(); + public void updateAclChangeSet(Long aclChangeSetEntityId, long commitTimeMs); + public AclChangeSet getAclChangeSet(Long aclChangeSetEntityId); + public void deleteAclChangeSet(Long aclChangeSetEntityId); // // ACL Member diff --git a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAOTest.java b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAOTest.java index c9b9c2dd8a..48179ad2c8 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclCrudDAOTest.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclCrudDAOTest.java @@ -75,7 +75,20 @@ public class AclCrudDAOTest extends TestCase return txnHelper.doInTransaction(callback); } - private void deleteAclChangeSet(final long aclChangeSetId) throws Exception + private void updateAclChangeSet(final Long aclChangeSetId) throws Exception + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + aclCrudDAO.updateAclChangeSet(aclChangeSetId, System.currentTimeMillis()); + return null; + } + }; + txnHelper.doInTransaction(callback); + } + + private void deleteAclChangeSet(final Long aclChangeSetId) throws Exception { RetryingTransactionCallback callback = new RetryingTransactionCallback() { @@ -102,12 +115,13 @@ public class AclCrudDAOTest extends TestCase public void testCreateAndDeleteAclChangeSet() throws Exception { - long aclChangeSetId = createAclChangeSet(); + Long aclChangeSetId = createAclChangeSet(); AclChangeSet acsEntity= getAclChangeSet(aclChangeSetId); assertNotNull(acsEntity); - assertEquals(new Long(aclChangeSetId), acsEntity.getId()); + assertEquals(aclChangeSetId, acsEntity.getId()); + updateAclChangeSet(aclChangeSetId); deleteAclChangeSet(aclChangeSetId); assertNull(getAclChangeSet(aclChangeSetId)); diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java index 7bd8dd3412..2de626c4dc 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java @@ -43,6 +43,7 @@ import org.alfresco.repo.security.permissions.impl.AclChange; import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityType; @@ -1646,19 +1647,47 @@ public class AclDAOImpl implements AclDAO private static final String RESOURCE_KEY_ACL_CHANGE_SET_ID = "acl.change.set.id"; + private UpdateChangeSetListener updateChangeSetListener = new UpdateChangeSetListener(); + /** + * Wrapper to update the current changeset to get the change time correct + * + * @author Derek Hulley + * @since 4.0 + */ + private class UpdateChangeSetListener extends TransactionListenerAdapter + { + @Override + public void beforeCommit(boolean readOnly) + { + if (readOnly) + { + return; + } + Long changeSetId = (Long) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_ACL_CHANGE_SET_ID); + if (changeSetId == null) + { + // There has not been a change + return; + } + // Update it + long commitTimeMs = System.currentTimeMillis(); + aclCrudDAO.updateAclChangeSet(changeSetId, commitTimeMs); + } + } /** * Support to get the current ACL change set and bind this to the transaction. So we only make one new version of an * ACL per change set. If something is in the current change set we can update it. */ private long getCurrentChangeSetId() { - Long changeSetId = (Long)AlfrescoTransactionSupport.getResource(RESOURCE_KEY_ACL_CHANGE_SET_ID); + Long changeSetId = (Long) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_ACL_CHANGE_SET_ID); if (changeSetId == null) { changeSetId = aclCrudDAO.createAclChangeSet(); - // bind the id + // bind the ID and the listener AlfrescoTransactionSupport.bindResource(RESOURCE_KEY_ACL_CHANGE_SET_ID, changeSetId); + AlfrescoTransactionSupport.bindListener(updateChangeSetListener); if (logger.isDebugEnabled()) { logger.debug("New change set = " + changeSetId); diff --git a/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java index 2c58fba63d..f8760f1ab0 100644 --- a/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/ibatis/AclCrudDAOImpl.java @@ -61,6 +61,7 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl private static final String DELETE_ACL_MEMBERS_BY_ACL = "alfresco.permissions.delete_AclMembersByAclId"; private static final String INSERT_ACL_CHANGESET = "alfresco.permissions.insert.insert_AclChangeSet"; + private static final String UPDATE_ACL_CHANGESET = "alfresco.permissions.update_AclChangeSet"; private static final String SELECT_ACL_CHANGESET_BY_ID = "alfresco.permissions.select_AclChangeSetById"; private static final String DELETE_ACL_CHANGESET = "alfresco.permissions.delete_AclChangeSet"; @@ -236,17 +237,15 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl } @Override - protected long createAclChangeSetEntity() + protected Long createAclChangeSetEntity() { AclChangeSetEntity entity = new AclChangeSetEntity(); - entity.setVersion(0L); template.insert(INSERT_ACL_CHANGESET, entity); - Long id = entity.getId(); - return (id != null ? id : -1); + return entity.getId(); } @Override - protected AclChangeSetEntity getAclChangeSetEntity(long aclChangeSetEntityId) + protected AclChangeSetEntity getAclChangeSetEntity(Long aclChangeSetEntityId) { Map params = new HashMap(1); params.put("id", aclChangeSetEntityId); @@ -255,7 +254,7 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl } @Override - protected int deleteAclChangeSetEntity(long aclChangeSetEntityId) + protected int deleteAclChangeSetEntity(Long aclChangeSetEntityId) { Map params = new HashMap(1); params.put("id", aclChangeSetEntityId); @@ -263,6 +262,16 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl return template.delete(DELETE_ACL_CHANGESET, params); } + @Override + protected int updateChangeSetEntity(Long id, long commitTimeMs) + { + AclChangeSetEntity entity = new AclChangeSetEntity(); + entity.setId(id); + entity.setCommitTimeMs(commitTimeMs); + + return template.update(UPDATE_ACL_CHANGESET, entity); + } + @Override protected long createAceEntity(AceEntity entity) { diff --git a/source/java/org/alfresco/repo/domain/solr/AclChangeSet.java b/source/java/org/alfresco/repo/domain/solr/AclChangeSet.java new file mode 100644 index 0000000000..5eb8c0297e --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/AclChangeSet.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +/** + * Interface for SOLR changeset objects. + * + * @author Derek Hulley + * @since 4.0 + */ +public interface AclChangeSet +{ + public Long getId(); + public Long getCommitTimeMs(); + public int getAclCount(); +} diff --git a/source/java/org/alfresco/repo/domain/solr/AclChangeSetEntity.java b/source/java/org/alfresco/repo/domain/solr/AclChangeSetEntity.java new file mode 100644 index 0000000000..43fa25501c --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/AclChangeSetEntity.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +/** + * Interface for SOLR changeset objects. + * + * @author Derek Hulley + * @since 4.0 + */ +public class AclChangeSetEntity implements AclChangeSet +{ + private Long id; + private Long commitTimeMs; + private int aclCount; + + public Long getId() + { + return id; + } + public void setId(Long id) + { + this.id = id; + } + public Long getCommitTimeMs() + { + return commitTimeMs; + } + public void setCommitTimeMs(Long commitTimeMs) + { + this.commitTimeMs = commitTimeMs; + } + public int getAclCount() + { + return aclCount; + } + public void setAclCount(int aclCount) + { + this.aclCount = aclCount; + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java b/source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java new file mode 100644 index 0000000000..d6ef839e0c --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/NodeParametersEntity.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.solr.NodeParameters; + +/** + * Stores node query parameters for use in SOLR DAO queries + * + * @since 4.0 + */ +public class NodeParametersEntity extends NodeParameters +{ + private List includeTypeIds; + private List excludeTypeIds; + + private List includeAspectIds; + private List excludeAspectIds; + + /** + * Public constructor, but not generally useful + */ + public NodeParametersEntity() + { + + } + + /** + * Construct from higher-level parameters + */ + public NodeParametersEntity(NodeParameters params, QNameDAO qnameDAO) + { + this.setFromNodeId(params.getFromNodeId()); + this.setToNodeId(params.getToNodeId()); + + this.setFromTxnId(params.getFromTxnId()); + this.setToTxnId(params.getToTxnId()); + this.setTransactionIds(params.getTransactionIds()); + + this.setStoreIdentifier(params.getStoreIdentifier()); + this.setStoreProtocol(params.getStoreProtocol()); + + // Translate the QNames, if provided + if (params.getIncludeNodeTypes() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getIncludeNodeTypes(), false); + this.setIncludeTypeIds(new ArrayList(qnamesIds)); + } + + if (params.getExcludeNodeTypes() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getExcludeNodeTypes(), false); + this.setExcludeTypeIds(new ArrayList(qnamesIds)); + } + + if (params.getExcludeAspects() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getExcludeAspects(), false); + this.setExcludeAspectIds(new ArrayList(qnamesIds)); + } + + if (params.getIncludeAspects() != null) + { + Set qnamesIds = qnameDAO.convertQNamesToIds(params.getIncludeAspects(), false); + this.setIncludeAspectIds(new ArrayList(qnamesIds)); + } + } + + public boolean getStoreFilter() + { + return (getStoreProtocol() != null || getStoreIdentifier() != null); + } + + public List getIncludeAspectIds() + { + return includeAspectIds; + } + + public void setIncludeAspectIds(List includeAspectIds) + { + this.includeAspectIds = includeAspectIds; + } + + public List getExcludeAspectIds() + { + return excludeAspectIds; + } + + public void setExcludeAspectIds(List excludeAspectIds) + { + this.excludeAspectIds = excludeAspectIds; + } + + public List getIncludeTypeIds() + { + return includeTypeIds; + } + + public void setIncludeTypeIds(List includeTypeIds) + { + this.includeTypeIds = includeTypeIds; + } + + public List getExcludeTypeIds() + { + return excludeTypeIds; + } + + public void setExcludeTypeIds(List excludeTypeIds) + { + this.excludeTypeIds = excludeTypeIds; + } + + public boolean isIncludeNodesTable() + { + return (getFromNodeId() != null || getToNodeId() != null || getIncludeTypeIds() != null || getExcludeTypeIds() != null || getIncludeAspectIds() != null || getExcludeAspectIds() != null); + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRDAO.java b/source/java/org/alfresco/repo/domain/solr/SOLRDAO.java index 75c7c74571..ece69d9659 100644 --- a/source/java/org/alfresco/repo/domain/solr/SOLRDAO.java +++ b/source/java/org/alfresco/repo/domain/solr/SOLRDAO.java @@ -1,88 +1,89 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr; - -import java.util.List; - -import org.alfresco.repo.domain.node.Node; - -/** - * DAO support for SOLR web scripts. - * - * @since 4.0 - */ -// TODO - permit shortened form of QNames for e.g. aspects i.e. cm:content vs {http://www.alfresco.org/model/content/1.0}content? -public interface SOLRDAO -{ - /** - * Get the transactions from either minTxnId or fromCommitTime, optionally limited to maxResults - * - * @param minTxnId greater than or equal to minTxnId - * @param fromCommitTime greater than or equal to transaction commit time - * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results - * @return list of transactions - */ - public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults); - - /** - * Get the nodes satisfying the constraints in nodeParameters - * - * @param nodeParameters set of constraints for which nodes to return - * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results - * @param callback a callback to receive the results - */ - public void getNodes(NodeParameters nodeParameters, NodeQueryCallback callback); - - /** - * Returns metadata for a set of node ids - * - * @param nodeIds a set of nodeIds for which to return node metadata - * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results - * @param callback a callback to receive the results - */ - public void getNodesMetadata(NodeMetaDataParameters nodeMetaDataParameters, MetaDataResultsFilter resultFilter, NodeMetaDataQueryCallback callback); - - /** - * The interface that will be used to give query results to the calling code. - */ - public static interface NodeQueryCallback - { - /** - * Handle a node. - * - * @param node the node - * @return Return true to continue processing rows or false to stop - */ - boolean handleNode(Node node); - } - - /** - * The interface that will be used to give query results to the calling code. - */ - public static interface NodeMetaDataQueryCallback - { - /** - * Handle a node. - * - * @param node the node meta data - * @return Return true to continue processing rows or false to stop - */ - boolean handleNodeMetaData(NodeMetaData nodeMetaData); - } -} +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +import java.util.List; + +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.solr.NodeParameters; + +/** + * DAO support for SOLR web scripts. + * + * @since 4.0 + */ +public interface SOLRDAO +{ + /** + * Get the ACL changesets for given range parameters + * + * @param minAclChangeSetId minimum ACL changeset ID - (inclusive and optional) + * @param fromCommitTime minimum ACL commit time - (inclusive and optional) + * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results + * @return list of ACL changesets + */ + public List getAclChangeSets(Long minAclChangeSetId, Long fromCommitTime, int maxResults); + + /** + * Get the transactions from either minTxnId or fromCommitTime, optionally limited to maxResults + * + * @param minTxnId greater than or equal to minTxnId + * @param fromCommitTime greater than or equal to transaction commit time + * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results + * @return list of transactions + */ + public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults); + + /** + * Get the nodes satisfying the constraints in nodeParameters + * + * @param nodeParameters set of constraints for which nodes to return + * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results + * @return list of matching nodes + */ + public List getNodes(NodeParameters nodeParameters); + +// /** +// * The interface that will be used to give query results to the calling code. +// */ +// public static interface NodeQueryCallback +// { +// /** +// * Handle a node. +// * +// * @param node the node +// * @return Return true to continue processing rows or false to stop +// */ +// boolean handleNode(Node node); +// } +// +// /** +// * The interface that will be used to give query results to the calling code. +// */ +// public static interface NodeMetaDataQueryCallback +// { +// /** +// * Handle a node. +// * +// * @param node the node meta data +// * @return Return true to continue processing rows or false to stop +// */ +// boolean handleNodeMetaData(NodeMetaData nodeMetaData); +// } +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java b/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java index 20e4f47942..c95215f5da 100644 --- a/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java +++ b/source/java/org/alfresco/repo/domain/solr/SOLRDAOTest.java @@ -1,1206 +1,143 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import junit.framework.TestCase; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.node.ContentDataWithId; -import org.alfresco.repo.domain.node.Node; -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.repo.domain.solr.SOLRDAO.NodeMetaDataQueryCallback; -import org.alfresco.repo.domain.solr.SOLRDAO.NodeQueryCallback; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.MLText; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.PropertyMap; -import org.springframework.context.ConfigurableApplicationContext; - -/** - * Tests for the SOLR DAO - * - * @since 4.0 - */ -// TODO test fromTxnId, toTxnId for getNodes -// TODO nodes created and deleted in the txns will be reported back as deleted. Is this desirable? -// TODO getNodes fromId, toId test -// TODO test filtering for getNodeMetaData -public class SOLRDAOTest extends TestCase -{ - private ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext(); - private static enum NodeStatus - { - UPDATED, DELETED; - } - - private AuthenticationComponent authenticationComponent; - private TransactionService transactionService; - private RetryingTransactionHelper txnHelper; - private NodeService nodeService; - private FileFolderService fileFolderService; - private NodeDAO nodeDAO; - private SOLRDAO solrDAO; - - private StoreRef storeRef; - private NodeRef rootNodeRef; - - @Override - public void setUp() throws Exception - { - ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - transactionService = serviceRegistry.getTransactionService(); - txnHelper = transactionService.getRetryingTransactionHelper(); - - solrDAO = (SOLRDAO)ctx.getBean("solrDAO"); - nodeDAO = (NodeDAO)ctx.getBean("nodeDAO"); - nodeService = (NodeService)ctx.getBean("NodeService"); - fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); - authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); - - authenticationComponent.setSystemUserAsCurrentUser(); - - storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis()); - rootNodeRef = nodeService.getRootNode(storeRef); - } -/* - public void testQueryTransactions1() - { - long startTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest1(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testQueryTransactions1", true, true); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, startTime, 0); - - int[] updates = new int[] {1, 1}; - int[] deletes = new int[] {0, 1}; - List txnIds = checkTransactions(txns, 2, updates, deletes); - - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - - //assertEquals("Unexpected nodes", 3, bt.getSuccessCount()); - } - - public void testQueryTransactions2() - { - long startTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest2(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testQueryTransactions2", true, true); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, startTime, 0); - - int[] updates = new int[] {1, 1}; - int[] deletes = new int[] {0, 1}; - List txnIds = new ArrayList(txns.size()); - int i = 0; - for(Transaction txn : txns) - { - assertEquals("Number of deletes is incorrect", deletes[i], txn.getDeletes()); - assertEquals("Number of updates is incorrect", updates[i], txn.getUpdates()); - i++; - - txnIds.add(txn.getId()); - } - - //TestNodeQueryCallback nodeQueryCallback = new TestNodeQueryCallback(bt.getNodes()); - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - - //assertEquals("Unexpected nodes", 3, nodeQueryCallback.getSuccessCount()); - } - - public void testGetNodeMetaData() - { - long startTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest3(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, startTime, 0); - - int[] updates = new int[] {1, 1}; - int[] deletes = new int[] {0, 1}; - List txnIds = checkTransactions(txns, 2, updates, deletes); - - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - -// assertEquals("Unxpected number of nodes", 3, nodeQueryCallback.getSuccessCount()); - - NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); - nodeMetaDataParams.setNodeIds(st.getNodeIds()); - getNodeMetaData(nodeMetaDataParams, null, st); - -// assertEquals("Unxpected number of nodes", 3, bt.getSuccessCount()); - } - - public void testGetNodeMetaData100Nodes() - { - long startTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest100Nodes(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, startTime, 0); - - int[] updates = new int[] {100}; - int[] deletes = new int[] {0}; - List txnIds = checkTransactions(txns, 1, updates, deletes); - - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - -// assertEquals("Unxpected number of nodes", 3, nodeQueryCallback.getSuccessCount()); - - NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); - nodeMetaDataParams.setNodeIds(st.getNodeIds()); - getNodeMetaData(nodeMetaDataParams, null, st); - - nodeMetaDataParams.setMaxResults(20); - getNodeMetaData(nodeMetaDataParams, null, st); - -// assertEquals("Unxpected number of nodes", 3, bt.getSuccessCount()); - } - - public void testNodeMetaDataManyNodes() throws Exception - { - long fromCommitTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest4(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataManyNodes", true, false); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, fromCommitTime, 0); - - int[] updates = new int[] {2001}; - int[] deletes = new int[] {0}; - List txnIds = checkTransactions(txns, 1, updates, deletes); - - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - - // make sure caches are warm - time last call - System.out.println("Cold cache"); - NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); - nodeMetaDataParams.setNodeIds(st.getNodeIds()); - getNodeMetaData(nodeMetaDataParams, null, st); - getNodeMetaData(nodeMetaDataParams, null, st); - System.out.println("Warm cache"); - getNodeMetaData(nodeMetaDataParams, null, st); - - // clear out node caches - nodeDAO.clear(); - - System.out.println("Cold cache - explicit clear"); - nodeMetaDataParams.setMaxResults(800); - getNodeMetaData(nodeMetaDataParams, null, st); - getNodeMetaData(nodeMetaDataParams, null, st); - System.out.println("Warm cache"); - getNodeMetaData(nodeMetaDataParams, null, st); - - System.out.println("Cold cache - explicit clear"); - nodeMetaDataParams.setMaxResults(500); - getNodeMetaData(nodeMetaDataParams, null, st); - getNodeMetaData(nodeMetaDataParams, null, st); - System.out.println("Warm cache"); - getNodeMetaData(nodeMetaDataParams, null, st); - - System.out.println("Cold cache - explicit clear"); - nodeMetaDataParams.setMaxResults(200); - getNodeMetaData(nodeMetaDataParams, null, st); - getNodeMetaData(nodeMetaDataParams, null, st); - System.out.println("Warm cache"); - getNodeMetaData(nodeMetaDataParams, null, st); - - // clear out node caches - nodeDAO.clear(); - - System.out.println("Cold cache - explicit clear"); - getNodeMetaData(nodeMetaDataParams, null, st); - } - - public void testNodeMetaDataCache() throws Exception - { - long fromCommitTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest4(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataManyNodes", true, false); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, fromCommitTime, 0); - - int[] updates = new int[] {2001}; - int[] deletes = new int[] {0}; - List txnIds = checkTransactions(txns, 1, updates, deletes); - - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - - // clear out node caches - nodeDAO.clear(); - - System.out.println("Cold cache - explicit clear"); - NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); - nodeMetaDataParams.setNodeIds(st.getNodeIds()); -// nodeMetaDataParams.setMaxResults(1000); - MetaDataResultsFilter filter = new MetaDataResultsFilter(); - filter.setIncludeAssociations(false); - //filter.setIncludePaths(false); - filter.setIncludeChildAssociations(false); - getNodeMetaData(nodeMetaDataParams, filter, st); - }*/ - - public void testNodeMetaDataNullPropertyValue() throws Exception - { - long fromCommitTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest5(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataNullPropertyValue", true, true); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, fromCommitTime, 0); - - int[] updates = new int[] {11}; - int[] deletes = new int[] {0}; - List txnIds = checkTransactions(txns, 1, updates, deletes); - - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - - NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); - nodeMetaDataParams.setNodeIds(st.getNodeIds()); - getNodeMetaData(nodeMetaDataParams, null, st); - } - -/* public void testFilters() - { - long startTime = System.currentTimeMillis(); - - SOLRTest st = new SOLRTest1(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testFilters", true, true); - st.buildTransactions(); - - List txns = solrDAO.getTransactions(null, startTime, 0); - - int[] updates = new int[] {1, 1}; - int[] deletes = new int[] {0, 1}; - List txnIds = checkTransactions(txns, 2, updates, deletes); - - NodeParameters nodeParameters = new NodeParameters(); - nodeParameters.setTransactionIds(txnIds); - getNodes(nodeParameters, st); - - NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); - nodeMetaDataParams.setNodeIds(st.getNodeIds()); - getNodeMetaData(nodeMetaDataParams, null, st); - }*/ - - private static class NodeAssertions - { - private Set aspects; - private Map properties; - private NodeStatus nodeStatus; - private Boolean expectAspects = true; - private Boolean expectProperties = true; - private boolean expectType = true; - private boolean expectOwner = true; - private boolean expectAssociations = true; - private boolean expectPaths = true; - private boolean expectAclId = true; - - public NodeAssertions() - { - super(); - } - - public boolean isExpectType() - { - return expectType; - } - - public void setExpectType(boolean expectType) - { - this.expectType = expectType; - } - - - public boolean isExpectOwner() - { - return expectOwner; - } - - - public void setExpectOwner(boolean expectOwner) - { - this.expectOwner = expectOwner; - } - - - public boolean isExpectAssociations() - { - return expectAssociations; - } - - - public void setExpectAssociations(boolean expectAssociations) - { - this.expectAssociations = expectAssociations; - } - - - public boolean isExpectPaths() - { - return expectPaths; - } - - - public void setExpectPaths(boolean expectPaths) - { - this.expectPaths = expectPaths; - } - - - public boolean isExpectAclId() - { - return expectAclId; - } - - - public void setExpectAclId(boolean expectAclId) - { - this.expectAclId = expectAclId; - } - - - public boolean isExpectAspects() - { - return expectAspects; - } - - public void setExpectAspects(boolean expectAspects) - { - this.expectAspects = expectAspects; - } - - public boolean isExpectProperties() - { - return expectProperties; - } - - public void setExpectProperties(boolean expectProperties) - { - this.expectProperties = expectProperties; - } - - public void setAspects(Set aspects) - { - this.aspects = aspects; - } - - public void setProperties(Map properties) - { - this.properties = properties; - } - - public void setNodeStatus(NodeStatus nodeStatus) - { - this.nodeStatus = nodeStatus; - } - - public NodeStatus getNodeStatus() - { - return nodeStatus; - } - - public Set getAspects() - { - return aspects; - } - - public Map getProperties() - { - return properties; - } - } - - private List checkTransactions(List txns, int numTransactions, int[] updates, int[] deletes) - { - assertEquals("Number of transactions is incorrect", numTransactions, txns.size()); - - List txnIds = new ArrayList(txns.size()); - int i = 0; - for(Transaction txn : txns) - { - assertEquals("Number of deletes is incorrect", deletes[i], txn.getDeletes()); - assertEquals("Number of updates is incorrect", updates[i], txn.getUpdates()); - i++; - - txnIds.add(txn.getId()); - } - - return txnIds; - } - - private void getNodes(NodeParameters nodeParameters, SOLRTest bt) - { - long startTime = System.currentTimeMillis(); - solrDAO.getNodes(nodeParameters, bt); - long endTime = System.currentTimeMillis(); - - bt.runNodeChecks(nodeParameters.getMaxResults()); - - System.out.println("Got " + bt.getActualNodeCount() + " nodes in " + (endTime - startTime) + " ms"); - } - - private void getNodeMetaData(NodeMetaDataParameters params, MetaDataResultsFilter filter, SOLRTest bt) - { - bt.clearNodesMetaData(); - - long startTime = System.currentTimeMillis(); - solrDAO.getNodesMetadata(params, filter, bt); - long endTime = System.currentTimeMillis(); - - bt.runNodeMetaDataChecks(params.getMaxResults()); - - System.out.println("Got " + bt.getActualNodeMetaDataCount() + " node metadatas in " + (endTime - startTime) + " ms"); - } - - private static abstract class SOLRTest implements NodeQueryCallback, NodeMetaDataQueryCallback - { - protected FileFolderService fileFolderService; - protected RetryingTransactionHelper txnHelper; - protected NodeService nodeService; - protected NodeRef rootNodeRef; - protected NodeDAO nodeDAO; - - protected String containerName; - protected Map nodeAssertions; - - protected boolean doChecks; - protected boolean doNodeChecks; - protected boolean doMetaDataChecks; - - protected int successCount = 0; - protected int failureCount = 0; - - protected List nodeIds; - - protected long expectedNumMetaDataNodes = 0; - - protected long actualNodeCount = 0; - protected long actualNodeMetaDataCount = 0; - - SOLRTest(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, - NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) - { - this.txnHelper = txnHelper; - this.nodeService = nodeService; - this.rootNodeRef = rootNodeRef; - this.fileFolderService = fileFolderService; - this.nodeDAO = nodeDAO; - - this.containerName = containerName; - this.nodeAssertions = new HashMap(); - this.nodeIds = new ArrayList(getExpectedNumNodes()); - - this.doNodeChecks = doNodeChecks; - this.doMetaDataChecks = doMetaDataChecks; - this.doChecks = doNodeChecks || doMetaDataChecks; - } - - void runNodeChecks(int maxResults) - { - if(doNodeChecks) - { - if(maxResults != 0 && maxResults != Integer.MAX_VALUE) - { - assertEquals("Number of returned nodes is incorrect", maxResults, getActualNodeCount()); - } - else - { - assertEquals("Number of returned nodes is incorrect", getExpectedNumNodes(), getActualNodeCount()); - } - assertEquals("Unexpected failures", 0, getFailureCount()); - assertEquals("Success count is incorrect", getActualNodeCount(), getSuccessCount()); - } - } - - // TODO - void runNodeMetaDataChecks(int maxResults) - { - if(maxResults != 0 && maxResults != Integer.MAX_VALUE) - { - assertEquals("Number of returned nodes is incorrect", maxResults, getActualNodeMetaDataCount()); - } - else - { - assertEquals("Number of returned nodes is incorrect", getExpectedNumMetaDataNodes(), getActualNodeMetaDataCount()); - } - } - - void clearNodes() - { - successCount = 0; - failureCount = 0; - actualNodeCount = 0; - nodeIds.clear(); - nodeAssertions.clear(); - } - - void clearNodesMetaData() - { - successCount = 0; - failureCount = 0; - actualNodeMetaDataCount = 0; - nodeAssertions.clear(); - } - - public long getActualNodeCount() - { - return actualNodeCount; - } - - public long getActualNodeMetaDataCount() - { - return actualNodeMetaDataCount; - } - - protected long getExpectedNumMetaDataNodes() - { - return expectedNumMetaDataNodes; - } - - protected abstract int getExpectedNumNodes(); - protected abstract void buildTransactionsInternal(); - - public NodeAssertions getNodeAssertions(NodeRef nodeRef) - { - NodeAssertions assertions = nodeAssertions.get(nodeRef); - if(assertions == null) - { - assertions = new NodeAssertions(); - nodeAssertions.put(nodeRef, assertions); - } - return assertions; - } - - protected void setExpectedNodeStatus(NodeRef nodeRef, NodeStatus nodeStatus) - { - if(nodeStatus == NodeStatus.UPDATED) - { - expectedNumMetaDataNodes++; - } - - if(doChecks) - { - NodeAssertions nodeAssertions = getNodeAssertions(nodeRef); - nodeAssertions.setNodeStatus(nodeStatus); - } - } - - void buildTransactions() - { - buildTransactionsInternal(); - } - - @Override - public boolean handleNode(Node node) { - actualNodeCount++; - - if(doNodeChecks) - { - NodeRef nodeRef = node.getNodeRef(); - Boolean isDeleted = node.getDeleted(); - nodeIds.add(node.getId()); - - NodeAssertions expectedStatus = getNodeAssertions(nodeRef); - if(expectedStatus == null) - { - throw new RuntimeException("Unexpected missing assertion for NodeRef " + nodeRef); - } - - if((expectedStatus.getNodeStatus() == NodeStatus.DELETED && isDeleted) || - (expectedStatus.getNodeStatus() == NodeStatus.UPDATED && !isDeleted)) - { - successCount++; - } - else - { - failureCount++; - } - } - - return true; - } - -/* private boolean compareProperties(Map properties1, Map properties2) - { - boolean match = true; - - if(properties1.size() != properties2.size()) - { - match = false; - } - else - { - for(QName qname : properties1.keySet()) - { - Serializable value1 = properties1.get(qname); - Serializable value2 = properties2.get(qname); - if(value1 instanceof MLText) - { - if(!(value2 instanceof MLText)) - { - match = false; - break; - } - MLText ml1 = (MLText)value1; - MLText ml2 = (MLText)value2; - if(ml1.getDefaultValue().equals(ml2.getDefaultValue())) - { - match = false; - break; - } - } - else if(value1 instanceof ContentDataWithId) - { - if(!(value2 instanceof ContentDataWithId)) - { - match = false; - break; - } - ContentDataWithId cd1 = (ContentDataWithId)value1; - ContentDataWithId cd2 = (ContentDataWithId)value2; - if(cd1.getDefaultValue().equals(ml2.getDefaultValue())) - { - match = false; - break; - } - } - else - { - if(!value1.equals(value2)) - { - match = false; - break; - } - } - } - } - - return match; - }*/ - - @Override - public boolean handleNodeMetaData(NodeMetaData nodeMetaData) { - actualNodeMetaDataCount++; - - if(doMetaDataChecks) - { - Long nodeId = nodeMetaData.getNodeId(); - NodeRef nodeRef = nodeMetaData.getNodeRef(); - - Set aspects = nodeMetaData.getAspects(); - Set actualAspects = nodeService.getAspects(nodeRef); - assertEquals("Aspects are incorrect", actualAspects, aspects); - - Map properties = nodeMetaData.getProperties(); - // NodeService converts properties so use nodeDAO to get unadulterated property value - Map actualProperties = nodeDAO.getNodeProperties(nodeId); - //assertTrue("Properties are incorrect", compareProperties(actualProperties, properties)); - assertEquals("Properties are incorrect", actualProperties, properties); - - NodeAssertions assertions = getNodeAssertions(nodeRef); -// NodeAssertions assertions = nodes.get(nodeRef); - - Set expectedAspects = assertions.getAspects(); - if(expectedAspects != null) - { - for(QName aspect : expectedAspects) - { - assertTrue("Expected aspect" + aspect, aspects.contains(aspect)); - } - } - - Map expectedProperties = assertions.getProperties(); - if(expectedProperties != null) - { - for(QName propName : expectedProperties.keySet()) - { - Serializable expectedPropValue = expectedProperties.get(propName); - Serializable actualPropValue = properties.get(propName); - assertNotNull("Missing property " + propName, actualPropValue); - assertEquals("Incorrect property value", expectedPropValue, actualPropValue); - } - } - - // TODO complete path tests -// List actualPaths = nodeMetaData.getPaths(); -// List expectedPaths = nodeService.getPaths(nodeRef, false); -// assertEquals("Paths are incorrect", expectedPaths, actualPaths); - - boolean expectAspects = assertions.isExpectAspects(); - if(expectAspects && nodeMetaData.getAspects() == null) - { - fail("Expecting aspects but got no aspects"); - } - else if(!expectAspects && nodeMetaData.getAspects() != null) - { - fail("Not expecting aspects but got aspects"); - } - - boolean expectProperties = assertions.isExpectProperties(); - if(expectProperties && nodeMetaData.getProperties() == null) - { - fail("Expecting properties but got no properties"); - } - else if(!expectProperties && nodeMetaData.getProperties() != null) - { - fail("Not expecting properties but got properties"); - } - - boolean expectType = assertions.isExpectType(); - if(expectType && nodeMetaData.getNodeType() == null) - { - fail("Expecting type but got no type"); - } - else if(!expectType && nodeMetaData.getNodeType() != null) - { - fail("Not expecting type but got type"); - } - - boolean expectAclId = assertions.isExpectAclId(); - if(expectAclId && nodeMetaData.getAclId() == null) - { - fail("Expecting acl id but got no acl id"); - } - else if(!expectAclId && nodeMetaData.getAclId() != null) - { - fail("Not expecting acl id but got acl id"); - } - - boolean expectPaths = assertions.isExpectPaths(); - if(expectPaths && nodeMetaData.getPaths() == null) - { - fail("Expecting paths but got no paths"); - } - else if(!expectPaths && nodeMetaData.getPaths() != null) - { - fail("Not expecting paths but got paths"); - } - - boolean expectAssociations = assertions.isExpectAssociations(); - if(expectAssociations && nodeMetaData.getChildAssocs() == null) - { - fail("Expecting associations but got no associations"); - } - else if(!expectAssociations && nodeMetaData.getChildAssocs() != null) - { - fail("Not expecting associations but got associations"); - } - - boolean expectOwner = assertions.isExpectOwner(); - if(expectOwner && nodeMetaData.getOwner() == null) - { - fail("Expecting owner but got no owner"); - } - else if(!expectOwner && nodeMetaData.getOwner() != null) - { - fail("Not expecting owner but got owner"); - } - } - - successCount++; - - return true; - } - - public int getSuccessCount() - { - return successCount; - } - - public int getFailureCount() - { - return failureCount; - } - - public List getNodeIds() - { - return nodeIds; - } - } - - private static class SOLRTest1 extends SOLRTest - { - private NodeRef container; - private NodeRef content1; - private NodeRef content2; - - SOLRTest1(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, - NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) - { - super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); - } - - public int getExpectedNumNodes() - { - return 3; - } - - protected void buildTransactionsInternal() - { - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - PropertyMap props = new PropertyMap(); - props.put(ContentModel.PROP_NAME, "Container1"); - container = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - props).getChildRef(); - - FileInfo contentInfo = fileFolderService.create(container, "Content1", ContentModel.TYPE_CONTENT); - content1 = contentInfo.getNodeRef(); - - return null; - } - }); - - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - FileInfo contentInfo = fileFolderService.create(container, "Content2", ContentModel.TYPE_CONTENT); - content2 = contentInfo.getNodeRef(); - - fileFolderService.delete(content1); - - return null; - } - }); - - setExpectedNodeStatus(container, NodeStatus.UPDATED); - setExpectedNodeStatus(content1, NodeStatus.DELETED); - setExpectedNodeStatus(content2, NodeStatus.UPDATED); - } - } - - private static class SOLRTest2 extends SOLRTest - { - private NodeRef container; - private NodeRef content1; - private NodeRef content2; - - SOLRTest2(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, - NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) - { - super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); - } - - public int getExpectedNumNodes() - { - return 3; - } - - protected void buildTransactionsInternal() - { - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - PropertyMap props = new PropertyMap(); - props.put(ContentModel.PROP_NAME, "Container1"); - container = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - props).getChildRef(); - - FileInfo contentInfo = fileFolderService.create(container, "Content1", ContentModel.TYPE_CONTENT); - content1 = contentInfo.getNodeRef(); - - return null; - } - }); - - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - FileInfo contentInfo = fileFolderService.create(container, "Content2", ContentModel.TYPE_CONTENT); - content2 = contentInfo.getNodeRef(); - - fileFolderService.delete(content1); - - return null; - } - }); - - setExpectedNodeStatus(container, NodeStatus.UPDATED); - setExpectedNodeStatus(content1, NodeStatus.DELETED); - setExpectedNodeStatus(content2, NodeStatus.UPDATED); - } - } - - private static class SOLRTest3 extends SOLRTest - { - private NodeRef container; - private NodeRef content1; - private NodeRef content2; - - SOLRTest3(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, - NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) - { - super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); - } - - public int getExpectedNumNodes() - { - return 3; - } - - protected void buildTransactionsInternal() - { - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - PropertyMap props = new PropertyMap(); - props.put(ContentModel.PROP_NAME, "Container1"); - container = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - props).getChildRef(); - - FileInfo contentInfo = fileFolderService.create(container, "Content1", ContentModel.TYPE_CONTENT); - content1 = contentInfo.getNodeRef(); - - Map aspectProperties = new HashMap(); - aspectProperties.put(ContentModel.PROP_AUTHOR, "steve"); - nodeService.addAspect(content1, ContentModel.ASPECT_AUTHOR, aspectProperties); - - return null; - } - }); - - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - FileInfo contentInfo = fileFolderService.create(container, "Content2", ContentModel.TYPE_CONTENT); - content2 = contentInfo.getNodeRef(); - - nodeService.addAspect(content2, ContentModel.ASPECT_TEMPORARY, null); - fileFolderService.delete(content1); - - return null; - } - }); - - setExpectedNodeStatus(container, NodeStatus.UPDATED); - setExpectedNodeStatus(content1, NodeStatus.DELETED); - setExpectedNodeStatus(content2, NodeStatus.UPDATED); - } - } - - private static class SOLRTest100Nodes extends SOLRTest - { - SOLRTest100Nodes(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, - NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) - { - super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); - } - - public int getExpectedNumNodes() - { - return 100; - } - - protected void buildTransactionsInternal() - { - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - PropertyMap props = new PropertyMap(); - props.put(ContentModel.PROP_NAME, "Container100Nodes"); - NodeRef container = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - props).getChildRef(); - setExpectedNodeStatus(container, NodeStatus.UPDATED); - - for(int i = 0; i < 99; i++) - { - FileInfo contentInfo = fileFolderService.create(container, "Content" + i, ContentModel.TYPE_CONTENT); - NodeRef nodeRef = contentInfo.getNodeRef(); - - setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); - } - - return null; - } - }); - } - } - - private static class SOLRTest4 extends SOLRTest - { - private int numContentNodes = 2000; - - SOLRTest4(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, - NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) - { - super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); - } - - public int getExpectedNumNodes() - { - return numContentNodes + 1; - } - - public void buildTransactionsInternal() - { - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - PropertyMap props = new PropertyMap(); - props.put(ContentModel.PROP_NAME, containerName); - NodeRef container = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - props).getChildRef(); - setExpectedNodeStatus(container, NodeStatus.UPDATED); - - for(int i = 0; i < numContentNodes; i++) - { - FileInfo contentInfo = fileFolderService.create(container, "Content" + i, ContentModel.TYPE_CONTENT); - NodeRef nodeRef = contentInfo.getNodeRef(); - - if(i % 2 == 1) - { - nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); - } - nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, null); - - setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); - } - - return null; - } - }); - } - } - - private static class SOLRTest5 extends SOLRTest - { - private int numContentNodes = 10; - - SOLRTest5(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, - NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) - { - super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); - } - - public int getExpectedNumNodes() - { - return numContentNodes + 1; - } - - public void buildTransactionsInternal() - { - final String titles[] = - { - "caf\u00E9", "\u00E7edilla", "\u00E0\u00E1\u00E2\u00E3", "\u00EC\u00ED\u00EE\u00EF", "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6", - "caf\u00E9", "\u00E7edilla", "\u00E0\u00E1\u00E2\u00E3", "\u00EC\u00ED\u00EE\u00EF", "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6" - }; - txnHelper.doInTransaction(new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - PropertyMap props = new PropertyMap(); - props.put(ContentModel.PROP_NAME, containerName); - NodeRef container = nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - props).getChildRef(); - setExpectedNodeStatus(container, NodeStatus.UPDATED); - - for(int i = 0; i < numContentNodes; i++) - { - FileInfo contentInfo = fileFolderService.create(container, "Content" + i, ContentModel.TYPE_CONTENT); - NodeRef nodeRef = contentInfo.getNodeRef(); - - nodeService.addAspect(nodeRef, ContentModel.ASPECT_AUTHOR, null); - if(i % 5 == 1) - { - nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, null); - } - else - { - nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, "author" + i); - } - - nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, titles[i]); - - setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); - } - - return null; - } - }); - } - } -} +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.solr.NodeParameters; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Tests for the SOLR DAO + * + * @since 4.0 + */ +public class SOLRDAOTest extends TestCase +{ + private ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext(); + + private AuthenticationComponent authenticationComponent; + private SOLRDAO solrDAO; + + @Override + public void setUp() throws Exception + { + solrDAO = (SOLRDAO)ctx.getBean("solrDAO"); + authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); + + authenticationComponent.setSystemUserAsCurrentUser(); + } + + public void testQueryTransactionsNoLimit() + { + long startTime = System.currentTimeMillis() - (5 * 60000L); + + try + { + solrDAO.getTransactions(null, startTime, 0); + fail("Must have result limit"); + } + catch (IllegalArgumentException e) + { + // Expected + } + } + + public void testQueryTransactionsTime() + { + long startTime = System.currentTimeMillis() + (5 * 60000L); // The future + List txns = solrDAO.getTransactions(null, startTime, 50); + assertTrue("Transaction count not limited", txns.size() == 0); + } + + public void testQueryTransactionsLimit() + { + List txns = solrDAO.getTransactions(null, 0L, 50); + assertTrue("Transaction count not limited", txns.size() <= 50); + } + + public void testGetNodesSimple() + { + long startTime = 0L; + + List txns = solrDAO.getTransactions(null, startTime, 500); + + List txnIds = toTxnIds(txns); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + nodeParameters.setStoreProtocol(StoreRef.PROTOCOL_WORKSPACE); + nodeParameters.setStoreIdentifier(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.getIdentifier()); + + List nodes = solrDAO.getNodes(nodeParameters); + assertTrue("Expect 'some' nodes associated with txns", nodes.size() > 0); + } + + public void testGetNodesForStore() + { + List txns = solrDAO.getTransactions(null, null, 500); + + List txnIds = toTxnIds(txns); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + + List nodes = solrDAO.getNodes(nodeParameters); + assertTrue("Expect 'some' nodes associated with txns", nodes.size() > 0); + } + + public void testGetNodesForTxnRange() + { + List txns = solrDAO.getTransactions(null, null, 500); + + List txnIds = toTxnIds(txns); + + // Only works if there are transactions + if (txnIds.size() < 2) + { + // Nothing to test + return; + } + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setFromTxnId(txnIds.get(0)); + nodeParameters.setToTxnId(txnIds.get(1)); + + List nodes = solrDAO.getNodes(nodeParameters); + assertTrue("Expect 'some' nodes associated with txns", nodes.size() > 0); + } + + private List toTxnIds(List txns) + { + List txnIds = new ArrayList(txns.size()); + for(Transaction txn : txns) + { + txnIds.add(txn.getId()); + } + + return txnIds; + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java b/source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java new file mode 100644 index 0000000000..c7b04f5ce3 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/solr/SOLRTrackingParameters.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +import java.util.Date; +import java.util.List; + +/** + * Holds parameters for SOLR DAO calls against alf_transaction and alf_change_set. + * + * @since 4.0 + */ +public class SOLRTrackingParameters +{ + private Long fromIdInclusive; + private Long fromCommitTimeInclusive; + private List ids; + private Long fromRelatedIdInclusive; + private Long toRelatedIdExclusive; + + public SOLRTrackingParameters() + { + } + + public Long getFromIdInclusive() + { + return fromIdInclusive; + } + + public void setFromIdInclusive(Long fromIdInclusive) + { + this.fromIdInclusive = fromIdInclusive; + } + + public Long getFromCommitTimeInclusive() + { + return fromCommitTimeInclusive; + } + + public void setFromCommitTimeInclusive(Long fromCommitTimeInclusive) + { + this.fromCommitTimeInclusive = fromCommitTimeInclusive; + } + + public List getIdsx() + { + return ids; + } + + public void setIdsx(List ids) + { + this.ids = ids; + } + + public Long getFromRelatedIdInclusive() + { + return fromRelatedIdInclusive; + } + + public void setFromRelatedIdInclusive(Long fromRelatedIdInclusive) + { + this.fromRelatedIdInclusive = fromRelatedIdInclusive; + } + + public Long getToRelatedIdExclusive() + { + return toRelatedIdExclusive; + } + + public void setToRelatedIdExclusive(Long toRelatedIdExclusive) + { + this.toRelatedIdExclusive = toRelatedIdExclusive; + } + + /** + * Helper for cross-DB boolean support + * + * @return true always + */ + public boolean getTrue() + { + return true; + } + + /** + * Helper for cross-DB boolean support + * + * @return false always + */ + public boolean getFalse() + { + return false; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("SOLRTrackingParameters") + .append(", fromIdInclusive").append(fromIdInclusive) + .append(", ids").append(ids == null ? null : ids.size()) + .append(", fromCommitTimeInclusive").append(fromCommitTimeInclusive == null ? null : new Date(fromCommitTimeInclusive)) + .append(", fromRelatedIdInclusive=").append(fromRelatedIdInclusive) + .append(", toRelatedIdExclusive").append(toRelatedIdExclusive) + .append("]"); + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/SOLRTransactionParameters.java b/source/java/org/alfresco/repo/domain/solr/SOLRTransactionParameters.java deleted file mode 100644 index 762cb2917b..0000000000 --- a/source/java/org/alfresco/repo/domain/solr/SOLRTransactionParameters.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr; - -import java.util.Date; -import java.util.List; - -/** - * Holds parameters for SOLR DAO calls - * - * @since 4.0 - */ -public class SOLRTransactionParameters { - private Long minTxnId; - private Long txnFromCommitTime; - private List transactionIds; - private Long fromNodeId; - private Long toNodeId; - - public SOLRTransactionParameters() - { - } - - public void setMinTxnId(Long minTxnId) - { - this.minTxnId = minTxnId; - } - - public Long getMinTxnId() - { - return minTxnId; - } - - public void setTxnFromCommitTime(Long txnFromCommitTime) { - this.txnFromCommitTime = txnFromCommitTime; - } - - public Long getTxnFromCommitTime() { - return txnFromCommitTime; - } - - public void setTransactionIds(List txnIds) { - this.transactionIds = txnIds; - } - - public List getTransactionIds() { - return transactionIds; - } - - public Long getFromNodeId() { - return fromNodeId; - } - - public void setFromNodeId(Long fromNodeId) { - this.fromNodeId = fromNodeId; - } - - public Long getToNodeId() { - return toNodeId; - } - - public void setToNodeId(Long toNodeId) { - this.toNodeId = toNodeId; - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(512); - sb.append("SOLRTransactionParameters") - .append(", txnFromCommitTime").append(txnFromCommitTime == null ? null : new Date(txnFromCommitTime)) - .append(", fromNodeId").append(fromNodeId == null ? null : fromNodeId) - .append(", toNodeId").append(toNodeId == null ? null : toNodeId) - .append(", txnIds").append(transactionIds == null ? null : transactionIds.size()) - .append("]"); - return sb.toString(); - } -} diff --git a/source/java/org/alfresco/repo/domain/solr/Transaction.java b/source/java/org/alfresco/repo/domain/solr/Transaction.java index 6076cb8b67..447c4d92ab 100644 --- a/source/java/org/alfresco/repo/domain/solr/Transaction.java +++ b/source/java/org/alfresco/repo/domain/solr/Transaction.java @@ -1,32 +1,32 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr; - -/** - * Interface for SOLR transaction objects. - * - * @since 4.0 - */ -public interface Transaction -{ - public Long getId(); - public Long getCommitTimeMs(); - public int getUpdates(); - public int getDeletes(); -} +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +/** + * Interface for SOLR transaction objects. + * + * @since 4.0 + */ +public interface Transaction +{ + public Long getId(); + public Long getCommitTimeMs(); + public int getUpdates(); + public int getDeletes(); +} diff --git a/source/java/org/alfresco/repo/domain/solr/TransactionEntity.java b/source/java/org/alfresco/repo/domain/solr/TransactionEntity.java index aa9ec92e91..fc3b2f3d87 100644 --- a/source/java/org/alfresco/repo/domain/solr/TransactionEntity.java +++ b/source/java/org/alfresco/repo/domain/solr/TransactionEntity.java @@ -1,93 +1,93 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr; - -/** - * Bean to represent SOLR transaction data. - * - * @since 4.0 - */ -public class TransactionEntity implements Transaction -{ - private Long id; - private Long commitTimeMs; - private int updates; - private int deletes; - - /** - * Required default constructor - */ - public TransactionEntity() - { - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(512); - sb.append("TransactionEntity") - .append("[ ID=").append(id) - .append(", updates=").append(updates) - .append(", deletes=").append(deletes) - .append(", commitTimeMs=").append(commitTimeMs) - .append("]"); - return sb.toString(); - } - - public Long getId() - { - return id; - } - - public void setId(Long id) - { - this.id = id; - } - - public int getUpdates() - { - return updates; - } - - public void setUpdates(int updates) - { - this.updates = updates; - } - - public int getDeletes() - { - return deletes; - } - - public void setDeletes(int deletes) - { - this.deletes = deletes; - } - - public Long getCommitTimeMs() - { - return commitTimeMs; - } - - public void setCommitTimeMs(Long commitTimeMs) - { - this.commitTimeMs = commitTimeMs; - } -} - +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr; + +/** + * Bean to represent SOLR transaction data. + * + * @since 4.0 + */ +public class TransactionEntity implements Transaction +{ + private Long id; + private Long commitTimeMs; + private int updates; + private int deletes; + + /** + * Required default constructor + */ + public TransactionEntity() + { + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("TransactionEntity") + .append("[ ID=").append(id) + .append(", updates=").append(updates) + .append(", deletes=").append(deletes) + .append(", commitTimeMs=").append(commitTimeMs) + .append("]"); + return sb.toString(); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public int getUpdates() + { + return updates; + } + + public void setUpdates(int updates) + { + this.updates = updates; + } + + public int getDeletes() + { + return deletes; + } + + public void setDeletes(int deletes) + { + this.deletes = deletes; + } + + public Long getCommitTimeMs() + { + return commitTimeMs; + } + + public void setCommitTimeMs(Long commitTimeMs) + { + this.commitTimeMs = commitTimeMs; + } +} + diff --git a/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java b/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java index 4f31fe5483..930efa8da4 100644 --- a/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/solr/ibatis/SOLRDAOImpl.java @@ -1,653 +1,556 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr.ibatis; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.node.Node; -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback; -import org.alfresco.repo.domain.node.NodeEntity; -import org.alfresco.repo.domain.qname.QNameDAO; -import org.alfresco.repo.domain.solr.MetaDataResultsFilter; -import org.alfresco.repo.domain.solr.NodeMetaData; -import org.alfresco.repo.domain.solr.NodeMetaDataEntity; -import org.alfresco.repo.domain.solr.NodeMetaDataParameters; -import org.alfresco.repo.domain.solr.NodeParameters; -import org.alfresco.repo.domain.solr.SOLRDAO; -import org.alfresco.repo.domain.solr.SOLRTransactionParameters; -import org.alfresco.repo.domain.solr.Transaction; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.security.OwnableService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.ibatis.session.RowBounds; -import org.mybatis.spring.SqlSessionTemplate; - -/** - * DAO support for SOLR web scripts. - * - * @since 4.0 - */ -// TODO Freemarker requires the construction of a model which means that lists and maps need to be built up in memory -// - consider building the JSON in a more streaming manner, without building up of these data structures. -// downside: loss of separation of model and view -// upside: better performance? -public class SOLRDAOImpl implements SOLRDAO -{ - private static final Log logger = LogFactory.getLog(SOLRDAOImpl.class); - private static final String SELECT_TRANSACTIONS = "alfresco.solr.select_Txns"; - private static final String SELECT_NODES = "alfresco.solr.select_Txn_Nodes"; - - private DictionaryService dictionaryService; - private NodeDAO nodeDAO; - private QNameDAO qnameDAO; - private OwnableService ownableService; - private TenantService tenantService; - - private SqlSessionTemplate template; - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public void setOwnableService(OwnableService ownableService) - { - this.ownableService = ownableService; - } - - public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) - { - this.template = sqlSessionTemplate; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setNodeDAO(NodeDAO nodeDAO) - { - this.nodeDAO = nodeDAO; - } - - public void setQNameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - - /* - * Initialize - */ - public void init() - { - PropertyCheck.mandatory(this, "ownableService", ownableService); - PropertyCheck.mandatory(this, "tenantService", tenantService); - PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); - PropertyCheck.mandatory(this, "nodeDAO", nodeDAO); - PropertyCheck.mandatory(this, "qnameDAO", qnameDAO); - } - - @SuppressWarnings("unchecked") - /** - * {@inheritDoc} - */ - public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults) - { - if(minTxnId == null && fromCommitTime == null && (maxResults == 0 || maxResults == Integer.MAX_VALUE)) - { - throw new IllegalArgumentException("Must specify at least one parameter"); - } - - List txns = null; - SOLRTransactionParameters params = new SOLRTransactionParameters(); - params.setMinTxnId(minTxnId); - params.setTxnFromCommitTime(fromCommitTime); - - if(maxResults != 0 && maxResults != Integer.MAX_VALUE) - { - txns = (List)template.selectList(SELECT_TRANSACTIONS, params, new RowBounds(0, maxResults)); - } - else - { - txns = (List)template.selectList(SELECT_TRANSACTIONS, params); - } - - return txns; - } - - @SuppressWarnings("unchecked") - // TODO should create qnames if don't exist? - /** - * {@inheritDoc} - */ - public void getNodes(NodeParameters nodeParameters, NodeQueryCallback callback) - { - List nodes = null; - NodeQueryRowHandler rowHandler = new NodeQueryRowHandler(callback); - - if(nodeParameters.getIncludeTypeIds() == null && nodeParameters.getIncludeNodeTypes() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getIncludeNodeTypes(), false); - nodeParameters.setIncludeTypeIds(new ArrayList(qnamesIds)); - } - - if(nodeParameters.getExcludeTypeIds() == null && nodeParameters.getExcludeNodeTypes() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getExcludeNodeTypes(), false); - nodeParameters.setExcludeTypeIds(new ArrayList(qnamesIds)); - } - - if(nodeParameters.getExcludeAspectIds() == null && nodeParameters.getExcludeAspects() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getExcludeAspects(), false); - nodeParameters.setExcludeAspectIds(new ArrayList(qnamesIds)); - } - - if(nodeParameters.getIncludeAspectIds() == null && nodeParameters.getIncludeAspects() != null) - { - Set qnamesIds = qnameDAO.convertQNamesToIds(nodeParameters.getIncludeAspects(), false); - nodeParameters.setIncludeAspectIds(new ArrayList(qnamesIds)); - } - - if(nodeParameters.getMaxResults() != 0 && nodeParameters.getMaxResults() != Integer.MAX_VALUE) - { - nodes = (List)template.selectList(SELECT_NODES, nodeParameters, - new RowBounds(0, nodeParameters.getMaxResults())); - } - else - { - nodes = (List)template.selectList(SELECT_NODES, nodeParameters); - } - - for(NodeEntity node : nodes) - { - rowHandler.processResult(node); - } - } - - /** - * A dumb iterator that iterates over longs in sequence. - * - */ - private static class SequenceIterator implements Iterable, Iterator - { - private long fromId; - private long toId; - private long counter; - private int maxResults; - private boolean inUse = false; - - SequenceIterator(Long fromId, Long toId, int maxResults) - { - this.fromId = (fromId == null ? 1 : fromId.longValue()); - this.toId = (toId == null ? Long.MAX_VALUE : toId.longValue()); - this.maxResults = maxResults; - this.counter = this.fromId; - } - - public List getList() - { - List ret = new ArrayList(100); - @SuppressWarnings("rawtypes") - Iterator nodeIds = iterator(); - while(nodeIds.hasNext()) - { - ret.add((Long)nodeIds.next()); - } - return ret; - } - - @Override - public Iterator iterator() - { - if(inUse) - { - throw new IllegalStateException("Already in use"); - } - this.counter = this.fromId; - this.inUse = true; - return this; - } - - @Override - public boolean hasNext() - { - return ((counter - this.fromId) < maxResults) && counter <= toId; - } - - @Override - public Long next() - { - return counter++; - } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } - } - - private boolean isCategorised(AspectDefinition aspDef) - { - if(aspDef == null) - { - return false; - } - AspectDefinition current = aspDef; - while (current != null) - { - if (current.getName().equals(ContentModel.ASPECT_CLASSIFIABLE)) - { - return true; - } - else - { - QName parentName = current.getParentName(); - if (parentName == null) - { - break; - } - current = dictionaryService.getAspect(parentName); - } - } - return false; - } - - private Collection> getCategoryPaths(NodeRef nodeRef, Set aspects, Map properties) - { - ArrayList> categoryPaths = new ArrayList>(); - - for (QName classRef : aspects) - { - AspectDefinition aspDef = dictionaryService.getAspect(classRef); - if (isCategorised(aspDef)) - { - LinkedList> aspectPaths = new LinkedList>(); - for (PropertyDefinition propDef : aspDef.getProperties().values()) - { - if (propDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY)) - { - for (NodeRef catRef : DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, properties.get(propDef.getName()))) - { - if (catRef != null) - { - // can be running in context of System user, hence use input nodeRef - catRef = tenantService.getName(nodeRef, catRef); - - try - { - Pair pair = nodeDAO.getNodePair(catRef); - for (Path path : nodeDAO.getPaths(pair, false)) - { - if ((path.size() > 1) && (path.get(1) instanceof Path.ChildAssocElement)) - { - Path.ChildAssocElement cae = (Path.ChildAssocElement) path.get(1); - boolean isFakeRoot = true; - - final List results = new ArrayList(10); - // We have a callback handler to filter results - ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback() - { - public boolean preLoadNodes() - { - return false; - } - - public boolean handle( - Pair childAssocPair, - Pair parentNodePair, - Pair childNodePair) - { - results.add(childAssocPair.getSecond()); - return true; - } - - public void done() - { - } - }; - - Pair caePair = nodeDAO.getNodePair(cae.getRef().getChildRef()); - nodeDAO.getParentAssocs(caePair.getFirst(), null, null, false, callback); - for (ChildAssociationRef car : results) - { - if (cae.getRef().equals(car)) - { - isFakeRoot = false; - break; - } - } - if (isFakeRoot) - { - if (path.toString().indexOf(aspDef.getName().toString()) != -1) - { - aspectPaths.add(new Pair(path, aspDef.getName())); - } - } - } - } - } - catch (InvalidNodeRefException e) - { - // If the category does not exists we move on the next - } - - } - } - } - } - categoryPaths.addAll(aspectPaths); - } - } - // Add member final element - for (Pair pair : categoryPaths) - { - if (pair.getFirst().last() instanceof Path.ChildAssocElement) - { - Path.ChildAssocElement cae = (Path.ChildAssocElement) pair.getFirst().last(); - ChildAssociationRef assocRef = cae.getRef(); - pair.getFirst().append(new Path.ChildAssocElement(new ChildAssociationRef(assocRef.getTypeQName(), assocRef.getChildRef(), QName.createQName("member"), nodeRef))); - } - } - - return categoryPaths; - } - - private List preCacheNodes(NodeMetaDataParameters nodeMetaDataParameters) - { - int maxResults = nodeMetaDataParameters.getMaxResults(); - boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE); - - List nodeIds = null; - Iterable iterable = null; - List allNodeIds = nodeMetaDataParameters.getNodeIds(); - if(allNodeIds != null) - { - int toIndex = (maxResults > allNodeIds.size() ? allNodeIds.size() : maxResults); - nodeIds = isLimitSet ? allNodeIds.subList(0, toIndex) : nodeMetaDataParameters.getNodeIds(); - iterable = nodeMetaDataParameters.getNodeIds(); - } - else - { - Long fromNodeId = nodeMetaDataParameters.getFromNodeId(); - Long toNodeId = nodeMetaDataParameters.getToNodeId(); - nodeIds = new ArrayList(isLimitSet ? maxResults : 100); // TODO better default here? - iterable = new SequenceIterator(fromNodeId, toNodeId, maxResults); - int counter = 1; - for(Long nodeId : iterable) - { - if(isLimitSet && counter++ > maxResults) - { - break; - } - nodeIds.add(nodeId); - } - } - // pre-cache nodes - nodeDAO.cacheNodesById(nodeIds); - - return nodeIds; - } - - /** - * {@inheritDoc} - */ - public void getNodesMetadata(NodeMetaDataParameters nodeMetaDataParameters, MetaDataResultsFilter resultFilter, NodeMetaDataQueryCallback callback) - { - int maxResults = nodeMetaDataParameters.getMaxResults(); - NodeMetaDataQueryRowHandler rowHandler = new NodeMetaDataQueryRowHandler(callback); - boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE); - boolean includeType = (resultFilter == null ? true : resultFilter.getIncludeType()); - boolean includeProperties = (resultFilter == null ? true : resultFilter.getIncludeProperties()); - boolean includeAspects = (resultFilter == null ? true : resultFilter.getIncludeAspects()); - boolean includePaths = (resultFilter == null ? true : resultFilter.getIncludePaths()); - boolean includeNodeRef = (resultFilter == null ? true : resultFilter.getIncludeNodeRef()); - boolean includeAssociations = (resultFilter == null ? true : resultFilter.getIncludeAssociations()); - boolean includeChildAssociations = (resultFilter == null ? true : resultFilter.getIncludeChildAssociations()); - boolean includeOwner = (resultFilter == null ? true : resultFilter.getIncludeOwner()); - - List nodeIds = preCacheNodes(nodeMetaDataParameters); - - //Iterable iterable = null; -// if(nodeMetaDataParameters.getNodeIds() != null) -// { -// int toIndex = (maxResults > allNodeIds.size() ? allNodeIds.size() : maxResults); -// nodeIds = isLimitSet ? nodeMetaDataParameters.getNodeIds().subList(0, maxResults) : nodeMetaDataParameters.getNodeIds(); -// //iterable = nodeMetaDataParameters.getNodeIds(); -// } -// else -// { -// Long fromNodeId = nodeMetaDataParameters.getFromNodeId(); -// Long toNodeId = nodeMetaDataParameters.getToNodeId(); -// nodeIds = new ArrayList(isLimitSet ? maxResults : 100); // TODO better default here -// SequenceIterator si = new SequenceIterator(fromNodeId, toNodeId, maxResults); -// nodeIds = si.getList(); -// //iterable = si; -// } - // pre-cache nodes -// nodeDAO.cacheNodesById(nodeIds); - - //int i = 1; - for(Long nodeId : nodeIds) - { - Map props = null; - Set aspects = null; - -// if(isLimitSet && i++ > maxResults) -// { -// break; -// } - - if(!nodeDAO.exists(nodeId)) - { - // TODO nodeDAO doesn't cache anything for deleted nodes. Should we be ignoring delete node meta data? - continue; - } - - NodeMetaDataEntity nodeMetaData = new NodeMetaDataEntity(); - - nodeMetaData.setNodeId(nodeId); - - Pair pair = nodeDAO.getNodePair(nodeId); - - nodeMetaData.setAclId(nodeDAO.getNodeAclId(nodeId)); - - if(includeType) - { - QName nodeType = nodeDAO.getNodeType(nodeId); - nodeMetaData.setNodeType(nodeType); - } - - if(includePaths || includeProperties) - { - props = nodeDAO.getNodeProperties(nodeId); - } - nodeMetaData.setProperties(props); - - if(includePaths || includeAspects) - { - aspects = nodeDAO.getNodeAspects(nodeId); - } - nodeMetaData.setAspects(aspects); - - // TODO paths may change during get i.e. node moved around in the graph - if(includePaths) - { - Collection> categoryPaths = getCategoryPaths(pair.getSecond(), aspects, props); - List directPaths = nodeDAO.getPaths(pair, false); - - Collection> paths = new ArrayList>(directPaths.size() + categoryPaths.size()); - for (Path path : directPaths) - { - paths.add(new Pair(path, null)); - } - paths.addAll(categoryPaths); - - nodeMetaData.setPaths(paths); - } - - if(includeNodeRef) - { - nodeMetaData.setNodeRef(pair.getSecond()); - } - - if(includeChildAssociations) - { - final List childAssocs = new ArrayList(100); - nodeDAO.getChildAssocs(nodeId, null, null, null, null, null, new ChildAssocRefQueryCallback() - { - @Override - public boolean preLoadNodes() - { - return false; - } - - @Override - public boolean handle(Pair childAssocPair, Pair parentNodePair, - Pair childNodePair) - { - childAssocs.add(childAssocPair.getSecond()); - return true; - } - - @Override - public void done() - { - } - }); - nodeMetaData.setChildAssocs(childAssocs); - } - - if(includeAssociations) - { - final List parentAssocs = new ArrayList(100); - nodeDAO.getParentAssocs(nodeId, null, null, null, new ChildAssocRefQueryCallback() - { - @Override - public boolean handle(Pair childAssocPair, - Pair parentNodePair, Pair childNodePair) - { - parentAssocs.add(childAssocPair.getSecond()); - return true; - } - - @Override - public boolean preLoadNodes() - { - return false; - } - - @Override - public void done() - { - } - }); - - // TODO non-child associations -// Collection> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId); -// Collection> targetAssocs = nodeDAO.getTargetNodeAssocs(nodeId); -// -// nodeMetaData.setAssocs(); - } - - if(includeOwner) - { - // cached in OwnableService - nodeMetaData.setOwner(ownableService.getOwner(pair.getSecond())); - } - - rowHandler.processResult(nodeMetaData); - } - } - - /** - * Class that passes results from a result entity into the client callback - */ - protected class NodeQueryRowHandler - { - private final NodeQueryCallback callback; - private boolean more; - - private NodeQueryRowHandler(NodeQueryCallback callback) - { - this.callback = callback; - this.more = true; - } - - public void processResult(Node row) - { - if (!more) - { - // No more results required - return; - } - - more = callback.handleNode(row); - } - } - - /** - * Class that passes results from a result entity into the client callback - */ - protected class NodeMetaDataQueryRowHandler - { - private final NodeMetaDataQueryCallback callback; - private boolean more; - - private NodeMetaDataQueryRowHandler(NodeMetaDataQueryCallback callback) - { - this.callback = callback; - this.more = true; - } - - public void processResult(NodeMetaData row) - { - if (!more) - { - // No more results required - return; - } - - more = callback.handleNodeMetaData(row); - } - } -} +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.solr.ibatis; + +import java.util.List; + +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.domain.solr.AclChangeSet; +import org.alfresco.repo.domain.solr.NodeParametersEntity; +import org.alfresco.repo.domain.solr.SOLRDAO; +import org.alfresco.repo.domain.solr.SOLRTrackingParameters; +import org.alfresco.repo.domain.solr.Transaction; +import org.alfresco.repo.solr.NodeParameters; +import org.alfresco.util.PropertyCheck; +import org.apache.ibatis.session.RowBounds; +import org.mybatis.spring.SqlSessionTemplate; + +/** + * DAO support for SOLR web scripts. + * + * @since 4.0 + */ +public class SOLRDAOImpl implements SOLRDAO +{ + private static final String SELECT_TRANSACTIONS = "alfresco.solr.select_Txns"; + private static final String SELECT_NODES = "alfresco.solr.select_Txn_Nodes"; + + private SqlSessionTemplate template; + private QNameDAO qnameDAO; + + public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) + { + this.template = sqlSessionTemplate; + } + + public void setQNameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + /** + * Initialize + */ + public void init() + { + PropertyCheck.mandatory(this, "template", template); + PropertyCheck.mandatory(this, "qnameDAO", qnameDAO); + } + + @Override + @SuppressWarnings("unchecked") + public List getAclChangeSets(Long minAclChangeSetId, Long fromCommitTime, int maxResults) + { + if (minAclChangeSetId == null && fromCommitTime == null && (maxResults == 0 || maxResults == Integer.MAX_VALUE)) + { + throw new IllegalArgumentException("Must specify at least one parameter"); + } + + SOLRTrackingParameters params = new SOLRTrackingParameters(); + params.setFromIdInclusive(minAclChangeSetId); + params.setFromCommitTimeInclusive(fromCommitTime); + + List results = null; + if(maxResults != 0 && maxResults != Integer.MAX_VALUE) + { + results = (List)template.selectList(SELECT_TRANSACTIONS, params, new RowBounds(0, maxResults)); + } + else + { + results = (List)template.selectList(SELECT_TRANSACTIONS, params); + } + + return results; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults) + { + if (maxResults <= 0 || maxResults == Integer.MAX_VALUE) + { + throw new IllegalArgumentException("Maximum results must be a reasonable number."); + } + + SOLRTrackingParameters params = new SOLRTrackingParameters(); + params.setFromIdInclusive(minTxnId); + params.setFromCommitTimeInclusive(fromCommitTime); + + return (List) template.selectList(SELECT_TRANSACTIONS, params, new RowBounds(0, maxResults)); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List getNodes(NodeParameters nodeParameters) + { + NodeParametersEntity params = new NodeParametersEntity(nodeParameters, qnameDAO); + + if(nodeParameters.getMaxResults() != 0 && nodeParameters.getMaxResults() != Integer.MAX_VALUE) + { + return (List) template.selectList( + SELECT_NODES, params, + new RowBounds(0, nodeParameters.getMaxResults())); + } + else + { + return (List) template.selectList(SELECT_NODES, params); + } + } +// +// /** +// * A dumb iterator that iterates over longs in sequence. +// * +// */ +// private static class SequenceIterator implements Iterable, Iterator +// { +// private long fromId; +// private long toId; +// private long counter; +// private int maxResults; +// private boolean inUse = false; +// +// SequenceIterator(Long fromId, Long toId, int maxResults) +// { +// this.fromId = (fromId == null ? 1 : fromId.longValue()); +// this.toId = (toId == null ? Long.MAX_VALUE : toId.longValue()); +// this.maxResults = maxResults; +// this.counter = this.fromId; +// } +// +// public List getList() +// { +// List ret = new ArrayList(100); +// @SuppressWarnings("rawtypes") +// Iterator nodeIds = iterator(); +// while(nodeIds.hasNext()) +// { +// ret.add((Long)nodeIds.next()); +// } +// return ret; +// } +// +// @Override +// public Iterator iterator() +// { +// if(inUse) +// { +// throw new IllegalStateException("Already in use"); +// } +// this.counter = this.fromId; +// this.inUse = true; +// return this; +// } +// +// @Override +// public boolean hasNext() +// { +// return ((counter - this.fromId) < maxResults) && counter <= toId; +// } +// +// @Override +// public Long next() +// { +// return counter++; +// } +// +// @Override +// public void remove() +// { +// throw new UnsupportedOperationException(); +// } +// } + +// private boolean isCategorised(AspectDefinition aspDef) +// { +// if(aspDef == null) +// { +// return false; +// } +// AspectDefinition current = aspDef; +// while (current != null) +// { +// if (current.getName().equals(ContentModel.ASPECT_CLASSIFIABLE)) +// { +// return true; +// } +// else +// { +// QName parentName = current.getParentName(); +// if (parentName == null) +// { +// break; +// } +// current = dictionaryService.getAspect(parentName); +// } +// } +// return false; +// } +// +// // TODO: Push into Abstract class +// private Collection> getCategoryPaths(NodeRef nodeRef, Set aspects, Map properties) +// { +// ArrayList> categoryPaths = new ArrayList>(); +// +// for (QName classRef : aspects) +// { +// AspectDefinition aspDef = dictionaryService.getAspect(classRef); +// if (isCategorised(aspDef)) +// { +// LinkedList> aspectPaths = new LinkedList>(); +// for (PropertyDefinition propDef : aspDef.getProperties().values()) +// { +// if (propDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY)) +// { +// for (NodeRef catRef : DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, properties.get(propDef.getName()))) +// { +// if (catRef != null) +// { +// // can be running in context of System user, hence use input nodeRef +// catRef = tenantService.getName(nodeRef, catRef); +// +// try +// { +// Pair pair = nodeDAO.getNodePair(catRef); +// for (Path path : nodeDAO.getPaths(pair, false)) +// { +// if ((path.size() > 1) && (path.get(1) instanceof Path.ChildAssocElement)) +// { +// Path.ChildAssocElement cae = (Path.ChildAssocElement) path.get(1); +// boolean isFakeRoot = true; +// +// final List results = new ArrayList(10); +// // We have a callback handler to filter results +// ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback() +// { +// public boolean preLoadNodes() +// { +// return false; +// } +// +// public boolean handle( +// Pair childAssocPair, +// Pair parentNodePair, +// Pair childNodePair) +// { +// results.add(childAssocPair.getSecond()); +// return true; +// } +// +// public void done() +// { +// } +// }; +// +// Pair caePair = nodeDAO.getNodePair(cae.getRef().getChildRef()); +// nodeDAO.getParentAssocs(caePair.getFirst(), null, null, false, callback); +// for (ChildAssociationRef car : results) +// { +// if (cae.getRef().equals(car)) +// { +// isFakeRoot = false; +// break; +// } +// } +// if (isFakeRoot) +// { +// if (path.toString().indexOf(aspDef.getName().toString()) != -1) +// { +// aspectPaths.add(new Pair(path, aspDef.getName())); +// } +// } +// } +// } +// } +// catch (InvalidNodeRefException e) +// { +// // If the category does not exists we move on the next +// } +// +// } +// } +// } +// } +// categoryPaths.addAll(aspectPaths); +// } +// } +// // Add member final element +// for (Pair pair : categoryPaths) +// { +// if (pair.getFirst().last() instanceof Path.ChildAssocElement) +// { +// Path.ChildAssocElement cae = (Path.ChildAssocElement) pair.getFirst().last(); +// ChildAssociationRef assocRef = cae.getRef(); +// pair.getFirst().append(new Path.ChildAssocElement(new ChildAssociationRef(assocRef.getTypeQName(), assocRef.getChildRef(), QName.createQName("member"), nodeRef))); +// } +// } +// +// return categoryPaths; +// } +// +// private List preCacheNodes(NodeMetaDataParameters nodeMetaDataParameters) +// { +// int maxResults = nodeMetaDataParameters.getMaxResults(); +// boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE); +// +// List nodeIds = null; +// Iterable iterable = null; +// List allNodeIds = nodeMetaDataParameters.getNodeIds(); +// if(allNodeIds != null) +// { +// int toIndex = (maxResults > allNodeIds.size() ? allNodeIds.size() : maxResults); +// nodeIds = isLimitSet ? allNodeIds.subList(0, toIndex) : nodeMetaDataParameters.getNodeIds(); +// iterable = nodeMetaDataParameters.getNodeIds(); +// } +// else +// { +// Long fromNodeId = nodeMetaDataParameters.getFromNodeId(); +// Long toNodeId = nodeMetaDataParameters.getToNodeId(); +// nodeIds = new ArrayList(isLimitSet ? maxResults : 100); // TODO better default here? +// iterable = new SequenceIterator(fromNodeId, toNodeId, maxResults); +// int counter = 1; +// for(Long nodeId : iterable) +// { +// if(isLimitSet && counter++ > maxResults) +// { +// break; +// } +// nodeIds.add(nodeId); +// } +// } +// // pre-cache nodes +// nodeDAO.cacheNodesById(nodeIds); +// +// return nodeIds; +// } +// +// /** +// * {@inheritDoc} +// */ +// public void getNodesMetadata( +// NodeMetaDataParameters nodeMetaDataParameters, +// MetaDataResultsFilter resultFilter, +// NodeMetaDataQueryCallback callback) +// { +// int maxResults = nodeMetaDataParameters.getMaxResults(); +// NodeMetaDataQueryRowHandler rowHandler = new NodeMetaDataQueryRowHandler(callback); +// boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE); +// boolean includeType = (resultFilter == null ? true : resultFilter.getIncludeType()); +// boolean includeProperties = (resultFilter == null ? true : resultFilter.getIncludeProperties()); +// boolean includeAspects = (resultFilter == null ? true : resultFilter.getIncludeAspects()); +// boolean includePaths = (resultFilter == null ? true : resultFilter.getIncludePaths()); +// boolean includeNodeRef = (resultFilter == null ? true : resultFilter.getIncludeNodeRef()); +// boolean includeAssociations = (resultFilter == null ? true : resultFilter.getIncludeAssociations()); +// boolean includeChildAssociations = (resultFilter == null ? true : resultFilter.getIncludeChildAssociations()); +// boolean includeOwner = (resultFilter == null ? true : resultFilter.getIncludeOwner()); +// +// List nodeIds = preCacheNodes(nodeMetaDataParameters); +// +// for(Long nodeId : nodeIds) +// { +// Map props = null; +// Set aspects = null; +// +// if (!nodeDAO.exists(nodeId)) +// { +// // Deleted nodes have no metadata +// continue; +// } +// +// NodeMetaDataEntity nodeMetaData = new NodeMetaDataEntity(); +// nodeMetaData.setNodeId(nodeId); +// +// Pair pair = nodeDAO.getNodePair(nodeId); +// nodeMetaData.setAclId(nodeDAO.getNodeAclId(nodeId)); +// +// if(includeType) +// { +// QName nodeType = nodeDAO.getNodeType(nodeId); +// nodeMetaData.setNodeType(nodeType); +// } +// +// if(includePaths || includeProperties) +// { +// props = nodeDAO.getNodeProperties(nodeId); +// } +// nodeMetaData.setProperties(props); +// +// if(includePaths || includeAspects) +// { +// aspects = nodeDAO.getNodeAspects(nodeId); +// } +// nodeMetaData.setAspects(aspects); +// +// // TODO paths may change during get i.e. node moved around in the graph +// if(includePaths) +// { +// Collection> categoryPaths = getCategoryPaths(pair.getSecond(), aspects, props); +// List directPaths = nodeDAO.getPaths(pair, false); +// +// Collection> paths = new ArrayList>(directPaths.size() + categoryPaths.size()); +// for (Path path : directPaths) +// { +// paths.add(new Pair(path, null)); +// } +// paths.addAll(categoryPaths); +// +// nodeMetaData.setPaths(paths); +// } +// +// if(includeNodeRef) +// { +// nodeMetaData.setNodeRef(pair.getSecond()); +// } +// +// if(includeChildAssociations) +// { +// final List childAssocs = new ArrayList(100); +// nodeDAO.getChildAssocs(nodeId, null, null, null, null, null, new ChildAssocRefQueryCallback() +// { +// @Override +// public boolean preLoadNodes() +// { +// return false; +// } +// +// @Override +// public boolean handle(Pair childAssocPair, Pair parentNodePair, +// Pair childNodePair) +// { +// childAssocs.add(childAssocPair.getSecond()); +// return true; +// } +// +// @Override +// public void done() +// { +// } +// }); +// nodeMetaData.setChildAssocs(childAssocs); +// } +// +// if(includeAssociations) +// { +// final List parentAssocs = new ArrayList(100); +// nodeDAO.getParentAssocs(nodeId, null, null, null, new ChildAssocRefQueryCallback() +// { +// @Override +// public boolean handle(Pair childAssocPair, +// Pair parentNodePair, Pair childNodePair) +// { +// parentAssocs.add(childAssocPair.getSecond()); +// return true; +// } +// +// @Override +// public boolean preLoadNodes() +// { +// return false; +// } +// +// @Override +// public void done() +// { +// } +// }); +// +// // TODO non-child associations +//// Collection> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId); +//// Collection> targetAssocs = nodeDAO.getTargetNodeAssocs(nodeId); +//// +//// nodeMetaData.setAssocs(); +// } +// +// if(includeOwner) +// { +// // cached in OwnableService +// nodeMetaData.setOwner(ownableService.getOwner(pair.getSecond())); +// } +// +// rowHandler.processResult(nodeMetaData); +// } +// } +// +// /** +// * Class that passes results from a result entity into the client callback +// */ +// protected class NodeQueryRowHandler +// { +// private final NodeQueryCallback callback; +// private boolean more; +// +// private NodeQueryRowHandler(NodeQueryCallback callback) +// { +// this.callback = callback; +// this.more = true; +// } +// +// public void processResult(Node row) +// { +// if (!more) +// { +// // No more results required +// return; +// } +// +// more = callback.handleNode(row); +// } +// } +// +// /** +// * Class that passes results from a result entity into the client callback +// */ +// protected class NodeMetaDataQueryRowHandler +// { +// private final NodeMetaDataQueryCallback callback; +// private boolean more; +// +// private NodeMetaDataQueryRowHandler(NodeMetaDataQueryCallback callback) +// { +// this.callback = callback; +// this.more = true; +// } +// +// public void processResult(NodeMetaData row) +// { +// if (!more) +// { +// // No more results required +// return; +// } +// +// more = callback.handleNodeMetaData(row); +// } +// } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index eaf4c09aa1..bd94997694 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -59,8 +59,11 @@ import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.model.PagingFileInfoResults; +import org.alfresco.service.cmr.model.PagingSortRequest; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; @@ -69,6 +72,7 @@ import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.PagingSortProp; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateImageResolver; @@ -135,6 +139,9 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol /** Cached values */ protected NodeRef nodeRef; + + private FileInfo nodeInfo; + private String name; private QName type; protected String id; @@ -167,6 +174,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol protected ServiceRegistry services = null; private NodeService nodeService = null; + private FileFolderService fileFolderService = null; private Boolean isDocument = null; private Boolean isContainer = null; private Boolean isLinkToDocument = null; @@ -201,6 +209,13 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol * @param services The ServiceRegistry the Node can use to access services * @param scope Root scope for this Node */ + public ScriptNode(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope) + { + this(nodeInfo.getNodeRef(), services, scope); + + this.nodeInfo = nodeInfo; + } + public ScriptNode(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) { if (nodeRef == null) @@ -217,6 +232,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol this.id = nodeRef.getId(); this.services = services; this.nodeService = services.getNodeService(); + this.fileFolderService = services.getFileFolderService(); this.scope = scope; } @@ -247,6 +263,11 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol return new ScriptNode(nodeRef, services, scope); } + public ScriptNode newInstance(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope) + { + return new ScriptNode(nodeInfo, services, scope); + } + /** * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) */ @@ -532,34 +553,18 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol * This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders() */ public Scriptable childFileFolders(boolean files, boolean folders, Object ignoreTypes) + { + return childFileFolders(files, folders, ignoreTypes, -1, -1, null, true).getResult(); + } + + @SuppressWarnings("unchecked") + public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int skipOffset, int maxItems, String sortProp, boolean ascending) { Object[] results; - // Build a list of file and folder types - DictionaryService dd = services.getDictionaryService(); - Set searchTypeQNames = new HashSet(16, 1.0f); - if (folders) - { - Collection qnames = dd.getSubTypes(ContentModel.TYPE_FOLDER, true); - searchTypeQNames.addAll(qnames); - searchTypeQNames.add(ContentModel.TYPE_FOLDER); - } - if (files) - { - Collection qnames = dd.getSubTypes(ContentModel.TYPE_CONTENT, true); - searchTypeQNames.addAll(qnames); - searchTypeQNames.add(ContentModel.TYPE_CONTENT); - qnames = dd.getSubTypes(ContentModel.TYPE_LINK, true); - searchTypeQNames.addAll(qnames); - searchTypeQNames.add(ContentModel.TYPE_LINK); - } + Set ignoreTypeQNames = new HashSet(5); - // Remove 'system' folder types - Collection qnames = dd.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true); - searchTypeQNames.removeAll(qnames); - searchTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER); - - // Add user defined types to ignore from the search + // Add user defined types to ignore if (ignoreTypes instanceof ScriptableObject) { Serializable types = getValueConverter().convertValueForRepo((ScriptableObject)ignoreTypes); @@ -567,36 +572,41 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol { for (Serializable typeObj : (List)types) { - searchTypeQNames.remove(createQName(typeObj.toString())); + ignoreTypeQNames.add(createQName(typeObj.toString())); } } else if (types instanceof String) { - searchTypeQNames.remove(createQName(types.toString())); + ignoreTypeQNames.add(createQName(types.toString())); } } else if (ignoreTypes instanceof String) { - searchTypeQNames.remove(createQName(ignoreTypes.toString())); + ignoreTypeQNames.add(createQName(ignoreTypes.toString())); } - // Perform the query and collect the results - if (searchTypeQNames.size() != 0) + List sortProps = null; // note: null sortProps => get all in default sort order + if (sortProp != null) { - List childAssocRefs = this.nodeService.getChildAssocs(this.nodeRef, searchTypeQNames); - results = new Object[childAssocRefs.size()]; - for (int i=0; i(1); + sortProps.add(new PagingSortProp(createQName(sortProp), ascending)); } - return Context.getCurrentContext().newArray(this.scope, results); + PagingSortRequest pageRequest = new PagingSortRequest(skipOffset, maxItems, true, sortProps); + + PagingFileInfoResults pageOfNodeInfos = this.fileFolderService.list(this.nodeRef, files, folders, ignoreTypeQNames, pageRequest); + + List nodeInfos = pageOfNodeInfos.getResultsForPage(); + + int size = nodeInfos.size(); + results = new Object[size]; + for (int i=0; i(this, this.services); - Map props = this.nodeService.getProperties(this.nodeRef); + Map props = null; + if (nodeInfo != null) + { + props = nodeInfo.getProperties(); + } + else + { + props = this.nodeService.getProperties(this.nodeRef); + } + for (QName qname : props.keySet()) { Serializable propValue = props.get(qname); diff --git a/source/java/org/alfresco/repo/jscript/ScriptPagingNodes.java b/source/java/org/alfresco/repo/jscript/ScriptPagingNodes.java new file mode 100644 index 0000000000..95f600364e --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ScriptPagingNodes.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2010 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.jscript; + +import java.io.Serializable; + +import org.mozilla.javascript.Scriptable; + +/** + * TEMP + * + * @deprecated for review (API is subject to change) + */ +public class ScriptPagingNodes implements Serializable +{ + private static final long serialVersionUID = -3252996649397737176L; + + private Scriptable result; // array of script nodes + private Boolean hasMore; // null => unknown + private Long totalCount; // null => not requested (or unknown) + + public ScriptPagingNodes(Scriptable result, Long totalCount, Boolean hasMore) + { + this.result = result; + this.totalCount = totalCount; + this.hasMore = hasMore; + } + + public Scriptable getResult() + { + return result; + } + + public Long getTotalCount() + { + return totalCount; + } + + public Boolean hasMore() + { + return hasMore; + } +} diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index ac17f7535a..485075ddee 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -34,7 +34,14 @@ import java.util.Stack; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.query.CannedQueryFactory; +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.query.CannedQueryResults; +import org.alfresco.query.CannedQuerySortDetails; +import org.alfresco.query.CannedQuerySortDetails.SortOrder; import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileExistsException; @@ -43,6 +50,8 @@ import org.alfresco.service.cmr.model.FileFolderServiceType; import org.alfresco.service.cmr.model.FileFolderUtil; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.model.PagingFileInfoResults; +import org.alfresco.service.cmr.model.PagingSortRequest; import org.alfresco.service.cmr.model.SubFolderFilter; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; @@ -55,6 +64,7 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.PagingSortProp; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.search.QueryParameterDefinition; import org.alfresco.service.cmr.search.SearchService; @@ -63,7 +73,9 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; import org.alfresco.util.SearchLanguageConversion; +import org.alfresco.util.registry.NamedObjectRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; @@ -115,6 +127,8 @@ public class FileFolderServiceImpl implements FileFolderService private SearchService searchService; private ContentService contentService; private MimetypeService mimetypeService; + private NamedObjectRegistry cannedQueryRegistry; + private Set systemNamespaces; // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) @@ -162,6 +176,14 @@ public class FileFolderServiceImpl implements FileFolderService { this.mimetypeService = mimetypeService; } + + /** + * Set the registry of {@link CannedQueryFactory canned queries} + */ + public void setCannedQueryRegistry(NamedObjectRegistry cannedQueryRegistry) + { + this.cannedQueryRegistry = cannedQueryRegistry; + } /** * Set the namespaces that should be treated as 'system' namespaces. @@ -202,11 +224,16 @@ public class FileFolderServiceImpl implements FileFolderService List results = new ArrayList(nodeRefs.size()); for (NodeRef nodeRef : nodeRefs) { - if (nodeService.exists(nodeRef)) + try { FileInfo fileInfo = toFileInfo(nodeRef, true); results.add(fileInfo); } + catch (InvalidNodeRefException inre) + { + logger.warn("toFileInfo: "+inre); + // skip + } } return results; } @@ -223,7 +250,7 @@ public class FileFolderServiceImpl implements FileFolderService boolean isFolder = isFolder(typeQName); // Construct the file info and add to the results - FileInfo fileInfo = new FileInfoImpl(nodeRef, isFolder, properties); + FileInfo fileInfo = new FileInfoImpl(nodeRef, typeQName, isFolder, properties); // Done return fileInfo; } @@ -301,9 +328,101 @@ public class FileFolderServiceImpl implements FileFolderService } public List list(NodeRef contextNodeRef) + { + return list(contextNodeRef, true, true); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.model.FileFolderService#list(org.alfresco.service.cmr.repository.NodeRef, boolean, boolean, java.util.Set, org.alfresco.service.cmr.model.PagingSortRequest) + */ + public PagingFileInfoResults list(NodeRef contextNodeRef, boolean files, boolean folders, Set ignoreQNameTypes, PagingSortRequest pagingRequest) + { + long start = System.currentTimeMillis(); + + Set searchTypeQNames = buildTypes(files, folders, ignoreQNameTypes); + + // specific query params + GetChildrenCannedQueryParams paramBean = new GetChildrenCannedQueryParams(contextNodeRef, searchTypeQNames); + + CannedQueryPageDetails cqpd = null; + CannedQuerySortDetails cqsd = null; + boolean requestTotalCount = true; + Integer pageSize = null; + int skipCount = -1; + int maxItems = -1; + + if (pagingRequest != null) + { + skipCount = (pagingRequest.getSkipCount() == -1 ? CannedQueryPageDetails.DEFAULT_SKIP_RESULTS : pagingRequest.getSkipCount()); + maxItems = (pagingRequest.getMaxItems() == -1 ? CannedQueryPageDetails.DEFAULT_PAGE_SIZE : pagingRequest.getMaxItems()); + + // page details + pageSize = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? maxItems : maxItems + 1); // TODO: add 1 to support hasMore (see below) - push down to canned query fwk + cqpd = new CannedQueryPageDetails(skipCount, pageSize, CannedQueryPageDetails.DEFAULT_PAGE_NUMBER, CannedQueryPageDetails.DEFAULT_PAGE_COUNT); + + // sort details + if (pagingRequest.getSortProps() != null) + { + List> sortPairs = new ArrayList>(pagingRequest.getSortProps().size()); + for (PagingSortProp sortProp : pagingRequest.getSortProps()) + { + sortPairs.add(new Pair(sortProp.getSortProp(), (sortProp.isAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING))); + } + + cqsd = new CannedQuerySortDetails(sortPairs); + } + + requestTotalCount = pagingRequest.requestTotalCount(); + } + + // create query params holder + CannedQueryParameters params = new CannedQueryParameters(paramBean, cqpd, cqsd, AuthenticationUtil.getRunAsUser(), requestTotalCount, null); + + // get canned query + GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject("getChildrenCannedQueryFactory"); + GetChildrenCannedQuery cq = (GetChildrenCannedQuery)getChildrenCannedQueryFactory.getCannedQuery(params); + + // execute canned query + CannedQueryResults results = cq.execute(); + + List nodeRefs = results.getPages().get(0); + + boolean hasMore = false; + if ((pageSize != null) && (results.getPagedResultCount() == pageSize)) + { + // TODO: remove 1 to support hasMore (see above) - push down to canned query fwk + hasMore = true; + nodeRefs.remove(nodeRefs.size() - 1); + } + + List nodeInfos = new ArrayList(nodeRefs.size()); + for (NodeRef nodeRef : nodeRefs) + { + nodeInfos.add(toFileInfo(nodeRef, true)); + } + + long totalCount = new Long(results.getTotalResultCount()); + + if (logger.isInfoEnabled()) + { + if ((skipCount == -1) && (maxItems == -1)) + { + logger.info("List: "+nodeInfos.size()+" items [total="+totalCount+"] in "+(System.currentTimeMillis()-start)+" msecs"); + } + else + { + int pageNum = (skipCount / maxItems) + 1; + logger.info("List: page "+pageNum+" of "+nodeInfos.size()+" items [skip="+skipCount+",max="+maxItems+",total="+totalCount+"] in "+(System.currentTimeMillis()-start)+" msecs"); + } + } + + return new PagingFileInfoResultsImpl(nodeInfos, hasMore, totalCount); + } + + private List list(NodeRef contextNodeRef, boolean files, boolean folders) { // execute the query - List nodeRefs = listSimple(contextNodeRef, true, true); + List nodeRefs = listSimple(contextNodeRef, files, folders); // convert the noderefs List results = toFileInfo(nodeRefs); // done @@ -319,7 +438,7 @@ public class FileFolderServiceImpl implements FileFolderService public List listFiles(NodeRef contextNodeRef) { // execute the query - List nodeRefs = listSimple(contextNodeRef, false, true); + List nodeRefs = listSimple(contextNodeRef, true, false); // convert the noderefs List results = toFileInfo(nodeRefs); // done @@ -335,7 +454,7 @@ public class FileFolderServiceImpl implements FileFolderService public List listFolders(NodeRef contextNodeRef) { // execute the query - List nodeRefs = listSimple(contextNodeRef, true, false); + List nodeRefs = listSimple(contextNodeRef, false, true); // convert the noderefs List results = toFileInfo(nodeRefs); // done @@ -351,7 +470,7 @@ public class FileFolderServiceImpl implements FileFolderService public List listDeepFolders(NodeRef contextNodeRef, SubFolderFilter filter) { - List nodeRefs = listSimpleDeep(contextNodeRef, true, false, filter); + List nodeRefs = listSimpleDeep(contextNodeRef, false, true, filter); List results = toFileInfo(nodeRefs); @@ -494,11 +613,11 @@ public class FileFolderServiceImpl implements FileFolderService // This is search for any name if(includeSubFolders) { - nodeRefs = listSimpleDeep(contextNodeRef, folderSearch, fileSearch, null); + nodeRefs = listSimpleDeep(contextNodeRef, fileSearch, folderSearch, null); } else { - nodeRefs = listSimple(contextNodeRef, folderSearch, fileSearch); + nodeRefs = listSimple(contextNodeRef, fileSearch, folderSearch); } } else @@ -558,9 +677,38 @@ public class FileFolderServiceImpl implements FileFolderService return nodeRefs; } - private List listSimple(NodeRef contextNodeRef, boolean folders, boolean files) + private List listSimple(NodeRef contextNodeRef, boolean files, boolean folders) { - Set searchTypeQNames = new HashSet(10); + return listSimple(contextNodeRef, files, folders, null); + } + + private List listSimple(NodeRef contextNodeRef, boolean files, boolean folders, Set ignoreQNameTypes) + { + Set searchTypeQNames = buildTypes(files, folders, ignoreQNameTypes); + + // Shortcut + if (searchTypeQNames.size() == 0) + { + return Collections.emptyList(); + } + + // Do the query + List childAssocRefs = nodeService.getChildAssocs(contextNodeRef, searchTypeQNames); + + List result = new ArrayList(childAssocRefs.size()); + for (ChildAssociationRef assocRef : childAssocRefs) + { + result.add(assocRef.getChildRef()); + } + + // Done + return result; + } + + private Set buildTypes(boolean files, boolean folders, Set ignoreQNameTypes) + { + Set searchTypeQNames = new HashSet(100); + // Build a list of file and folder types if (folders) { @@ -581,20 +729,13 @@ public class FileFolderServiceImpl implements FileFolderService Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true); searchTypeQNames.removeAll(qnames); searchTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER); - // Shortcut - if (searchTypeQNames.size() == 0) + + if (ignoreQNameTypes != null) { - return Collections.emptyList(); + searchTypeQNames.removeAll(ignoreQNameTypes); } - // Do the query - List childAssocRefs = nodeService.getChildAssocs(contextNodeRef, searchTypeQNames); - List result = new ArrayList(childAssocRefs.size()); - for (ChildAssociationRef assocRef : childAssocRefs) - { - result.add(assocRef.getChildRef()); - } - // Done - return result; + + return searchTypeQNames; } /** @@ -616,7 +757,7 @@ public class FileFolderServiceImpl implements FileFolderService * XPath which is awful or adding new methods to the NodeService/DB This is also a dangerous method in that it can return a * lot of data and take a long time. */ - private List listSimpleDeep(NodeRef contextNodeRef, boolean folders, boolean files, SubFolderFilter folderFilter) + private List listSimpleDeep(NodeRef contextNodeRef, boolean files, boolean folders, SubFolderFilter folderFilter) { if(logger.isDebugEnabled()) { diff --git a/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java index ab66ce48e1..e339a50e52 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -43,16 +43,20 @@ public class FileInfoImpl implements FileInfo private boolean isFolder; private boolean isLink; private Map properties; + private QName typeQName; /** * Package-level constructor */ /* package */ FileInfoImpl( NodeRef nodeRef, + QName typeQName, boolean isFolder, Map properties) { this.nodeRef = nodeRef; + this.typeQName = typeQName; + this.isFolder = isFolder; this.properties = properties; @@ -160,4 +164,9 @@ public class FileInfoImpl implements FileInfo { return properties; } + + public QName getType() + { + return typeQName; + } } diff --git a/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java new file mode 100644 index 0000000000..9cb6b5822b --- /dev/null +++ b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQuery.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2005-2010 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.model.filefolder; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.ConfigAttributeDefinition; +import net.sf.acegisecurity.context.Context; +import net.sf.acegisecurity.context.ContextHolder; +import net.sf.acegisecurity.context.security.SecureContext; + +import org.alfresco.model.ContentModel; +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.security.permissions.impl.acegi.MethodSecurityInterceptor; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.MLText; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Canned query to get children of a parent node + * + * @author janv + * @since 4.0 + */ +public class GetChildrenCannedQuery extends AbstractCannedQuery +{ + private Log logger = LogFactory.getLog(getClass()); + + private NodeService nodeService; + private MethodSecurityInterceptor methodSecurityInterceptor; + + public GetChildrenCannedQuery( + NodeService nodeService, + MethodSecurityInterceptor methodSecurityInterceptor, + CannedQueryParameters params, + String queryExecutionId) + { + super(params, queryExecutionId); + + this.nodeService = nodeService; + this.methodSecurityInterceptor = methodSecurityInterceptor; + } + + @Override + protected List query(CannedQueryParameters parameters) + { + long start = System.currentTimeMillis(); + + GetChildrenCannedQueryParams paramBean = (GetChildrenCannedQueryParams)parameters.getParameterBean(); + List childAssocRefs = nodeService.getChildAssocs(paramBean.getParentRef(), paramBean.getSearchTypeQNames()); + + List result = new ArrayList(childAssocRefs.size()); + for (ChildAssociationRef assocRef : childAssocRefs) + { + result.add(assocRef.getChildRef()); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Raw query: "+result.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); + } + + return result; + } + + @Override + protected boolean isApplyPostQuerySorting() + { + return true; + } + + @Override + protected List applyPostQuerySorting(List results, CannedQuerySortDetails sortDetails) + { + if (sortDetails.getSortPairs().size() == 0) + { + // Nothing to sort on + return results; + } + + long start = System.currentTimeMillis(); + + List ret = new ArrayList(results); + + List> sortPairs = sortDetails.getSortPairs(); + + Collections.sort(ret, new PropComparatorAsc(sortPairs)); + + if (logger.isDebugEnabled()) + { + logger.debug("Post query sort: "+ret.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); + } + + return ret; + } + + private class PropComparatorAsc implements Comparator + { + private List> sortProps; + + public PropComparatorAsc(List> sortProps) + { + this.sortProps = sortProps; + } + + public int compare(NodeRef n1, NodeRef n2) + { + return compareImpl(n1, n2, sortProps); + } + + private int compareImpl(NodeRef node1In, NodeRef node2In, List> sortProps) + { + Object pv1 = null; + Object pv2 = null; + + QName sortPropQName = (QName)sortProps.get(0).getFirst(); + boolean sortAscending = (sortProps.get(0).getSecond() == SortOrder.ASCENDING); + + NodeRef node1 = node1In; + NodeRef node2 = node2In; + + if (sortAscending == false) + { + node1 = node2In; + node2 = node1In; + } + + int result = 0; + + if (sortPropQName.equals(QName.createQName(".size")) || sortPropQName.equals(QName.createQName(".mimetype"))) + { + // content data properties (size or mimetype) + + ContentData cd1 = (ContentData)nodeService.getProperty(node1, ContentModel.PROP_CONTENT); + ContentData cd2 = (ContentData)nodeService.getProperty(node2, ContentModel.PROP_CONTENT); + + if (cd1 == null) + { + return (cd2 == null ? 0 : 1); + } + else if (cd2 == null) + { + return -1; + } + + if (sortPropQName.equals(QName.createQName(".size"))) + { + result = ((Long)cd1.getSize()).compareTo((Long)cd2.getSize()); + } + else if (sortPropQName.equals(QName.createQName(".mimetype"))) + { + result = (cd1.getMimetype()).compareTo(cd2.getMimetype()); + } + } + else + { + // property other than content size / mimetype + pv1 = nodeService.getProperty(node1, sortPropQName); + pv2 = nodeService.getProperty(node2, sortPropQName); + + if (pv1 == null) + { + return (pv2 == null ? 0 : 1); + } + else if (pv2 == null) + { + return -1; + } + + if (pv1 instanceof String) + { + result = (((String)pv1).compareTo((String)pv2)); + } + else if (pv1 instanceof MLText) // eg. title, description + { + String sv1 = DefaultTypeConverter.INSTANCE.convert(String.class, (MLText)pv1); + String sv2 = DefaultTypeConverter.INSTANCE.convert(String.class, (MLText)pv2); + + result = (sv1.compareTo(sv2)); + } + else if (pv1 instanceof Date) + { + result = (((Date)pv1).compareTo((Date)pv2)); + } + else + { + // TODO other comparisons + throw new RuntimeException("Unsupported sort type"); + } + } + + if ((result == 0) && (sortProps.size() > 1)) + { + return compareImpl(node1In, node2In, sortProps.subList(1, sortProps.size()-1)); + } + + return result; + } + } + @Override + protected boolean isApplyPostQueryPermissions() + { + return true; + } + + @SuppressWarnings("unchecked") + @Override + protected List applyPostQueryPermissions(List results, String authenticationToken, int requestedCount) + { + long start = System.currentTimeMillis(); + + // TODO push down cut-off (as option within permission interceptor) + //boolean cutoffAllowed = !getParameters().isReturnTotalResultCount(); + + List ret = new ArrayList(results.size()); + + Context context = ContextHolder.getContext(); + if ((context == null) || !(context instanceof SecureContext)) + { + return ret; // empty list + } + + Authentication authentication = (((SecureContext) context).getAuthentication()); + + Method listMethod = null; + for (Method method : FileFolderServiceImpl.class.getMethods()) + { + if (method.getName().equals("list")) + { + // found one of the list methods + listMethod = method; + break; + } + } + + if (listMethod == null) + { + return ret; // empty list + } + + // TODO ALF-8419 + ConfigAttributeDefinition listCad = methodSecurityInterceptor.getObjectDefinitionSource().getAttributes(new InternalMethodInvocation(listMethod)); + ret = (List)methodSecurityInterceptor.getAfterInvocationManager().decide(authentication, null, listCad, results); // need to be able to cut-off etc ... + + if (logger.isDebugEnabled()) + { + logger.debug("Post query perms: "+ret.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); + } + + return ret; + } + + class InternalMethodInvocation implements MethodInvocation { + Method method; + + public InternalMethodInvocation(Method method) { + this.method = method; + } + + protected InternalMethodInvocation() { + throw new UnsupportedOperationException(); + } + + public Object[] getArguments() { + throw new UnsupportedOperationException(); + } + + public Method getMethod() { + return this.method; + } + + public AccessibleObject getStaticPart() { + throw new UnsupportedOperationException(); + } + + public Object getThis() { + throw new UnsupportedOperationException(); + } + + public Object proceed() throws Throwable { + throw new UnsupportedOperationException(); + } + } +} diff --git a/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryFactory.java b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryFactory.java new file mode 100644 index 0000000000..df5b6fb3e0 --- /dev/null +++ b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2010 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.model.filefolder; + +import org.alfresco.query.AbstractCannedQueryFactory; +import org.alfresco.query.CannedQuery; +import org.alfresco.query.CannedQueryParameters; +import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor; +import org.alfresco.service.cmr.repository.NodeService; + +/** + * GetChildren (paged list of FileInfo) + * + * @author janv + * @since 4.0 + */ +public class GetChildrenCannedQueryFactory extends AbstractCannedQueryFactory +{ + private NodeService nodeService; + private MethodSecurityInterceptor methodSecurityInterceptor; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setMethodSecurityInterceptor(MethodSecurityInterceptor methodSecurityInterceptor) + { + this.methodSecurityInterceptor = methodSecurityInterceptor; + } + + @SuppressWarnings("unchecked") + @Override + public CannedQuery getCannedQuery(CannedQueryParameters parameters) + { + String queryExecutionId = super.getQueryExecutionId(parameters); + return (CannedQuery) new GetChildrenCannedQuery(nodeService, methodSecurityInterceptor, parameters, queryExecutionId); + } +} diff --git a/source/java/org/alfresco/repo/domain/solr/NodeMetaData.java b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryParams.java similarity index 53% rename from source/java/org/alfresco/repo/domain/solr/NodeMetaData.java rename to source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryParams.java index eb6fd3e378..f75c2d91c6 100644 --- a/source/java/org/alfresco/repo/domain/solr/NodeMetaData.java +++ b/source/java/org/alfresco/repo/model/filefolder/GetChildrenCannedQueryParams.java @@ -1,44 +1,52 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.Pair; - -public interface NodeMetaData -{ - public NodeRef getNodeRef(); - public Collection> getPaths(); - public QName getNodeType(); - public Long getNodeId(); - public Long getAclId(); - public String getOwner(); - public Map getProperties(); - public Set getAspects(); - public List getChildAssocs(); -} +/* + * Copyright (C) 2005-2010 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.model.filefolder; + +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * GetChildren CQ Parameters + * + * @author janv + * @since 4.0 + */ +public class GetChildrenCannedQueryParams +{ + private NodeRef parentRef; + private Set searchTypeQNames; + + public GetChildrenCannedQueryParams(NodeRef parentRef, Set searchTypeQNames) + { + this.parentRef = parentRef; + this.searchTypeQNames = searchTypeQNames; + } + + public NodeRef getParentRef() + { + return parentRef; + } + + public Set getSearchTypeQNames() + { + return searchTypeQNames; + } +} diff --git a/source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java b/source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java new file mode 100644 index 0000000000..337b071c0c --- /dev/null +++ b/source/java/org/alfresco/repo/model/filefolder/PagingFileInfoResultsImpl.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.model.filefolder; + +import java.util.List; + +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.PagingFileInfoResults; + +/** + * TEMP + * + * @deprecated for review (API is subject to change) + */ +/* package */ class PagingFileInfoResultsImpl implements PagingFileInfoResults +{ + private List nodeInfos; + private Boolean hasMore; // null => unknown + private Long totalCount; // null => not requested (or unknown) + + public PagingFileInfoResultsImpl(List nodeInfos, Boolean hasMore, Long totalCount) + { + this.nodeInfos = nodeInfos; + this.hasMore = hasMore; + this.totalCount= totalCount; + } + + public List getResultsForPage() + { + return nodeInfos; + } + + public Boolean hasMore() + { + return hasMore; + } + public Long getTotalCount() + { + return totalCount; + } +} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java index 6bb1ba9f33..435428d8fd 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java @@ -1,6 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * + * Copyright (C) 2005-2011 Alfresco Software Limited. * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify @@ -42,6 +41,7 @@ import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.PagingFileInfoResults; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -269,6 +269,14 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, { return decide(authentication, object, config, (FileInfo) returnedObject); } + else if (PagingFileInfoResults.class.isAssignableFrom(returnedObject.getClass())) + { + if (log.isDebugEnabled()) + { + log.debug("Controlled object (paged permissions already applied) - access allowed for " + object.getClass().getName()); + } + return returnedObject; + } else if (Pair.class.isAssignableFrom(returnedObject.getClass())) { return decide(authentication, object, config, (Pair) returnedObject); @@ -617,13 +625,21 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, for (int i = 0; i < returnedObject.length(); i++) { long currentTimeMillis = System.currentTimeMillis(); - if (i >= maxChecks || (currentTimeMillis - startTimeMillis) > maxCheckTime) + if (i >= maxChecks) { + log.warn("maxChecks exceeded (" + maxChecks + ")", new Exception("Back Trace")); filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS, PermissionEvaluationMode.EAGER, returnedObject .getResultSetMetaData().getSearchParameters())); break; } - + else if ((currentTimeMillis - startTimeMillis) > maxCheckTime) + { + log.warn("maxCheckTime exceeded (" + (currentTimeMillis - startTimeMillis) + " milliseconds)", new Exception("Back Trace")); + filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS, PermissionEvaluationMode.EAGER, returnedObject + .getResultSetMetaData().getSearchParameters())); + break; + } + // All permission checks must pass filteringResultSet.setIncluded(i, true); diff --git a/source/java/org/alfresco/repo/domain/solr/MetaDataResultsFilter.java b/source/java/org/alfresco/repo/solr/MetaDataResultsFilter.java similarity index 98% rename from source/java/org/alfresco/repo/domain/solr/MetaDataResultsFilter.java rename to source/java/org/alfresco/repo/solr/MetaDataResultsFilter.java index 9a6a53bb2e..e1f0461f3c 100644 --- a/source/java/org/alfresco/repo/domain/solr/MetaDataResultsFilter.java +++ b/source/java/org/alfresco/repo/solr/MetaDataResultsFilter.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.repo.domain.solr; +package org.alfresco.repo.solr; /** * Filters for node metadata results e.g. include properties, aspect, ... or not diff --git a/source/java/org/alfresco/repo/domain/solr/NodeMetaDataEntity.java b/source/java/org/alfresco/repo/solr/NodeMetaData.java similarity index 96% rename from source/java/org/alfresco/repo/domain/solr/NodeMetaDataEntity.java rename to source/java/org/alfresco/repo/solr/NodeMetaData.java index 6b497dcc65..4dc80c0477 100644 --- a/source/java/org/alfresco/repo/domain/solr/NodeMetaDataEntity.java +++ b/source/java/org/alfresco/repo/solr/NodeMetaData.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.repo.domain.solr; +package org.alfresco.repo.solr; import java.io.Serializable; import java.util.Collection; @@ -33,9 +33,8 @@ import org.alfresco.util.Pair; /** * * @since 4.0 - * */ -public class NodeMetaDataEntity implements NodeMetaData +public class NodeMetaData { private Long nodeId; private NodeRef nodeRef; diff --git a/source/java/org/alfresco/repo/domain/solr/NodeMetaDataParameters.java b/source/java/org/alfresco/repo/solr/NodeMetaDataParameters.java similarity index 98% rename from source/java/org/alfresco/repo/domain/solr/NodeMetaDataParameters.java rename to source/java/org/alfresco/repo/solr/NodeMetaDataParameters.java index 3fe429815b..942b552d0e 100644 --- a/source/java/org/alfresco/repo/domain/solr/NodeMetaDataParameters.java +++ b/source/java/org/alfresco/repo/solr/NodeMetaDataParameters.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.repo.domain.solr; +package org.alfresco.repo.solr; import java.util.List; diff --git a/source/java/org/alfresco/repo/domain/solr/NodeParameters.java b/source/java/org/alfresco/repo/solr/NodeParameters.java similarity index 74% rename from source/java/org/alfresco/repo/domain/solr/NodeParameters.java rename to source/java/org/alfresco/repo/solr/NodeParameters.java index a8bf400f76..311b94d087 100644 --- a/source/java/org/alfresco/repo/domain/solr/NodeParameters.java +++ b/source/java/org/alfresco/repo/solr/NodeParameters.java @@ -1,226 +1,176 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.domain.solr; - -import java.util.List; -import java.util.Set; - -import org.alfresco.service.namespace.QName; - -/** - * Stores node query parameters for use in SOLR DAO queries - * - * @since 4.0 - */ -public class NodeParameters -{ - private List transactionIds; - private Long fromTxnId; - private Long toTxnId; - - private Long fromNodeId; - private Long toNodeId; - - // default is 'all' results - private int maxResults = 0; - - private String storeProtocol; - private String storeIdentifier; - - private Set includeNodeTypes; - private Set excludeNodeTypes; - private List includeTypeIds; - private List excludeTypeIds; - - private Set includeAspects; - private Set excludeAspects; - private List includeAspectIds; - private List excludeAspectIds; - - public int getMaxResults() - { - return maxResults; - } - - public void setMaxResults(int maxResults) - { - this.maxResults = maxResults; - } - - public boolean getStoreFilter() - { - return (storeProtocol != null || storeIdentifier != null); - } - - public void setStoreProtocol(String storeProtocol) - { - this.storeProtocol = storeProtocol; - } - - public String getStoreProtocol() - { - return storeProtocol; - } - - public void setStoreIdentifier(String storeIdentifier) - { - this.storeIdentifier = storeIdentifier; - } - - public String getStoreIdentifier() - { - return storeIdentifier; - } - - public void setTransactionIds(List txnIds) - { - this.transactionIds = txnIds; - } - - public List getTransactionIds() - { - return transactionIds; - } - - public Long getFromTxnId() - { - return fromTxnId; - } - - public void setFromTxnId(Long fromTxnId) - { - this.fromTxnId = fromTxnId; - } - - public Long getToTxnId() - { - return toTxnId; - } - - public void setToTxnId(Long toTxnId) - { - this.toTxnId = toTxnId; - } - - public Long getFromNodeId() - { - return fromNodeId; - } - - public void setFromNodeId(Long fromNodeId) - { - this.fromNodeId = fromNodeId; - } - - public Long getToNodeId() - { - return toNodeId; - } - - public void setToNodeId(Long toNodeId) - { - this.toNodeId = toNodeId; - } - - public Set getIncludeNodeTypes() - { - return includeNodeTypes; - } - - public Set getExcludeNodeTypes() - { - return excludeNodeTypes; - } - - public Set getIncludeAspects() - { - return includeAspects; - } - - public Set getExcludeAspects() - { - return excludeAspects; - } - - public void setIncludeNodeTypes(Set includeNodeTypes) - { - this.includeNodeTypes = includeNodeTypes; - } - - public void setExcludeNodeTypes(Set excludeNodeTypes) - { - this.excludeNodeTypes = excludeNodeTypes; - } - - public void setIncludeAspects(Set includeAspects) - { - this.includeAspects = includeAspects; - } - - public void setExcludeAspects(Set excludeAspects) - { - this.excludeAspects = excludeAspects; - } - - public List getIncludeAspectIds() - { - return includeAspectIds; - } - - public void setIncludeAspectIds(List includeAspectIds) - { - this.includeAspectIds = includeAspectIds; - } - - public List getExcludeAspectIds() - { - return excludeAspectIds; - } - - public void setExcludeAspectIds(List excludeAspectIds) - { - this.excludeAspectIds = excludeAspectIds; - } - - public List getIncludeTypeIds() - { - return includeTypeIds; - } - - public void setIncludeTypeIds(List includeTypeIds) - { - this.includeTypeIds = includeTypeIds; - } - - public List getExcludeTypeIds() - { - return excludeTypeIds; - } - - public void setExcludeTypeIds(List excludeTypeIds) - { - this.excludeTypeIds = excludeTypeIds; - } - - public boolean isIncludeNodesTable() - { - return (getFromNodeId() != null || getToNodeId() != null || getIncludeTypeIds() != null || getExcludeTypeIds() != null || getIncludeAspectIds() != null || getExcludeAspectIds() != null); - } - -} +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.solr; + +import java.util.List; +import java.util.Set; + +import org.alfresco.service.namespace.QName; + +/** + * Stores node query parameters for use in SOLR DAO queries + * + * @since 4.0 + */ +public class NodeParameters +{ + private List transactionIds; + private Long fromTxnId; + private Long toTxnId; + + private Long fromNodeId; + private Long toNodeId; + + // default is 'all' results + private int maxResults = 0; + + private String storeProtocol; + private String storeIdentifier; + + private Set includeNodeTypes; + private Set excludeNodeTypes; + + private Set includeAspects; + private Set excludeAspects; + + public int getMaxResults() + { + return maxResults; + } + + public void setMaxResults(int maxResults) + { + this.maxResults = maxResults; + } + + public boolean getStoreFilter() + { + return (storeProtocol != null || storeIdentifier != null); + } + + public void setStoreProtocol(String storeProtocol) + { + this.storeProtocol = storeProtocol; + } + + public String getStoreProtocol() + { + return storeProtocol; + } + + public void setStoreIdentifier(String storeIdentifier) + { + this.storeIdentifier = storeIdentifier; + } + + public String getStoreIdentifier() + { + return storeIdentifier; + } + + public void setTransactionIds(List txnIds) + { + this.transactionIds = txnIds; + } + + public List getTransactionIds() + { + return transactionIds; + } + + public Long getFromTxnId() + { + return fromTxnId; + } + + public void setFromTxnId(Long fromTxnId) + { + this.fromTxnId = fromTxnId; + } + + public Long getToTxnId() + { + return toTxnId; + } + + public void setToTxnId(Long toTxnId) + { + this.toTxnId = toTxnId; + } + + public Long getFromNodeId() + { + return fromNodeId; + } + + public void setFromNodeId(Long fromNodeId) + { + this.fromNodeId = fromNodeId; + } + + public Long getToNodeId() + { + return toNodeId; + } + + public void setToNodeId(Long toNodeId) + { + this.toNodeId = toNodeId; + } + + public Set getIncludeNodeTypes() + { + return includeNodeTypes; + } + + public Set getExcludeNodeTypes() + { + return excludeNodeTypes; + } + + public Set getIncludeAspects() + { + return includeAspects; + } + + public Set getExcludeAspects() + { + return excludeAspects; + } + + public void setIncludeNodeTypes(Set includeNodeTypes) + { + this.includeNodeTypes = includeNodeTypes; + } + + public void setExcludeNodeTypes(Set excludeNodeTypes) + { + this.excludeNodeTypes = excludeNodeTypes; + } + + public void setIncludeAspects(Set includeAspects) + { + this.includeAspects = includeAspects; + } + + public void setExcludeAspects(Set excludeAspects) + { + this.excludeAspects = excludeAspects; + } +} diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java new file mode 100644 index 0000000000..6d729f462b --- /dev/null +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.solr; + +import java.util.List; + +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.solr.AclChangeSet; +import org.alfresco.repo.domain.solr.Transaction; + +/** + * Interface for component to provide tracking data for SOLR. + * + * @since 4.0 + */ +public interface SOLRTrackingComponent +{ + /** + * Get the ACL changesets for given range parameters + * + * @param minAclChangeSetId minimum ACL changeset ID - (inclusive and optional) + * @param fromCommitTime minimum ACL commit time - (inclusive and optional) + * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results + * @return list of ACL changesets + */ + public List getAclChangeSets(Long minAclChangeSetId, Long fromCommitTime, int maxResults); + + /** + * Get the transactions from either minTxnId or fromCommitTime, optionally limited to maxResults + * + * @param minTxnId greater than or equal to minTxnId + * @param fromCommitTime greater than or equal to transaction commit time + * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results + * @return list of transactions + */ + public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults); + + /** + * Get the nodes satisfying the constraints in nodeParameters + * + * @param nodeParameters set of constraints for which nodes to return + * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results + * @param callback a callback to receive the results + */ + public void getNodes(NodeParameters nodeParameters, NodeQueryCallback callback); + + /** + * Returns metadata for a set of node ids + * + * @param nodeIds a set of nodeIds for which to return node metadata + * @param maxResults limit the results. 0 or Integer.MAX_VALUE does not limit the results + * @param callback a callback to receive the results + */ + public void getNodesMetadata(NodeMetaDataParameters nodeMetaDataParameters, MetaDataResultsFilter resultFilter, NodeMetaDataQueryCallback callback); + + /** + * The interface that will be used to give query results to the calling code. + */ + public interface NodeQueryCallback + { + /** + * Handle a node. + * + * @param node the node + * @return Return true to continue processing rows or false to stop + */ + boolean handleNode(Node node); + } + + /** + * The interface that will be used to give query results to the calling code. + */ + public interface NodeMetaDataQueryCallback + { + /** + * Handle a node. + * + * @param node the node meta data + * @return Return true to continue processing rows or false to stop + */ + boolean handleNodeMetaData(NodeMetaData nodeMetaData); + } +} diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java new file mode 100644 index 0000000000..3c57bee975 --- /dev/null +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.solr; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.domain.solr.AclChangeSet; +import org.alfresco.repo.domain.solr.SOLRDAO; +import org.alfresco.repo.domain.solr.Transaction; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.OwnableService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; + +/** + * Component providing data for SOLR tracking + * + * @since 4.0 + */ +public class SOLRTrackingComponentImpl implements SOLRTrackingComponent +{ + private NodeDAO nodeDAO; + private QNameDAO qnameDAO; + private SOLRDAO solrDAO; + private OwnableService ownableService; + private TenantService tenantService; + private DictionaryService dictionaryService; + + public void setSolrDAO(SOLRDAO solrDAO) + { + this.solrDAO = solrDAO; + } + + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + public void setOwnableService(OwnableService ownableService) + { + this.ownableService = ownableService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Initialize + */ + public void init() + { + PropertyCheck.mandatory(this, "solrDAO", solrDAO); + PropertyCheck.mandatory(this, "nodeDAO", nodeDAO); + PropertyCheck.mandatory(this, "qnameDAO", qnameDAO); + PropertyCheck.mandatory(this, "ownableService", ownableService); + PropertyCheck.mandatory(this, "tenantService", tenantService); + PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); + } + + @Override + public List getAclChangeSets(Long minAclChangeSetId, Long fromCommitTime, int maxResults) + { + List changesets = solrDAO.getAclChangeSets(minAclChangeSetId, fromCommitTime, maxResults); + return changesets; + } + + @Override + public List getTransactions(Long minTxnId, Long fromCommitTime, int maxResults) + { + List txns = solrDAO.getTransactions(minTxnId, fromCommitTime, maxResults); + return txns; + } + + /** + * {@inheritDoc} + */ + public void getNodes(NodeParameters nodeParameters, NodeQueryCallback callback) + { + List nodes = solrDAO.getNodes(nodeParameters); + + for (Node node : nodes) + { + callback.handleNode(node); + } + } + + /** + * A dumb iterator that iterates over longs in sequence. + */ + private static class SequenceIterator implements Iterable, Iterator + { + private long fromId; + private long toId; + private long counter; + private int maxResults; + private boolean inUse = false; + + SequenceIterator(Long fromId, Long toId, int maxResults) + { + this.fromId = (fromId == null ? 1 : fromId.longValue()); + this.toId = (toId == null ? Long.MAX_VALUE : toId.longValue()); + this.maxResults = maxResults; + this.counter = this.fromId; + } + + @Override + public Iterator iterator() + { + if(inUse) + { + throw new IllegalStateException("Already in use"); + } + this.counter = this.fromId; + this.inUse = true; + return this; + } + + @Override + public boolean hasNext() + { + return ((counter - this.fromId) < maxResults) && counter <= toId; + } + + @Override + public Long next() + { + return counter++; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + } + + private boolean isCategorised(AspectDefinition aspDef) + { + if(aspDef == null) + { + return false; + } + AspectDefinition current = aspDef; + while (current != null) + { + if (current.getName().equals(ContentModel.ASPECT_CLASSIFIABLE)) + { + return true; + } + else + { + QName parentName = current.getParentName(); + if (parentName == null) + { + break; + } + current = dictionaryService.getAspect(parentName); + } + } + return false; + } + + private Collection> getCategoryPaths(NodeRef nodeRef, Set aspects, Map properties) + { + ArrayList> categoryPaths = new ArrayList>(); + + for (QName classRef : aspects) + { + AspectDefinition aspDef = dictionaryService.getAspect(classRef); + if (!isCategorised(aspDef)) + { + continue; + } + LinkedList> aspectPaths = new LinkedList>(); + for (PropertyDefinition propDef : aspDef.getProperties().values()) + { + if (propDef.getDataType().getName().equals(DataTypeDefinition.CATEGORY)) + { + for (NodeRef catRef : DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, properties.get(propDef.getName()))) + { + if (catRef == null) + { + continue; + } + // can be running in context of System user, hence use input nodeRef + catRef = tenantService.getName(nodeRef, catRef); + + try + { + Pair pair = nodeDAO.getNodePair(catRef); + for (Path path : nodeDAO.getPaths(pair, false)) + { + if ((path.size() > 1) && (path.get(1) instanceof Path.ChildAssocElement)) + { + Path.ChildAssocElement cae = (Path.ChildAssocElement) path.get(1); + boolean isFakeRoot = true; + + final List results = new ArrayList(10); + // We have a callback handler to filter results + ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback() + { + public boolean preLoadNodes() + { + return false; + } + + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + results.add(childAssocPair.getSecond()); + return true; + } + + public void done() + { + } + }; + + Pair caePair = nodeDAO.getNodePair(cae.getRef().getChildRef()); + nodeDAO.getParentAssocs(caePair.getFirst(), null, null, false, callback); + for (ChildAssociationRef car : results) + { + if (cae.getRef().equals(car)) + { + isFakeRoot = false; + break; + } + } + if (isFakeRoot) + { + if (path.toString().indexOf(aspDef.getName().toString()) != -1) + { + aspectPaths.add(new Pair(path, aspDef.getName())); + } + } + } + } + } + catch (InvalidNodeRefException e) + { + // If the category does not exists we move on the next + } + } + } + } + categoryPaths.addAll(aspectPaths); + } + // Add member final element + for (Pair pair : categoryPaths) + { + if (pair.getFirst().last() instanceof Path.ChildAssocElement) + { + Path.ChildAssocElement cae = (Path.ChildAssocElement) pair.getFirst().last(); + ChildAssociationRef assocRef = cae.getRef(); + pair.getFirst().append(new Path.ChildAssocElement(new ChildAssociationRef(assocRef.getTypeQName(), assocRef.getChildRef(), QName.createQName("member"), nodeRef))); + } + } + + return categoryPaths; + } + + private List preCacheNodes(NodeMetaDataParameters nodeMetaDataParameters) + { + int maxResults = nodeMetaDataParameters.getMaxResults(); + boolean isLimitSet = (maxResults != 0 && maxResults != Integer.MAX_VALUE); + + List nodeIds = null; + Iterable iterable = null; + List allNodeIds = nodeMetaDataParameters.getNodeIds(); + if(allNodeIds != null) + { + int toIndex = (maxResults > allNodeIds.size() ? allNodeIds.size() : maxResults); + nodeIds = isLimitSet ? allNodeIds.subList(0, toIndex) : nodeMetaDataParameters.getNodeIds(); + iterable = nodeMetaDataParameters.getNodeIds(); + } + else + { + Long fromNodeId = nodeMetaDataParameters.getFromNodeId(); + Long toNodeId = nodeMetaDataParameters.getToNodeId(); + nodeIds = new ArrayList(isLimitSet ? maxResults : 100); // TODO better default here? + iterable = new SequenceIterator(fromNodeId, toNodeId, maxResults); + int counter = 1; + for(Long nodeId : iterable) + { + if(isLimitSet && counter++ > maxResults) + { + break; + } + nodeIds.add(nodeId); + } + } + // pre-cache nodes + nodeDAO.cacheNodesById(nodeIds); + + return nodeIds; + } + + /** + * {@inheritDoc} + */ + public void getNodesMetadata( + NodeMetaDataParameters nodeMetaDataParameters, + MetaDataResultsFilter resultFilter, + NodeMetaDataQueryCallback callback) + { + NodeMetaDataQueryRowHandler rowHandler = new NodeMetaDataQueryRowHandler(callback); + boolean includeType = (resultFilter == null ? true : resultFilter.getIncludeType()); + boolean includeProperties = (resultFilter == null ? true : resultFilter.getIncludeProperties()); + boolean includeAspects = (resultFilter == null ? true : resultFilter.getIncludeAspects()); + boolean includePaths = (resultFilter == null ? true : resultFilter.getIncludePaths()); + boolean includeNodeRef = (resultFilter == null ? true : resultFilter.getIncludeNodeRef()); + boolean includeAssociations = (resultFilter == null ? true : resultFilter.getIncludeAssociations()); + boolean includeChildAssociations = (resultFilter == null ? true : resultFilter.getIncludeChildAssociations()); + boolean includeOwner = (resultFilter == null ? true : resultFilter.getIncludeOwner()); + + List nodeIds = preCacheNodes(nodeMetaDataParameters); + + for(Long nodeId : nodeIds) + { + Map props = null; + Set aspects = null; + + if (!nodeDAO.exists(nodeId)) + { + // Deleted nodes have no metadata + continue; + } + + NodeMetaData nodeMetaData = new NodeMetaData(); + nodeMetaData.setNodeId(nodeId); + + Pair pair = nodeDAO.getNodePair(nodeId); + nodeMetaData.setAclId(nodeDAO.getNodeAclId(nodeId)); + + if(includeType) + { + QName nodeType = nodeDAO.getNodeType(nodeId); + nodeMetaData.setNodeType(nodeType); + } + + if(includePaths || includeProperties) + { + props = nodeDAO.getNodeProperties(nodeId); + } + nodeMetaData.setProperties(props); + + if(includePaths || includeAspects) + { + aspects = nodeDAO.getNodeAspects(nodeId); + } + nodeMetaData.setAspects(aspects); + + // TODO paths may change during get i.e. node moved around in the graph + if(includePaths) + { + Collection> categoryPaths = getCategoryPaths(pair.getSecond(), aspects, props); + List directPaths = nodeDAO.getPaths(pair, false); + + Collection> paths = new ArrayList>(directPaths.size() + categoryPaths.size()); + for (Path path : directPaths) + { + paths.add(new Pair(path, null)); + } + paths.addAll(categoryPaths); + + nodeMetaData.setPaths(paths); + } + + if(includeNodeRef) + { + nodeMetaData.setNodeRef(pair.getSecond()); + } + + if(includeChildAssociations) + { + final List childAssocs = new ArrayList(100); + nodeDAO.getChildAssocs(nodeId, null, null, null, null, null, new ChildAssocRefQueryCallback() + { + @Override + public boolean preLoadNodes() + { + return false; + } + + @Override + public boolean handle(Pair childAssocPair, Pair parentNodePair, + Pair childNodePair) + { + childAssocs.add(childAssocPair.getSecond()); + return true; + } + + @Override + public void done() + { + } + }); + nodeMetaData.setChildAssocs(childAssocs); + } + + if(includeAssociations) + { + final List parentAssocs = new ArrayList(100); + nodeDAO.getParentAssocs(nodeId, null, null, null, new ChildAssocRefQueryCallback() + { + @Override + public boolean handle(Pair childAssocPair, + Pair parentNodePair, Pair childNodePair) + { + parentAssocs.add(childAssocPair.getSecond()); + return true; + } + + @Override + public boolean preLoadNodes() + { + return false; + } + + @Override + public void done() + { + } + }); + + // TODO non-child associations +// Collection> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId); +// Collection> targetAssocs = nodeDAO.getTargetNodeAssocs(nodeId); +// +// nodeMetaData.setAssocs(); + } + + if(includeOwner) + { + // cached in OwnableService + nodeMetaData.setOwner(ownableService.getOwner(pair.getSecond())); + } + + rowHandler.processResult(nodeMetaData); + } + } + + /** + * Class that passes results from a result entity into the client callback + */ + protected class NodeQueryRowHandler + { + private final NodeQueryCallback callback; + private boolean more; + + private NodeQueryRowHandler(NodeQueryCallback callback) + { + this.callback = callback; + this.more = true; + } + + public void processResult(Node row) + { + if (!more) + { + // No more results required + return; + } + + more = callback.handleNode(row); + } + } + + /** + * Class that passes results from a result entity into the client callback + */ + protected class NodeMetaDataQueryRowHandler + { + private final NodeMetaDataQueryCallback callback; + private boolean more; + + private NodeMetaDataQueryRowHandler(NodeMetaDataQueryCallback callback) + { + this.callback = callback; + this.more = true; + } + + public void processResult(NodeMetaData row) + { + if (!more) + { + // No more results required + return; + } + + more = callback.handleNodeMetaData(row); + } + } +} diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java new file mode 100644 index 0000000000..e543faf670 --- /dev/null +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java @@ -0,0 +1,1033 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.solr; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.solr.Transaction; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.solr.MetaDataResultsFilter; +import org.alfresco.repo.solr.NodeMetaData; +import org.alfresco.repo.solr.NodeMetaDataParameters; +import org.alfresco.repo.solr.NodeParameters; +import org.alfresco.repo.solr.SOLRTrackingComponent.NodeMetaDataQueryCallback; +import org.alfresco.repo.solr.SOLRTrackingComponent.NodeQueryCallback; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Tests tracking component + * + * @since 4.0 + */ +public class SOLRTrackingComponentTest extends TestCase +{ + private static final Log logger = LogFactory.getLog(SOLRTrackingComponentTest.class); + + private ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext(); + private static enum NodeStatus + { + UPDATED, DELETED; + } + + private AuthenticationComponent authenticationComponent; + private TransactionService transactionService; + private RetryingTransactionHelper txnHelper; + private NodeService nodeService; + private FileFolderService fileFolderService; + private NodeDAO nodeDAO; + private SOLRTrackingComponent solrTrackingComponent; + + private StoreRef storeRef; + private NodeRef rootNodeRef; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + transactionService = serviceRegistry.getTransactionService(); + txnHelper = transactionService.getRetryingTransactionHelper(); + + solrTrackingComponent = (SOLRTrackingComponent) ctx.getBean("solrTrackingComponent"); + nodeDAO = (NodeDAO)ctx.getBean("nodeDAO"); + nodeService = (NodeService)ctx.getBean("NodeService"); + fileFolderService = (FileFolderService)ctx.getBean("FileFolderService"); + authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); + + authenticationComponent.setSystemUserAsCurrentUser(); + + storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis()); + rootNodeRef = nodeService.getRootNode(storeRef); + } + + public void testGetNodeMetaData() + { + long startTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest3(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); + st.buildTransactions(); + + List txns = solrTrackingComponent.getTransactions(null, startTime, 50); + + int[] updates = new int[] {1, 1}; + int[] deletes = new int[] {0, 1}; + List txnIds = checkTransactions(txns, 2, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); + getNodeMetaData(nodeMetaDataParams, null, st); + } + + public void testGetNodeMetaData100Nodes() + { + long startTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest100Nodes(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testGetNodeMetaData", true, true); + st.buildTransactions(); + + List txns = solrTrackingComponent.getTransactions(null, startTime, 50); + + int[] updates = new int[] {100}; + int[] deletes = new int[] {0}; + List txnIds = checkTransactions(txns, 1, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + +// assertEquals("Unxpected number of nodes", 3, nodeQueryCallback.getSuccessCount()); + + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); + getNodeMetaData(nodeMetaDataParams, null, st); + + nodeMetaDataParams.setMaxResults(20); + getNodeMetaData(nodeMetaDataParams, null, st); + +// assertEquals("Unxpected number of nodes", 3, bt.getSuccessCount()); + } + + public void testNodeMetaDataManyNodes() throws Exception + { + long fromCommitTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest4(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataManyNodes", true, false); + st.buildTransactions(); + + List txns = solrTrackingComponent.getTransactions(null, fromCommitTime, 50); + + int[] updates = new int[] {2001}; + int[] deletes = new int[] {0}; + List txnIds = checkTransactions(txns, 1, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + + // make sure caches are warm - time last call + logger.debug("Cold cache"); + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); + getNodeMetaData(nodeMetaDataParams, null, st); + getNodeMetaData(nodeMetaDataParams, null, st); + logger.debug("Warm cache"); + getNodeMetaData(nodeMetaDataParams, null, st); + + // clear out node caches + nodeDAO.clear(); + + logger.debug("Cold cache - explicit clear"); + nodeMetaDataParams.setMaxResults(800); + getNodeMetaData(nodeMetaDataParams, null, st); + getNodeMetaData(nodeMetaDataParams, null, st); + logger.debug("Warm cache"); + getNodeMetaData(nodeMetaDataParams, null, st); + + logger.debug("Cold cache - explicit clear"); + nodeMetaDataParams.setMaxResults(500); + getNodeMetaData(nodeMetaDataParams, null, st); + getNodeMetaData(nodeMetaDataParams, null, st); + logger.debug("Warm cache"); + getNodeMetaData(nodeMetaDataParams, null, st); + + logger.debug("Cold cache - explicit clear"); + nodeMetaDataParams.setMaxResults(200); + getNodeMetaData(nodeMetaDataParams, null, st); + getNodeMetaData(nodeMetaDataParams, null, st); + logger.debug("Warm cache"); + getNodeMetaData(nodeMetaDataParams, null, st); + + // clear out node caches + nodeDAO.clear(); + + logger.debug("Cold cache - explicit clear"); + getNodeMetaData(nodeMetaDataParams, null, st); + } + + public void testNodeMetaDataCache() throws Exception + { + long fromCommitTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest4(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataManyNodes", true, false); + st.buildTransactions(); + + List txns = solrTrackingComponent.getTransactions(null, fromCommitTime, 50); + + int[] updates = new int[] {2001}; + int[] deletes = new int[] {0}; + List txnIds = checkTransactions(txns, 1, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + + // clear out node caches + nodeDAO.clear(); + + logger.debug("Cold cache - explicit clear"); + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); + MetaDataResultsFilter filter = new MetaDataResultsFilter(); + filter.setIncludeAssociations(false); + //filter.setIncludePaths(false); + filter.setIncludeChildAssociations(false); + getNodeMetaData(nodeMetaDataParams, filter, st); + } + + public void testNodeMetaDataNullPropertyValue() throws Exception + { + long fromCommitTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest5(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testNodeMetaDataNullPropertyValue", true, true); + st.buildTransactions(); + + List txns = solrTrackingComponent.getTransactions(null, fromCommitTime, 50); + + int[] updates = new int[] {11}; + int[] deletes = new int[] {0}; + List txnIds = checkTransactions(txns, 1, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); + getNodeMetaData(nodeMetaDataParams, null, st); + } + + public void testFilters() + { + long startTime = System.currentTimeMillis(); + + SOLRTest st = new SOLRTest1(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, "testFilters", true, true); + st.buildTransactions(); + + List txns = solrTrackingComponent.getTransactions(null, startTime, 50); + + int[] updates = new int[] {1, 1}; + int[] deletes = new int[] {0, 1}; + List txnIds = checkTransactions(txns, 2, updates, deletes); + + NodeParameters nodeParameters = new NodeParameters(); + nodeParameters.setTransactionIds(txnIds); + getNodes(nodeParameters, st); + + NodeMetaDataParameters nodeMetaDataParams = new NodeMetaDataParameters(); + nodeMetaDataParams.setNodeIds(st.getNodeIds()); + getNodeMetaData(nodeMetaDataParams, null, st); + } + + private static class NodeAssertions + { + private Set aspects; + private Map properties; + private NodeStatus nodeStatus; + private Boolean expectAspects = true; + private Boolean expectProperties = true; + private boolean expectType = true; + private boolean expectOwner = true; + private boolean expectAssociations = true; + private boolean expectPaths = true; + private boolean expectAclId = true; + + public NodeAssertions() + { + super(); + } + + public boolean isExpectType() + { + return expectType; + } + + public boolean isExpectOwner() + { + return expectOwner; + } + + public boolean isExpectAssociations() + { + return expectAssociations; + } + + public boolean isExpectPaths() + { + return expectPaths; + } + + public boolean isExpectAclId() + { + return expectAclId; + } + + public boolean isExpectAspects() + { + return expectAspects; + } + + public boolean isExpectProperties() + { + return expectProperties; + } + + public void setNodeStatus(NodeStatus nodeStatus) + { + this.nodeStatus = nodeStatus; + } + + public NodeStatus getNodeStatus() + { + return nodeStatus; + } + + public Set getAspects() + { + return aspects; + } + + public Map getProperties() + { + return properties; + } + } + + private List checkTransactions(List txns, int numTransactions, int[] updates, int[] deletes) + { + assertEquals("Number of transactions is incorrect", numTransactions, txns.size()); + + List txnIds = new ArrayList(txns.size()); + int i = 0; + for(Transaction txn : txns) + { + assertEquals("Number of deletes is incorrect", deletes[i], txn.getDeletes()); + assertEquals("Number of updates is incorrect", updates[i], txn.getUpdates()); + i++; + + txnIds.add(txn.getId()); + } + + return txnIds; + } + + private void getNodes(NodeParameters nodeParameters, SOLRTest bt) + { + long startTime = System.currentTimeMillis(); + solrTrackingComponent.getNodes(nodeParameters, bt); + long endTime = System.currentTimeMillis(); + + bt.runNodeChecks(nodeParameters.getMaxResults()); + + logger.debug("Got " + bt.getActualNodeCount() + " nodes in " + (endTime - startTime) + " ms"); + } + + private void getNodeMetaData(NodeMetaDataParameters params, MetaDataResultsFilter filter, SOLRTest bt) + { + bt.clearNodesMetaData(); + + long startTime = System.currentTimeMillis(); + solrTrackingComponent.getNodesMetadata(params, filter, bt); + long endTime = System.currentTimeMillis(); + + bt.runNodeMetaDataChecks(params.getMaxResults()); + + logger.debug("Got " + bt.getActualNodeMetaDataCount() + " node metadatas in " + (endTime - startTime) + " ms"); + } + + private static abstract class SOLRTest implements NodeQueryCallback, NodeMetaDataQueryCallback + { + protected FileFolderService fileFolderService; + protected RetryingTransactionHelper txnHelper; + protected NodeService nodeService; + protected NodeRef rootNodeRef; + protected NodeDAO nodeDAO; + + protected String containerName; + protected Map nodeAssertions; + + protected boolean doChecks; + protected boolean doNodeChecks; + protected boolean doMetaDataChecks; + + protected int successCount = 0; + protected int failureCount = 0; + + protected List nodeIds; + + protected long expectedNumMetaDataNodes = 0; + + protected long actualNodeCount = 0; + protected long actualNodeMetaDataCount = 0; + + SOLRTest(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, + NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) + { + this.txnHelper = txnHelper; + this.nodeService = nodeService; + this.rootNodeRef = rootNodeRef; + this.fileFolderService = fileFolderService; + this.nodeDAO = nodeDAO; + + this.containerName = containerName; + this.nodeAssertions = new HashMap(); + this.nodeIds = new ArrayList(getExpectedNumNodes()); + + this.doNodeChecks = doNodeChecks; + this.doMetaDataChecks = doMetaDataChecks; + this.doChecks = doNodeChecks || doMetaDataChecks; + } + + void runNodeChecks(int maxResults) + { + if(doNodeChecks) + { + if(maxResults != 0 && maxResults != Integer.MAX_VALUE) + { + assertEquals("Number of returned nodes is incorrect", maxResults, getActualNodeCount()); + } + else + { + assertEquals("Number of returned nodes is incorrect", getExpectedNumNodes(), getActualNodeCount()); + } + assertEquals("Unexpected failures", 0, getFailureCount()); + assertEquals("Success count is incorrect", getActualNodeCount(), getSuccessCount()); + } + } + + void runNodeMetaDataChecks(int maxResults) + { + if(maxResults != 0 && maxResults != Integer.MAX_VALUE) + { + assertEquals("Number of returned nodes is incorrect", maxResults, getActualNodeMetaDataCount()); + } + else + { + assertEquals("Number of returned nodes is incorrect", getExpectedNumMetaDataNodes(), getActualNodeMetaDataCount()); + } + } + + void clearNodesMetaData() + { + successCount = 0; + failureCount = 0; + actualNodeMetaDataCount = 0; + nodeAssertions.clear(); + } + + public long getActualNodeCount() + { + return actualNodeCount; + } + + public long getActualNodeMetaDataCount() + { + return actualNodeMetaDataCount; + } + + protected long getExpectedNumMetaDataNodes() + { + return expectedNumMetaDataNodes; + } + + protected abstract int getExpectedNumNodes(); + protected abstract void buildTransactionsInternal(); + + public NodeAssertions getNodeAssertions(NodeRef nodeRef) + { + NodeAssertions assertions = nodeAssertions.get(nodeRef); + if(assertions == null) + { + assertions = new NodeAssertions(); + nodeAssertions.put(nodeRef, assertions); + } + return assertions; + } + + protected void setExpectedNodeStatus(NodeRef nodeRef, NodeStatus nodeStatus) + { + if(nodeStatus == NodeStatus.UPDATED) + { + expectedNumMetaDataNodes++; + } + + if(doChecks) + { + NodeAssertions nodeAssertions = getNodeAssertions(nodeRef); + nodeAssertions.setNodeStatus(nodeStatus); + } + } + + void buildTransactions() + { + buildTransactionsInternal(); + } + + @Override + public boolean handleNode(Node node) { + actualNodeCount++; + + if(doNodeChecks) + { + NodeRef nodeRef = node.getNodeRef(); + Boolean isDeleted = node.getDeleted(); + nodeIds.add(node.getId()); + + NodeAssertions expectedStatus = getNodeAssertions(nodeRef); + if(expectedStatus == null) + { + throw new RuntimeException("Unexpected missing assertion for NodeRef " + nodeRef); + } + + if((expectedStatus.getNodeStatus() == NodeStatus.DELETED && isDeleted) || + (expectedStatus.getNodeStatus() == NodeStatus.UPDATED && !isDeleted)) + { + successCount++; + } + else + { + failureCount++; + } + } + + return true; + } + +/* private boolean compareProperties(Map properties1, Map properties2) + { + boolean match = true; + + if(properties1.size() != properties2.size()) + { + match = false; + } + else + { + for(QName qname : properties1.keySet()) + { + Serializable value1 = properties1.get(qname); + Serializable value2 = properties2.get(qname); + if(value1 instanceof MLText) + { + if(!(value2 instanceof MLText)) + { + match = false; + break; + } + MLText ml1 = (MLText)value1; + MLText ml2 = (MLText)value2; + if(ml1.getDefaultValue().equals(ml2.getDefaultValue())) + { + match = false; + break; + } + } + else if(value1 instanceof ContentDataWithId) + { + if(!(value2 instanceof ContentDataWithId)) + { + match = false; + break; + } + ContentDataWithId cd1 = (ContentDataWithId)value1; + ContentDataWithId cd2 = (ContentDataWithId)value2; + if(cd1.getDefaultValue().equals(ml2.getDefaultValue())) + { + match = false; + break; + } + } + else + { + if(!value1.equals(value2)) + { + match = false; + break; + } + } + } + } + + return match; + }*/ + + @Override + public boolean handleNodeMetaData(NodeMetaData nodeMetaData) { + actualNodeMetaDataCount++; + + if(doMetaDataChecks) + { + Long nodeId = nodeMetaData.getNodeId(); + NodeRef nodeRef = nodeMetaData.getNodeRef(); + + Set aspects = nodeMetaData.getAspects(); + Set actualAspects = nodeService.getAspects(nodeRef); + assertEquals("Aspects are incorrect", actualAspects, aspects); + + Map properties = nodeMetaData.getProperties(); + // NodeService converts properties so use nodeDAO to get unadulterated property value + Map actualProperties = nodeDAO.getNodeProperties(nodeId); + //assertTrue("Properties are incorrect", compareProperties(actualProperties, properties)); + assertEquals("Properties are incorrect", actualProperties, properties); + + NodeAssertions assertions = getNodeAssertions(nodeRef); +// NodeAssertions assertions = nodes.get(nodeRef); + + Set expectedAspects = assertions.getAspects(); + if(expectedAspects != null) + { + for(QName aspect : expectedAspects) + { + assertTrue("Expected aspect" + aspect, aspects.contains(aspect)); + } + } + + Map expectedProperties = assertions.getProperties(); + if(expectedProperties != null) + { + for(QName propName : expectedProperties.keySet()) + { + Serializable expectedPropValue = expectedProperties.get(propName); + Serializable actualPropValue = properties.get(propName); + assertNotNull("Missing property " + propName, actualPropValue); + assertEquals("Incorrect property value", expectedPropValue, actualPropValue); + } + } + + // TODO complete path tests +// List actualPaths = nodeMetaData.getPaths(); +// List expectedPaths = nodeService.getPaths(nodeRef, false); +// assertEquals("Paths are incorrect", expectedPaths, actualPaths); + + boolean expectAspects = assertions.isExpectAspects(); + if(expectAspects && nodeMetaData.getAspects() == null) + { + fail("Expecting aspects but got no aspects"); + } + else if(!expectAspects && nodeMetaData.getAspects() != null) + { + fail("Not expecting aspects but got aspects"); + } + + boolean expectProperties = assertions.isExpectProperties(); + if(expectProperties && nodeMetaData.getProperties() == null) + { + fail("Expecting properties but got no properties"); + } + else if(!expectProperties && nodeMetaData.getProperties() != null) + { + fail("Not expecting properties but got properties"); + } + + boolean expectType = assertions.isExpectType(); + if(expectType && nodeMetaData.getNodeType() == null) + { + fail("Expecting type but got no type"); + } + else if(!expectType && nodeMetaData.getNodeType() != null) + { + fail("Not expecting type but got type"); + } + + boolean expectAclId = assertions.isExpectAclId(); + if(expectAclId && nodeMetaData.getAclId() == null) + { + fail("Expecting acl id but got no acl id"); + } + else if(!expectAclId && nodeMetaData.getAclId() != null) + { + fail("Not expecting acl id but got acl id"); + } + + boolean expectPaths = assertions.isExpectPaths(); + if(expectPaths && nodeMetaData.getPaths() == null) + { + fail("Expecting paths but got no paths"); + } + else if(!expectPaths && nodeMetaData.getPaths() != null) + { + fail("Not expecting paths but got paths"); + } + + boolean expectAssociations = assertions.isExpectAssociations(); + if(expectAssociations && nodeMetaData.getChildAssocs() == null) + { + fail("Expecting associations but got no associations"); + } + else if(!expectAssociations && nodeMetaData.getChildAssocs() != null) + { + fail("Not expecting associations but got associations"); + } + + boolean expectOwner = assertions.isExpectOwner(); + if(expectOwner && nodeMetaData.getOwner() == null) + { + fail("Expecting owner but got no owner"); + } + else if(!expectOwner && nodeMetaData.getOwner() != null) + { + fail("Not expecting owner but got owner"); + } + } + + successCount++; + + return true; + } + + public int getSuccessCount() + { + return successCount; + } + + public int getFailureCount() + { + return failureCount; + } + + public List getNodeIds() + { + return nodeIds; + } + } + + private static class SOLRTest1 extends SOLRTest + { + private NodeRef container; + private NodeRef content1; + private NodeRef content2; + + SOLRTest1( + RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, + NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) + { + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + } + + public int getExpectedNumNodes() + { + return 3; + } + + protected void buildTransactionsInternal() + { + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, "Container1"); + container = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + + FileInfo contentInfo = fileFolderService.create(container, "Content1", ContentModel.TYPE_CONTENT); + content1 = contentInfo.getNodeRef(); + + return null; + } + }); + + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + FileInfo contentInfo = fileFolderService.create(container, "Content2", ContentModel.TYPE_CONTENT); + content2 = contentInfo.getNodeRef(); + + fileFolderService.delete(content1); + + return null; + } + }); + + setExpectedNodeStatus(container, NodeStatus.UPDATED); + setExpectedNodeStatus(content1, NodeStatus.DELETED); + setExpectedNodeStatus(content2, NodeStatus.UPDATED); + } + } + + private static class SOLRTest3 extends SOLRTest + { + private NodeRef container; + private NodeRef content1; + private NodeRef content2; + + SOLRTest3(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, + NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) + { + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + } + + public int getExpectedNumNodes() + { + return 3; + } + + protected void buildTransactionsInternal() + { + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, "Container1"); + container = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + + FileInfo contentInfo = fileFolderService.create(container, "Content1", ContentModel.TYPE_CONTENT); + content1 = contentInfo.getNodeRef(); + + Map aspectProperties = new HashMap(); + aspectProperties.put(ContentModel.PROP_AUTHOR, "steve"); + nodeService.addAspect(content1, ContentModel.ASPECT_AUTHOR, aspectProperties); + + return null; + } + }); + + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + FileInfo contentInfo = fileFolderService.create(container, "Content2", ContentModel.TYPE_CONTENT); + content2 = contentInfo.getNodeRef(); + + nodeService.addAspect(content2, ContentModel.ASPECT_TEMPORARY, null); + fileFolderService.delete(content1); + + return null; + } + }); + + setExpectedNodeStatus(container, NodeStatus.UPDATED); + setExpectedNodeStatus(content1, NodeStatus.DELETED); + setExpectedNodeStatus(content2, NodeStatus.UPDATED); + } + } + + private static class SOLRTest100Nodes extends SOLRTest + { + SOLRTest100Nodes(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, + NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) + { + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + } + + public int getExpectedNumNodes() + { + return 100; + } + + protected void buildTransactionsInternal() + { + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, "Container100Nodes"); + NodeRef container = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + setExpectedNodeStatus(container, NodeStatus.UPDATED); + + for(int i = 0; i < 99; i++) + { + FileInfo contentInfo = fileFolderService.create(container, "Content" + i, ContentModel.TYPE_CONTENT); + NodeRef nodeRef = contentInfo.getNodeRef(); + + setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); + } + + return null; + } + }); + } + } + + private static class SOLRTest4 extends SOLRTest + { + private int numContentNodes = 2000; + + SOLRTest4(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, + NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) + { + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + } + + public int getExpectedNumNodes() + { + return numContentNodes + 1; + } + + public void buildTransactionsInternal() + { + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, containerName); + NodeRef container = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + setExpectedNodeStatus(container, NodeStatus.UPDATED); + + for(int i = 0; i < numContentNodes; i++) + { + FileInfo contentInfo = fileFolderService.create(container, "Content" + i, ContentModel.TYPE_CONTENT); + NodeRef nodeRef = contentInfo.getNodeRef(); + + if(i % 2 == 1) + { + nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); + } + nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, null); + + setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); + } + + return null; + } + }); + } + } + + private static class SOLRTest5 extends SOLRTest + { + private int numContentNodes = 10; + + SOLRTest5(RetryingTransactionHelper txnHelper, FileFolderService fileFolderService, NodeDAO nodeDAO, NodeService nodeService, + NodeRef rootNodeRef, String containerName, boolean doNodeChecks, boolean doMetaDataChecks) + { + super(txnHelper, fileFolderService, nodeDAO, nodeService, rootNodeRef, containerName, doNodeChecks, doMetaDataChecks); + } + + public int getExpectedNumNodes() + { + return numContentNodes + 1; + } + + public void buildTransactionsInternal() + { + final String titles[] = + { + "caf\u00E9", "\u00E7edilla", "\u00E0\u00E1\u00E2\u00E3", "\u00EC\u00ED\u00EE\u00EF", "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6", + "caf\u00E9", "\u00E7edilla", "\u00E0\u00E1\u00E2\u00E3", "\u00EC\u00ED\u00EE\u00EF", "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6" + }; + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + PropertyMap props = new PropertyMap(); + props.put(ContentModel.PROP_NAME, containerName); + NodeRef container = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + props).getChildRef(); + setExpectedNodeStatus(container, NodeStatus.UPDATED); + + for(int i = 0; i < numContentNodes; i++) + { + FileInfo contentInfo = fileFolderService.create(container, "Content" + i, ContentModel.TYPE_CONTENT); + NodeRef nodeRef = contentInfo.getNodeRef(); + + nodeService.addAspect(nodeRef, ContentModel.ASPECT_AUTHOR, null); + if(i % 5 == 1) + { + nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, null); + } + else + { + nodeService.setProperty(nodeRef, ContentModel.PROP_AUTHOR, "author" + i); + } + + nodeService.setProperty(nodeRef, ContentModel.PROP_TITLE, titles[i]); + + setExpectedNodeStatus(nodeRef, NodeStatus.UPDATED); + } + + return null; + } + }); + } + } +} diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index b3d8fb2b02..a29668a19b 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -40,6 +40,7 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.exceptions.TooManyResultsException; import org.hibernate.ObjectNotFoundException; import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; @@ -100,6 +101,7 @@ public class RetryingTransactionHelper BatchUpdateException.class, DataIntegrityViolationException.class, StaleStateException.class, + TooManyResultsException.class, // Expected one result but found multiple (bad key alert) ObjectNotFoundException.class, CacheException.class, // Usually a cache replication issue RemoteCacheException.class, // A cache replication issue diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderService.java b/source/java/org/alfresco/service/cmr/model/FileFolderService.java index a2c2d3f315..d027d60afc 100644 --- a/source/java/org/alfresco/service/cmr/model/FileFolderService.java +++ b/source/java/org/alfresco/service/cmr/model/FileFolderService.java @@ -19,6 +19,7 @@ package org.alfresco.service.cmr.model; import java.util.List; +import java.util.Set; import org.alfresco.service.Auditable; import org.alfresco.service.PublicService; @@ -50,6 +51,17 @@ public interface FileFolderService @Auditable(parameters = {"contextNodeRef"}) public List list(NodeRef contextNodeRef); + /** + * TEMP + * + * @deprecated for review (API is subject to change) + */ + public PagingFileInfoResults list(NodeRef contextNodeRef, + boolean files, + boolean folders, + Set ignoreTypeQNames, + PagingSortRequest pagingRequest); + /** * Lists all immediate child files of the given context node * diff --git a/source/java/org/alfresco/service/cmr/model/FileInfo.java b/source/java/org/alfresco/service/cmr/model/FileInfo.java index 0a6a0cba1c..1749bb0f4c 100644 --- a/source/java/org/alfresco/service/cmr/model/FileInfo.java +++ b/source/java/org/alfresco/service/cmr/model/FileInfo.java @@ -81,4 +81,9 @@ public interface FileInfo extends Serializable * @return Returns all the node properties */ public Map getProperties(); + + /** + * @return Returns (sub-)type of folder or file + */ + public QName getType(); } diff --git a/source/java/org/alfresco/service/cmr/model/PagingFileInfoResults.java b/source/java/org/alfresco/service/cmr/model/PagingFileInfoResults.java new file mode 100644 index 0000000000..5798d299aa --- /dev/null +++ b/source/java/org/alfresco/service/cmr/model/PagingFileInfoResults.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.cmr.model; + +import org.alfresco.service.cmr.repository.PagingResultsSPI; + +/** + * TEMP + * + * @deprecated for review (API is subject to change) + */ +public interface PagingFileInfoResults extends PagingResultsSPI +{ +} diff --git a/source/java/org/alfresco/service/cmr/model/PagingSortRequest.java b/source/java/org/alfresco/service/cmr/model/PagingSortRequest.java new file mode 100644 index 0000000000..3b0d9f3bde --- /dev/null +++ b/source/java/org/alfresco/service/cmr/model/PagingSortRequest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.cmr.model; + +import java.util.List; + +import org.alfresco.service.cmr.repository.PagingSortProp; + + +/** + * TEMP + * + * @deprecated for review (API is subject to change) + */ +public class PagingSortRequest +{ + private int skipCount; + private int maxItems; + private boolean requestTotalCount; + private List sortProps; + + public PagingSortRequest(int skipCount, int maxItems, boolean requestTotalCount, List sortProps) + { + this.skipCount = skipCount; + this.maxItems = maxItems; + this.requestTotalCount = requestTotalCount; + this.sortProps = sortProps; + } + + public int getSkipCount() + { + return skipCount; + } + + public int getMaxItems() + { + return maxItems; + } + + public boolean requestTotalCount() + { + return requestTotalCount; + } + + public List getSortProps() + { + return sortProps; + } +}